From 1ad703ee9fd6827f1072e767fc0cd4dc49a72232 Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Mon, 1 Jun 2026 21:31:59 -0700 Subject: [PATCH 1/3] improved tunnel cleanup --- packages/prime-tunnel/src/prime_tunnel/tunnel.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/prime-tunnel/src/prime_tunnel/tunnel.py b/packages/prime-tunnel/src/prime_tunnel/tunnel.py index 1535800f2..d521eeced 100644 --- a/packages/prime-tunnel/src/prime_tunnel/tunnel.py +++ b/packages/prime-tunnel/src/prime_tunnel/tunnel.py @@ -1,4 +1,5 @@ import asyncio +import atexit import fcntl import os import re @@ -205,6 +206,8 @@ async def start(self) -> str: self._started = True + atexit.register(self.sync_stop) + return self.url async def stop(self) -> None: @@ -220,6 +223,8 @@ def sync_stop(self) -> None: if not self._started: return + atexit.unregister(self.sync_stop) + if self._process is not None: try: self._process.terminate() @@ -258,6 +263,8 @@ def sync_stop(self) -> None: async def _cleanup(self) -> None: """Clean up tunnel resources.""" + atexit.unregister(self.sync_stop) + # Stop frpc process (this will cause drain threads to exit via EOF) if self._process is not None: try: From 521c36dd5719e4ed3d054b5888fae6cc26204aca Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Mon, 1 Jun 2026 21:36:41 -0700 Subject: [PATCH 2/3] weakset --- .../prime-tunnel/src/prime_tunnel/tunnel.py | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/prime-tunnel/src/prime_tunnel/tunnel.py b/packages/prime-tunnel/src/prime_tunnel/tunnel.py index d521eeced..c1dd12707 100644 --- a/packages/prime-tunnel/src/prime_tunnel/tunnel.py +++ b/packages/prime-tunnel/src/prime_tunnel/tunnel.py @@ -6,6 +6,7 @@ import subprocess import threading import time +import weakref from pathlib import Path from typing import Optional @@ -30,6 +31,22 @@ ) _ANSI_RE = re.compile(r"\x1b\[[0-9;]*m") +# Started-but-not-stopped tunnels, so an atexit backstop can delete their backend +# registrations if the caller never stops them (forgotten cleanup, unhandled +# exception) +_active_tunnels: "weakref.WeakSet[Tunnel]" = weakref.WeakSet() + + +def _stop_active_tunnels() -> None: + for tunnel in list(_active_tunnels): + try: + tunnel.sync_stop() + except Exception: + pass + + +atexit.register(_stop_active_tunnels) + def _parse_frpc_error( output_lines: list[str], @@ -206,7 +223,7 @@ async def start(self) -> str: self._started = True - atexit.register(self.sync_stop) + _active_tunnels.add(self) return self.url @@ -223,7 +240,7 @@ def sync_stop(self) -> None: if not self._started: return - atexit.unregister(self.sync_stop) + _active_tunnels.discard(self) if self._process is not None: try: @@ -263,7 +280,7 @@ def sync_stop(self) -> None: async def _cleanup(self) -> None: """Clean up tunnel resources.""" - atexit.unregister(self.sync_stop) + _active_tunnels.discard(self) # Stop frpc process (this will cause drain threads to exit via EOF) if self._process is not None: From 7784d73bc1d8a2751c2c0f420af3a639f01e3a65 Mon Sep 17 00:00:00 2001 From: Cooper Miller Date: Mon, 1 Jun 2026 21:53:51 -0700 Subject: [PATCH 3/3] set --- packages/prime-tunnel/src/prime_tunnel/tunnel.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/prime-tunnel/src/prime_tunnel/tunnel.py b/packages/prime-tunnel/src/prime_tunnel/tunnel.py index c1dd12707..27823984a 100644 --- a/packages/prime-tunnel/src/prime_tunnel/tunnel.py +++ b/packages/prime-tunnel/src/prime_tunnel/tunnel.py @@ -6,7 +6,6 @@ import subprocess import threading import time -import weakref from pathlib import Path from typing import Optional @@ -34,7 +33,7 @@ # Started-but-not-stopped tunnels, so an atexit backstop can delete their backend # registrations if the caller never stops them (forgotten cleanup, unhandled # exception) -_active_tunnels: "weakref.WeakSet[Tunnel]" = weakref.WeakSet() +_active_tunnels: "set[Tunnel]" = set() def _stop_active_tunnels() -> None: