From dc62876e14fb0b2f783282c8af3deef991fafb73 Mon Sep 17 00:00:00 2001 From: deltag0 Date: Wed, 3 Jun 2026 16:15:38 -0400 Subject: [PATCH 01/14] thumbnail generation cleanup on deletion Signed-off-by: deltag0 --- .../session_manager/local_thumbnail_gen.py | 64 +++++++++++++++++-- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 35bb9dfcc..bb8313d96 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -78,7 +78,9 @@ def __init__(self) -> None: self._loading_active = False self._display_preview = False if os.getenv("RV_SESSION_MANAGER_USE_THUMBNAILS") == "0" else True self._shutting_down = False - self._active_procs: list[subprocess.Popen] = [] + # Each entry pairs a running rvio process with the cache_key it is + # generating for, so a source deletion can cancel the matching proc. + self._active_procs: list[tuple[subprocess.Popen, str]] = [] self._procs_lock = threading.Lock() self._pool = ThreadPoolExecutor(max_workers=MAX_WORKERS) @@ -106,6 +108,11 @@ def global_bindings(self) -> list[tuple[str, Any, str]]: self._on_session_deletion, "Delete all cached local filmstrips and thumbnails on RV close", ), + ( + "before-source-delete", + self._on_source_delete, + "Cancel in-flight generation and evict cache when a media source is removed", + ), ( "play-start", self._on_play_start, @@ -396,7 +403,7 @@ def _write_filmstrip_session( return output_width, output_height - def _run_suspendable(self, cmd: list[str], timeout: int = 120) -> None: + def _run_suspendable(self, cmd: list[str], cache_key: str, timeout: int = 120) -> None: """Run a subprocess that can be suspended/resumed during playback. The timeout counts only non-suspended wall-clock time: while the @@ -427,7 +434,7 @@ def _run_suspendable(self, cmd: list[str], timeout: int = 120) -> None: finally: with self._procs_lock: try: - self._active_procs.remove(proc) + self._active_procs.remove((proc, cache_key)) except ValueError: logger.warning(f"Process {proc.pid} was not in active processes list") @@ -437,6 +444,7 @@ def _generate_thumbnail(self, cache_key: str, rvio_bin: str, media_path: str, mi try: self._run_suspendable( [rvio_bin, media_path, "-t", str(mid_frame), "-o", str(output_path)], + cache_key, ) except Exception as e: logger.error(f"Thumbnail generation failed: {e}") @@ -477,6 +485,7 @@ def _generate_filmstrip( "-o", str(output_path), ], + cache_key, ) except Exception as e: logger.error(f"Filmstrip generation failed: {e}") @@ -531,7 +540,7 @@ def _on_play_start(self, event: Any) -> None: self._playback_active = True if should_defer: return - for proc in self._active_procs: + for proc, _ in self._active_procs: _suspend_proc(proc) def _on_play_stop(self, event: Any) -> None: @@ -589,7 +598,7 @@ def _on_session_deletion(self, event: Any) -> None: event.reject() self._shutting_down = True with self._procs_lock: - for proc in self._active_procs: + for proc, _ in self._active_procs: _resume_proc(proc) try: proc.terminate() @@ -606,6 +615,51 @@ def _on_session_deletion(self, event: Any) -> None: logger.warning(f"Failed to delete cache directory {self._cache_dir}: {e}") self._cache.clear() + def _on_source_delete(self, event: Any) -> None: + """Cancel generation immediately and evict the cache for a removed media source. + """ + event.reject() + + source_node = event.contents() + media_path = self._get_media_path(source_node) + if not media_path: + return + + cache_key = self._cache_key(media_path) + + sources = self._cache_key_to_sources.get(cache_key) + if sources is not None: + sources.discard(source_node) + if sources: + return + self._cache_key_to_sources.pop(cache_key, None) + + # Kill any running rvio proc generating for this media. + with self._procs_lock: + for proc, proc_cache_key in self._active_procs: + if proc_cache_key == cache_key: + # Can't reliably kill a stopped proc, so resume before killing + _resume_proc(proc) + try: + proc.terminate() + except OSError: + logger.warning(f"Failed to terminate process {proc.pid}") + + self._deferred_jobs = [job for job in self._deferred_jobs if job[1] != cache_key] + + self._in_flight.discard(f"{cache_key}_thumbnail_path") + self._in_flight.discard(f"{cache_key}_filmstrip_path") + + self._deferred_sources.discard(source_node) + + cached = self._cache.pop(cache_key, {}) + for path in cached.values(): + if path: + try: + Path(path).unlink(missing_ok=True) + except Exception as e: + logger.warning(f"Failed to delete cached preview {path}: {e}") + def createMode() -> LocalThumbnailGen: global the_mode From fea5d559d4e312de6970b45eebc91c29cb3aa40c Mon Sep 17 00:00:00 2001 From: deltag0 Date: Thu, 4 Jun 2026 10:44:31 -0400 Subject: [PATCH 02/14] thumbnail cleanup with source node lookup Signed-off-by: deltag0 --- .../session_manager/local_thumbnail_gen.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index bb8313d96..7208eb963 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -235,9 +235,21 @@ def _get_media_path(self, source_node: str) -> str | None: try: return commands.getStringProperty(f"{source_node}.media.movie")[0] except Exception as e: - logger.warning(f"Could not get media path: {e}") + logger.debug(f"No media path for {source_node}: {e}") return None + def _source_node_of_group(self, group: str) -> str | None: + """ + Find the first RVFileSource or RVImageSource node in the group and return its node name. + """ + try: + for node in commands.nodesInGroup(group): + if commands.nodeType(node) in ("RVFileSource", "RVImageSource"): + return node + except Exception as e: + logger.debug(f"Could not resolve source node of group {group}: {e}") + return None + def _get_source_info(self, source_node: str) -> tuple[int, int, int, int] | None: # Skip inactive media representations if not commands.getIntProperty(f"{source_node}.media.active")[0]: @@ -620,7 +632,12 @@ def _on_source_delete(self, event: Any) -> None: """ event.reject() - source_node = event.contents() + node = event.contents() + + source_node = self._source_node_of_group(node) + if not source_node: + return + media_path = self._get_media_path(source_node) if not media_path: return From 5cf6d2cfe3de99c25ea76b05ea3456479b042b2f Mon Sep 17 00:00:00 2001 From: deltag0 Date: Thu, 4 Jun 2026 10:46:31 -0400 Subject: [PATCH 03/14] format Signed-off-by: deltag0 --- src/plugins/rv-packages/session_manager/local_thumbnail_gen.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 7208eb963..7b04eb698 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -628,8 +628,7 @@ def _on_session_deletion(self, event: Any) -> None: self._cache.clear() def _on_source_delete(self, event: Any) -> None: - """Cancel generation immediately and evict the cache for a removed media source. - """ + """Cancel generation immediately and evict the cache for a removed media source.""" event.reject() node = event.contents() From faf6b47c4b3493b6ae52b4d13ec5093a6d5925c5 Mon Sep 17 00:00:00 2001 From: deltag0 Date: Thu, 4 Jun 2026 11:05:10 -0400 Subject: [PATCH 04/14] docs to ensure correctness Signed-off-by: deltag0 --- src/plugins/rv-packages/session_manager/local_thumbnail_gen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 7b04eb698..714067889 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -240,7 +240,8 @@ def _get_media_path(self, source_node: str) -> str | None: def _source_node_of_group(self, group: str) -> str | None: """ - Find the first RVFileSource or RVImageSource node in the group and return its node name. + RVSourceGroup nodes have at most 1 RVFileSource or RVImageSource child (as a leaf), which is the actual media source. + Find it and return its node name. """ try: for node in commands.nodesInGroup(group): From 01c9b5e53633e28680fc9e31a50d095434834e97 Mon Sep 17 00:00:00 2001 From: deltag0 Date: Thu, 4 Jun 2026 11:43:05 -0400 Subject: [PATCH 05/14] change logger level Signed-off-by: deltag0 --- .../rv-packages/session_manager/local_thumbnail_gen.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 714067889..9bc89b795 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -78,8 +78,6 @@ def __init__(self) -> None: self._loading_active = False self._display_preview = False if os.getenv("RV_SESSION_MANAGER_USE_THUMBNAILS") == "0" else True self._shutting_down = False - # Each entry pairs a running rvio process with the cache_key it is - # generating for, so a source deletion can cancel the matching proc. self._active_procs: list[tuple[subprocess.Popen, str]] = [] self._procs_lock = threading.Lock() self._pool = ThreadPoolExecutor(max_workers=MAX_WORKERS) @@ -235,7 +233,7 @@ def _get_media_path(self, source_node: str) -> str | None: try: return commands.getStringProperty(f"{source_node}.media.movie")[0] except Exception as e: - logger.debug(f"No media path for {source_node}: {e}") + logger.warning(f"Could not get media path: {e}") return None def _source_node_of_group(self, group: str) -> str | None: @@ -248,7 +246,7 @@ def _source_node_of_group(self, group: str) -> str | None: if commands.nodeType(node) in ("RVFileSource", "RVImageSource"): return node except Exception as e: - logger.debug(f"Could not resolve source node of group {group}: {e}") + return return None def _get_source_info(self, source_node: str) -> tuple[int, int, int, int] | None: From ebbcb70cb0b0fcac62fb20ff1144c19b1f49a665 Mon Sep 17 00:00:00 2001 From: deltag0 Date: Fri, 5 Jun 2026 11:35:08 -0400 Subject: [PATCH 06/14] keep track of all sources Signed-off-by: deltag0 --- .../rv-packages/session_manager/local_thumbnail_gen.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 9bc89b795..233a3df85 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -68,6 +68,7 @@ def __init__(self) -> None: self._cache_dir = Path(tempfile.gettempdir()) / f"rv_thumbnails_{os.getpid()}" self._cache_dir.mkdir(parents=True, exist_ok=True) self._in_flight: set[str] = set() + # Cache key to set of source node names self._cache_key_to_sources: dict[str, set[str]] = {} self._deferred_sources: set[str] = set() self._deferred_jobs: list[tuple[str, str, str, str]] = [] @@ -157,6 +158,9 @@ def _get_cached_path(self, event: Any, path_key: str) -> None: return cache_key = self._cache_key(media_path) + + self._cache_key_to_sources.setdefault(cache_key, set()).add(source_node) + cached = self._cache.get(cache_key, {}) path = cached.get(path_key) @@ -164,8 +168,6 @@ def _get_cached_path(self, event: Any, path_key: str) -> None: event.setReturnContent(str(path)) return - self._cache_key_to_sources.setdefault(cache_key, set()).add(source_node) - flight_key = f"{cache_key}_{path_key}" if flight_key not in self._in_flight: self._start_generation(source_node, cache_key, media_path, path_key) From 4de2e8547f9befeec314ba7b3786304de9100567 Mon Sep 17 00:00:00 2001 From: deltag0 Date: Fri, 5 Jun 2026 13:30:46 -0400 Subject: [PATCH 07/14] robustness improvement Signed-off-by: deltag0 --- .../rv-packages/session_manager/local_thumbnail_gen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 233a3df85..fb80cdda2 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -644,6 +644,8 @@ def _on_source_delete(self, event: Any) -> None: cache_key = self._cache_key(media_path) + self._deferred_sources.discard(source_node) + sources = self._cache_key_to_sources.get(cache_key) if sources is not None: sources.discard(source_node) @@ -667,8 +669,6 @@ def _on_source_delete(self, event: Any) -> None: self._in_flight.discard(f"{cache_key}_thumbnail_path") self._in_flight.discard(f"{cache_key}_filmstrip_path") - self._deferred_sources.discard(source_node) - cached = self._cache.pop(cache_key, {}) for path in cached.values(): if path: From 2c8b9bd078757545fd739fe768cfaa0bb98fffde Mon Sep 17 00:00:00 2001 From: deltag0 Date: Fri, 5 Jun 2026 13:53:27 -0400 Subject: [PATCH 08/14] format Signed-off-by: deltag0 --- src/plugins/rv-packages/session_manager/local_thumbnail_gen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index fb80cdda2..79641f428 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -247,7 +247,7 @@ def _source_node_of_group(self, group: str) -> str | None: for node in commands.nodesInGroup(group): if commands.nodeType(node) in ("RVFileSource", "RVImageSource"): return node - except Exception as e: + except Exception: return return None From 354f1242176617e956092e804c6e7a96519a9c83 Mon Sep 17 00:00:00 2001 From: deltag0 Date: Mon, 8 Jun 2026 09:38:31 -0400 Subject: [PATCH 09/14] add direct node deletion check Signed-off-by: deltag0 --- .../rv-packages/session_manager/local_thumbnail_gen.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 79641f428..08c833e45 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -634,7 +634,10 @@ def _on_source_delete(self, event: Any) -> None: node = event.contents() - source_node = self._source_node_of_group(node) + if commands.nodeType(node) in ("RVFileSource", "RVImageSource"): + source_node = node + else: + source_node = self._source_node_of_group(node) if not source_node: return From 1fa362b75914ed7887d037a568cfd3ed8d6e88fa Mon Sep 17 00:00:00 2001 From: deltag0 Date: Tue, 9 Jun 2026 16:51:37 -0400 Subject: [PATCH 10/14] on session clear event Signed-off-by: deltag0 --- .../session_manager/local_thumbnail_gen.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 08c833e45..7d313206d 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -107,6 +107,11 @@ def global_bindings(self) -> list[tuple[str, Any, str]]: self._on_session_deletion, "Delete all cached local filmstrips and thumbnails on RV close", ), + ( + "before-clear-session", + self._on_clear_session, + "Cancel in-flight generation and evict cache when the session is cleared", + ), ( "before-source-delete", self._on_source_delete, @@ -607,6 +612,24 @@ def _on_previews_enabled(self, event: Any) -> None: _resume_proc(proc) self._drain_one() + def _on_clear_session(self, event: Any) -> None: + """Cancel in-flight generation and evict all caches when the session is cleared.""" + event.reject() + self._pool.shutdown(wait=False, cancel_futures=True) + self._pool = ThreadPoolExecutor(max_workers=MAX_WORKERS) + with self._procs_lock: + for proc, _ in self._active_procs: + _resume_proc(proc) + try: + proc.terminate() + except OSError: + logger.warning(f"Failed to terminate process {proc}") + self._in_flight.clear() + self._deferred_jobs.clear() + self._cache_key_to_sources.clear() + self._deferred_sources.clear() + self._cache.clear() + def _on_session_deletion(self, event: Any) -> None: event.reject() self._shutting_down = True From d04c1a85875549a20ad40dfa3427170c9699c2a1 Mon Sep 17 00:00:00 2001 From: deltag0 Date: Thu, 11 Jun 2026 12:33:14 -0400 Subject: [PATCH 11/14] optimize locking Signed-off-by: deltag0 --- .../session_manager/local_thumbnail_gen.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 7d313206d..c0b3b0276 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -618,12 +618,13 @@ def _on_clear_session(self, event: Any) -> None: self._pool.shutdown(wait=False, cancel_futures=True) self._pool = ThreadPoolExecutor(max_workers=MAX_WORKERS) with self._procs_lock: - for proc, _ in self._active_procs: - _resume_proc(proc) - try: - proc.terminate() - except OSError: - logger.warning(f"Failed to terminate process {proc}") + procs_to_terminate = list(self._active_procs) + for proc, _ in procs_to_terminate: + _resume_proc(proc) + try: + proc.terminate() + except OSError: + logger.warning(f"Failed to terminate process {proc}") self._in_flight.clear() self._deferred_jobs.clear() self._cache_key_to_sources.clear() From 714646129f291948c63ec0c774e1122c4dc9a3d7 Mon Sep 17 00:00:00 2001 From: deltag0 Date: Fri, 12 Jun 2026 12:57:33 -0400 Subject: [PATCH 12/14] stop thumbnail regeneration after clearing session Signed-off-by: deltag0 --- .../session_manager/local_thumbnail_gen.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index c0b3b0276..3c6fb8498 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -615,21 +615,23 @@ def _on_previews_enabled(self, event: Any) -> None: def _on_clear_session(self, event: Any) -> None: """Cancel in-flight generation and evict all caches when the session is cleared.""" event.reject() + self._shutting_down = True self._pool.shutdown(wait=False, cancel_futures=True) self._pool = ThreadPoolExecutor(max_workers=MAX_WORKERS) + self._in_flight.clear() + self._deferred_jobs.clear() + self._cache_key_to_sources.clear() + self._deferred_sources.clear() + self._cache.clear() with self._procs_lock: procs_to_terminate = list(self._active_procs) for proc, _ in procs_to_terminate: _resume_proc(proc) try: - proc.terminate() + proc.kill() + proc.wait() except OSError: - logger.warning(f"Failed to terminate process {proc}") - self._in_flight.clear() - self._deferred_jobs.clear() - self._cache_key_to_sources.clear() - self._deferred_sources.clear() - self._cache.clear() + logger.warning(f"Failed to kill process {proc}") def _on_session_deletion(self, event: Any) -> None: event.reject() From d0e9e01e7e2c305b7b695ac212fa191549145cbe Mon Sep 17 00:00:00 2001 From: deltag0 Date: Fri, 12 Jun 2026 16:19:49 -0400 Subject: [PATCH 13/14] merge with main Signed-off-by: deltag0 --- src/plugins/rv-packages/session_manager/local_thumbnail_gen.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 3c6fb8498..830eace4f 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -576,6 +576,7 @@ def _on_play_stop(self, event: Any) -> None: def _on_loading_start(self, event: Any) -> None: event.reject() + self._shutting_down = False with self._procs_lock: should_defer = self._should_defer() self._loading_active = True From a4fd08395d8f83ee483b5739b5664673541f143c Mon Sep 17 00:00:00 2001 From: deltag0 Date: Tue, 16 Jun 2026 16:17:13 -0400 Subject: [PATCH 14/14] fix robustness on closing rvio processes Signed-off-by: deltag0 --- .../session_manager/local_thumbnail_gen.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py index 830eace4f..e6e2f519d 100644 --- a/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py +++ b/src/plugins/rv-packages/session_manager/local_thumbnail_gen.py @@ -431,7 +431,7 @@ def _run_suspendable(self, cmd: list[str], cache_key: str, timeout: int = 120) - creationflags = subprocess.CREATE_NO_WINDOW if _IS_WIN32 else 0 proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=creationflags) with self._procs_lock: - self._active_procs.append(proc) + self._active_procs.append((proc, cache_key)) if self._should_defer(): _suspend_proc(proc) deadline = time.monotonic() + timeout @@ -570,7 +570,7 @@ def _on_play_stop(self, event: Any) -> None: with self._procs_lock: self._playback_active = False if not self._should_defer(): - for proc in self._active_procs: + for proc, _ in self._active_procs: _resume_proc(proc) self._drain_one() @@ -582,7 +582,7 @@ def _on_loading_start(self, event: Any) -> None: self._loading_active = True if should_defer: return - for proc in self._active_procs: + for proc, _ in self._active_procs: _suspend_proc(proc) def _on_loading_stop(self, event: Any) -> None: @@ -590,7 +590,7 @@ def _on_loading_stop(self, event: Any) -> None: with self._procs_lock: self._loading_active = False if not self._should_defer(): - for proc in self._active_procs: + for proc, _ in self._active_procs: _resume_proc(proc) self._drain_one() @@ -601,7 +601,7 @@ def _on_previews_disabled(self, event: Any) -> None: self._display_preview = False if should_defer: return - for proc in self._active_procs: + for proc, _ in self._active_procs: _suspend_proc(proc) def _on_previews_enabled(self, event: Any) -> None: @@ -609,7 +609,7 @@ def _on_previews_enabled(self, event: Any) -> None: with self._procs_lock: self._display_preview = True if not self._should_defer(): - for proc in self._active_procs: + for proc, _ in self._active_procs: _resume_proc(proc) self._drain_one() @@ -638,12 +638,14 @@ def _on_session_deletion(self, event: Any) -> None: event.reject() self._shutting_down = True with self._procs_lock: - for proc, _ in self._active_procs: - _resume_proc(proc) - try: - proc.terminate() - except OSError: - logger.warning(f"Failed to terminate process {proc}") + procs_to_kill = list(self._active_procs) + for proc, _ in procs_to_kill: + _resume_proc(proc) + try: + proc.kill() + proc.wait() + except OSError: + logger.warning(f"Failed to kill process {proc}") self._pool.shutdown(wait=False, cancel_futures=True) self._in_flight.clear() self._cache_key_to_sources.clear()