From 11d0e36b2afdaf24eb1aeeed3f319f31efc88fbd Mon Sep 17 00:00:00 2001 From: David Foster Date: Tue, 26 Sep 2023 22:17:41 -0400 Subject: [PATCH] MainWindow: Mark inconsistent and non-deterministic aspects of closing process --- src/crystal/browser/__init__.py | 9 ++++++++- src/crystal/model.py | 6 +++++- src/crystal/task.py | 9 +++++---- src/crystal/util/xthreading.py | 4 +--- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/crystal/browser/__init__.py b/src/crystal/browser/__init__.py index 75542fb5..4c572a86 100644 --- a/src/crystal/browser/__init__.py +++ b/src/crystal/browser/__init__.py @@ -376,9 +376,16 @@ def __exit__(self, exc_type, exc_value, exc_traceback) -> None: # === File Menu: Events === def _on_close_project(self, event: wx.CommandEvent) -> None: + # TODO: Move these dispose operations inside _on_close_frame(). + # But be careful! The last time this movement was attempted, + # various shutdown-related race conditions were triggered. + # + # Wait to perform movement until enough time to formalize + # a deterministic shutdown procedure. self.entity_tree.dispose() self.task_tree.dispose() - self._frame.Close() + + self._frame.Close() # will trigger call to _on_close_frame() def _on_quit(self, event: wx.CommandEvent) -> None: if event.Id == wx.ID_EXIT: diff --git a/src/crystal/model.py b/src/crystal/model.py index 5127b42e..35d2153d 100644 --- a/src/crystal/model.py +++ b/src/crystal/model.py @@ -697,7 +697,11 @@ def _resource_group_did_instantiate(self, group: ResourceGroup) -> None: # === Close === def close(self) -> None: - self.root_task.close() + # Stop scheduler thread soon + self.root_task.interrupt() + # (TODO: Actually wait for the scheduler thread to exit + # in a deterministic fashion, rather than relying on + # garbage collection to clean up objects.) # Disable Write Ahead Log (WAL) mode when closing database # in case the user decides to burn the project to read-only media, diff --git a/src/crystal/task.py b/src/crystal/task.py index fad07906..bd83ddff 100644 --- a/src/crystal/task.py +++ b/src/crystal/task.py @@ -1299,8 +1299,6 @@ class RootTask(Task): Access to this task's children list must be synchronized with the foreground thread. - - This task never completes. """ icon_name = None scheduling_style = SCHEDULING_STYLE_ROUND_ROBIN @@ -1365,8 +1363,11 @@ def fg_task() -> None: # NOTE: Must synchronize access to RootTask.children with foreground thread fg_call_and_wait(fg_task) - def close(self) -> None: - """Stop all descendent tasks, asynchronously.""" + def interrupt(self) -> None: + """ + Stop all descendent tasks, asynchronously, + by interrupting the scheduler thread. + """ self.finish() def __repr__(self) -> str: diff --git a/src/crystal/util/xthreading.py b/src/crystal/util/xthreading.py index 228ca309..f35c541e 100644 --- a/src/crystal/util/xthreading.py +++ b/src/crystal/util/xthreading.py @@ -232,9 +232,7 @@ def bg_call_later(callable: Callable[..., None], daemon: bool=False, *args) -> N if True, forces the background thread to be a daemon, and not prevent program termination while it is running. """ - thread = threading.Thread(target=callable, args=args) - if daemon: - thread.daemon = True + thread = threading.Thread(target=callable, args=args, daemon=daemon) thread.start()