Merge branch fix/provider-session-dispatch
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / platform-infra-gitea-nc01- Failed
Pipelines as Code CI / unidesk-host- Success

This commit is contained in:
pikastech
2026-07-19 12:52:00 +02:00
3 changed files with 34 additions and 5 deletions
@@ -1,6 +1,7 @@
import hashlib
import json
import logging
import socket
import tempfile
import unittest
from pathlib import Path
@@ -16,6 +17,7 @@ from unideskcprovider import (
ProviderConfig,
UiBus,
UpdateManager,
configure_tcp_keepalive,
default_data_endpoint,
encode_data_frame,
parse_bridge_powershell_command,
@@ -137,6 +139,11 @@ class ProtocolHelpersTest(unittest.TestCase):
self.assertIn(b'"length":3', header)
self.assertEqual(frame[-3:], b"abc")
def test_data_channel_enables_tcp_keepalive(self):
value = MagicMock()
configure_tcp_keepalive(value)
value.setsockopt.assert_any_call(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
def test_recycles_only_the_oldest_expired_idle_channel(self):
manager = ProviderManager.__new__(ProviderManager)
manager.state_lock = provider.threading.RLock()
@@ -48,7 +48,7 @@ else:
APP_NAME = "UniDesk Windows Provider"
APP_VERSION = "0.3.1"
APP_VERSION = "0.3.2"
CONFIG_DIR = Path.home() / ".unidesk"
CONFIG_PATH = CONFIG_DIR / "provider.yaml"
LOG_DIR = CONFIG_DIR / "logs"
@@ -62,6 +62,9 @@ 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
DEFAULT_UPDATE_METADATA_URL = (
"https://apps.hwpod.com/v1/unideskcprovider/update.json"
)
@@ -121,6 +124,24 @@ def default_data_endpoint(server: urllib.parse.ParseResult) -> tuple[str, int]:
return server.hostname or "", port
def configure_tcp_keepalive(value: socket.socket) -> None:
value.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
if os.name == "nt" and hasattr(socket, "SIO_KEEPALIVE_VALS"):
value.ioctl(
socket.SIO_KEEPALIVE_VALS,
(1, TCP_KEEPALIVE_IDLE_SECONDS * 1000, TCP_KEEPALIVE_INTERVAL_SECONDS * 1000),
)
return
for option_name, option_value in (
("TCP_KEEPIDLE", TCP_KEEPALIVE_IDLE_SECONDS),
("TCP_KEEPINTVL", TCP_KEEPALIVE_INTERVAL_SECONDS),
("TCP_KEEPCNT", TCP_KEEPALIVE_PROBES),
):
option = getattr(socket, option_name, None)
if option is not None:
value.setsockopt(socket.IPPROTO_TCP, option, option_value)
def version_key(value: str) -> tuple[int, ...]:
if not isinstance(value, str) or not VERSION_PATTERN.fullmatch(value):
raise ValueError(f"invalid update version: {value!r}")
@@ -681,6 +702,7 @@ class DataChannel:
reason = "data channel closed"
try:
self.socket = socket.create_connection((self.manager.config.data_host, self.manager.config.data_port), timeout=5)
configure_tcp_keepalive(self.socket)
self.socket.settimeout(1)
self.write(
{
+4 -4
View File
@@ -1,9 +1,9 @@
{
"schemaVersion": 1,
"channel": "stable",
"latestVersion": "0.3.1",
"latestVersion": "0.3.2",
"downloadUrl": "unideskcprovider.py",
"bytes": 72278,
"sha256": "075a41d81d551b5ef2b75aa7958a7cf38da3c2865bc999e79cc440c1b3b4195d",
"releaseNotes": "主动轮换长期空闲 TCP 通道,修复托盘退出期间的日志控件异常,并保持 32 条 warm pool。"
"bytes": 73147,
"sha256": "5b98d5f18bed0c1b3efd736bc3f9464681d8610cee3ebca64f844abc402900bf",
"releaseNotes": "为 32 条 warm pool 启用 Windows TCP keepalive,避免旧空闲通道在并发传输时被网络设备重置。"
}