From d4e9d1d3259ff5bf8ae7cd748e252b529d39b9f8 Mon Sep 17 00:00:00 2001 From: pikastech Date: Mon, 20 Jul 2026 04:18:15 +0200 Subject: [PATCH] fix: keep provider TCP channels stable --- .../unideskcprovider/test_unideskcprovider.py | 37 ------------------- .../unideskcprovider/unideskcprovider.py | 21 +---------- src/components/unideskcprovider/update.json | 8 ++-- 3 files changed, 5 insertions(+), 61 deletions(-) diff --git a/src/components/unideskcprovider/test_unideskcprovider.py b/src/components/unideskcprovider/test_unideskcprovider.py index 313b8431..bc2fc4c7 100644 --- a/src/components/unideskcprovider/test_unideskcprovider.py +++ b/src/components/unideskcprovider/test_unideskcprovider.py @@ -303,43 +303,6 @@ class ProtocolHelpersTest(unittest.TestCase): stderr_thread.join.assert_called_once_with() manager.finish_session.assert_called_once_with("session", 0) - def test_recycles_only_the_oldest_expired_idle_channel(self): - manager = ProviderManager.__new__(ProviderManager) - manager.state_lock = provider.threading.RLock() - manager.logger = MagicMock() - expired = DataChannel(manager, "expired") - expired.status = "ready" - expired.idle_since = 100.0 - newer = DataChannel(manager, "newer") - newer.status = "ready" - newer.idle_since = 200.0 - claimed = DataChannel(manager, "claimed") - claimed.status = "claimed" - claimed.session_id = "session" - claimed.idle_since = 0.0 - manager.channels = {value.id: value for value in (newer, claimed, expired)} - for channel in manager.channels.values(): - channel.close = MagicMock() - - self.assertTrue(manager.recycle_idle_channel(now=1000.0)) - expired.close.assert_called_once_with("空闲通道主动轮换") - newer.close.assert_not_called() - claimed.close.assert_not_called() - - def test_keeps_recent_idle_channels(self): - manager = ProviderManager.__new__(ProviderManager) - manager.state_lock = provider.threading.RLock() - manager.logger = MagicMock() - channel = DataChannel(manager, "recent") - channel.status = "ready" - channel.idle_since = 500.0 - channel.close = MagicMock() - manager.channels = {channel.id: channel} - - self.assertFalse(manager.recycle_idle_channel(now=1000.0)) - channel.close.assert_not_called() - - class UpdateManagerTest(unittest.TestCase): @staticmethod def _source(version: str = "9.9.9") -> bytes: diff --git a/src/components/unideskcprovider/unideskcprovider.py b/src/components/unideskcprovider/unideskcprovider.py index 66dafcef..b8bd028a 100644 --- a/src/components/unideskcprovider/unideskcprovider.py +++ b/src/components/unideskcprovider/unideskcprovider.py @@ -56,7 +56,7 @@ else: APP_NAME = "UniDesk Windows Provider" DISPLAY_NAME = "UniDesk Windows Provider" if sys.platform == "win32" else "UniDesk Linux Provider" -APP_VERSION = "0.4.0" +APP_VERSION = "0.4.1" CONFIG_DIR = Path.home() / ".unidesk" CONFIG_PATH = CONFIG_DIR / "provider.yaml" LOG_DIR = CONFIG_DIR / "logs" @@ -69,7 +69,6 @@ POOL_SIZE = 32 MAX_HEADER_BYTES = 64 * 1024 MAX_PAYLOAD_BYTES = 16 * 1024 * 1024 HEARTBEAT_SECONDS = 15 -DATA_CHANNEL_MAX_IDLE_SECONDS = 10 * 60 TCP_KEEPALIVE_IDLE_SECONDS = 30 TCP_KEEPALIVE_INTERVAL_SECONDS = 10 TCP_KEEPALIVE_PROBES = 3 @@ -1262,7 +1261,6 @@ class ProviderManager: while not self.stop_event.is_set(): if time.time() - last_heartbeat >= HEARTBEAT_SECONDS: self._send_heartbeat() - self.recycle_idle_channel() last_heartbeat = time.time() try: opcode, payload = control.receive() @@ -1444,23 +1442,6 @@ class ProviderManager: channel.idle_since = time.monotonic() self.pool_changed() - def recycle_idle_channel(self, now: float | None = None) -> bool: - current = time.monotonic() if now is None else now - with self.state_lock: - candidates = [ - channel - for channel in self.channels.values() - if channel.status == "ready" - and channel.session_id is None - and current - channel.idle_since >= DATA_CHANNEL_MAX_IDLE_SECONDS - ] - channel = min(candidates, key=lambda value: value.idle_since) if candidates else None - if channel is None: - return False - self.logger.info("主动轮换空闲 TCP 通道: %s", channel.id) - channel.close("空闲通道主动轮换") - return True - def handle_data_frame(self, channel: DataChannel, header: dict, payload: bytes) -> None: session_id = str(header.get("sessionId") or "") with self.state_lock: diff --git a/src/components/unideskcprovider/update.json b/src/components/unideskcprovider/update.json index d93f72c8..5a51c266 100644 --- a/src/components/unideskcprovider/update.json +++ b/src/components/unideskcprovider/update.json @@ -1,9 +1,9 @@ { "schemaVersion": 1, "channel": "stable", - "latestVersion": "0.4.0", + "latestVersion": "0.4.1", "downloadUrl": "unideskcprovider.py", - "bytes": 91440, - "sha256": "75fade32b5beeab31b8614a130a2123094d3e59a37d0b0b33b19954b60697bb7", - "releaseNotes": "同一 Python Provider 新增 Linux headless、Bash、systemd 一键安装与跨平台升级支持。" + "bytes": 90608, + "sha256": "136c146036324092b88ab118850e86428d214fd0310dec4f6c9eb608a2809e81", + "releaseNotes": "Linux 原生 Provider 禁用无协调的空闲 TCP 通道主动轮换,避免高并发分配竞态。" }