From e13a9c27f74ab2ea330518e107f17228a19774f6 Mon Sep 17 00:00:00 2001 From: NianJiuZst <3235467914@qq.com> Date: Fri, 10 Jul 2026 22:08:28 +0800 Subject: [PATCH 1/4] fix(runtime): clean up sessions on disconnect --- apps/extension/src/entrypoints/background.ts | 23 +++++- .../__tests__/connection-controller.test.ts | 30 ++++++++ .../src/lib/connection-controller.ts | 51 ++++++++++++- .../__tests__/disconnect-cleanup.test.ts | 58 ++++++++++++++ .../src/session-manager/disconnect-cleanup.ts | 75 +++++++++++++++++++ crates/bsk-cli/src/daemon/sessions.rs | 7 +- crates/bsk-cli/src/daemon/ws.rs | 11 +++ crates/bsk-cli/tests/sessions_ipc.rs | 30 ++++---- 8 files changed, 261 insertions(+), 24 deletions(-) create mode 100644 apps/extension/src/session-manager/__tests__/disconnect-cleanup.test.ts create mode 100644 apps/extension/src/session-manager/disconnect-cleanup.ts diff --git a/apps/extension/src/entrypoints/background.ts b/apps/extension/src/entrypoints/background.ts index 91c73e3..e092ce7 100644 --- a/apps/extension/src/entrypoints/background.ts +++ b/apps/extension/src/entrypoints/background.ts @@ -16,6 +16,7 @@ import { } from "@/lib/overlay-bridge"; import { POPUP_PORT_NAME, type PopupInbound, type PopupOutbound } from "@/lib/popup-bridge"; import { attachSessionsLiveFlag } from "@/lib/sessions-live-flag"; +import { createDisconnectCleanup } from "@/session-manager/disconnect-cleanup"; import { attachSessionEventHandler } from "@/session-manager/event-handler"; import { SessionManager } from "@/session-manager/manager"; import { @@ -41,6 +42,13 @@ export default defineBackground(() => { // stale `true` does not keep waking us on every page load until the // first mutation (review M4/M5 round 3 m-R3-1). void sessionsLive.refresh(); + const cleanupAfterDisconnect = createDisconnectCleanup({ + manager: sessions, + sessionStopDeps: { cdp }, + onSessionsChanged: () => { + void sessionsLive.syncFromManager(); + }, + }); const dispatcher = new ToolDispatcher({ transport, sessions, @@ -115,7 +123,20 @@ export default defineBackground(() => { void (async () => { const connectionEnabled = await getConnectionEnabled(); - await controller.attach(transport, detectBrowserMeta(), connectionEnabled); + await controller.attach(transport, detectBrowserMeta(), connectionEnabled, { + beforeDisconnect: async () => { + const report = await cleanupAfterDisconnect(); + if (report.failures.length > 0) { + console.warn("[browser-skill] session cleanup before disconnect was incomplete", report); + } + }, + onDisconnected: async () => { + const report = await cleanupAfterDisconnect(); + if (report.failures.length > 0) { + console.warn("[browser-skill] session cleanup after disconnect was incomplete", report); + } + }, + }); })().catch((err) => { console.error("[browser-skill] controller failed to attach", err); }); diff --git a/apps/extension/src/lib/__tests__/connection-controller.test.ts b/apps/extension/src/lib/__tests__/connection-controller.test.ts index b3eb9ae..408cd89 100644 --- a/apps/extension/src/lib/__tests__/connection-controller.test.ts +++ b/apps/extension/src/lib/__tests__/connection-controller.test.ts @@ -152,6 +152,36 @@ describe("ConnectionController connectionEnabled", () => { expect(controller.snapshot().lastError).toBeNull(); }); + it("runs safe session cleanup before an intentional disconnect", async () => { + const controller = new ConnectionController(); + const transport = makeMockTransport(); + const order: string[] = []; + transport.disconnect.mockImplementation(async () => { + order.push("disconnect"); + }); + await controller.attach(transport, { name: "Chrome", version: "120" }, true, { + beforeDisconnect: async () => { + order.push("cleanup"); + }, + }); + + await controller.setConnectionEnabled(false); + + expect(order).toEqual(["cleanup", "disconnect"]); + }); + + it("runs session cleanup when the transport disconnects unexpectedly", async () => { + const controller = new ConnectionController(); + const transport = makeMockTransport(); + const onDisconnected = vi.fn(async () => {}); + await controller.attach(transport, { name: "Chrome", version: "120" }, true, { + onDisconnected, + }); + + transport.emitState("disconnected"); + await vi.waitFor(() => expect(onDisconnected).toHaveBeenCalledTimes(1)); + }); + it("reconnects when connection is re-enabled", async () => { const controller = new ConnectionController(); const transport = makeMockTransport(); diff --git a/apps/extension/src/lib/connection-controller.ts b/apps/extension/src/lib/connection-controller.ts index bc0187b..9f325b3 100644 --- a/apps/extension/src/lib/connection-controller.ts +++ b/apps/extension/src/lib/connection-controller.ts @@ -21,6 +21,13 @@ export interface SnapshotInfo { type Listener = (s: SnapshotInfo) => void; +export interface ConnectionLifecycleHooks { + /** Safe local teardown that must finish before an intentional disconnect. */ + beforeDisconnect?: () => void | Promise; + /** Best-effort teardown after an unexpected transport loss. */ + onDisconnected?: () => void | Promise; +} + /** * Orchestrates Transport + handshake lifecycle for the background SW. * @@ -37,6 +44,8 @@ export class ConnectionController { private connectionEnabled = true; private listeners = new Set(); private handshakeInFlight = false; + private lifecycleHooks: ConnectionLifecycleHooks = {}; + private disconnectRecovery: Promise | null = null; get isConnectionEnabled(): boolean { return this.connectionEnabled; @@ -68,21 +77,28 @@ export class ConnectionController { transport: Transport, browser: { name: string; version: string }, connectionEnabled = true, + lifecycleHooks: ConnectionLifecycleHooks = {}, ): Promise { this.transport = transport; this.connectionEnabled = connectionEnabled; + this.lifecycleHooks = lifecycleHooks; this.instanceId = await getOrCreateInstanceId(); this.label = await getLabel(); transport.onConnectionStateChange((s) => { + if (s === "disconnected") { + this.handshake = null; + if (this.connectionEnabled) { + this.setState("disconnected"); + void this.recoverFromDisconnect(); + } + return; + } if (!this.connectionEnabled) return; if (s === "connected") { void this.runHandshake(browser); return; } - if (s === "disconnected") { - this.handshake = null; - } this.setState(s); }); @@ -161,6 +177,7 @@ export class ConnectionController { private async applyDisabledState(): Promise { this.handshake = null; this.lastError = null; + await this.lifecycleHooks.beforeDisconnect?.(); if (this.transport) { await this.transport.disconnect().catch(() => {}); } @@ -169,6 +186,34 @@ export class ConnectionController { if (was === "disconnected") this.fire(); } + private recoverFromDisconnect(): Promise { + if (this.disconnectRecovery) return this.disconnectRecovery; + const recovery = (async () => { + // WSTransport schedules its reconnect timer immediately after notifying + // state listeners. Yield once, then disconnect explicitly so that timer + // is cancelled before local session teardown begins. + await Promise.resolve(); + if (!this.connectionEnabled || !this.transport) return; + await this.transport.disconnect().catch(() => {}); + try { + await this.lifecycleHooks.onDisconnected?.(); + } catch (err) { + console.warn("[browser-skill] session cleanup after disconnect failed", err); + } + if (!this.connectionEnabled) return; + try { + await this.transport.connect(); + } catch (err) { + this.lastError = err instanceof Error ? err.message : String(err); + this.setState("disconnected"); + } + })(); + this.disconnectRecovery = recovery.finally(() => { + this.disconnectRecovery = null; + }); + return this.disconnectRecovery; + } + private setState(next: ConnectionState): void { if (this.currentState === next) return; this.currentState = next; diff --git a/apps/extension/src/session-manager/__tests__/disconnect-cleanup.test.ts b/apps/extension/src/session-manager/__tests__/disconnect-cleanup.test.ts new file mode 100644 index 0000000..3ca893a --- /dev/null +++ b/apps/extension/src/session-manager/__tests__/disconnect-cleanup.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it, vi } from "vitest"; +import { createDisconnectCleanup } from "../disconnect-cleanup"; +import { SessionManager } from "../manager"; + +describe("disconnect session cleanup", () => { + it("stops every local session through the safe session-stop path", async () => { + const remove = vi.fn(async () => {}); + let nextWindowId = 100; + const manager = new SessionManager({ + agentWindow: { + create: vi.fn(async () => nextWindowId++), + ensureActiveTab: vi.fn(async () => {}), + remove, + }, + }); + await manager.start("aa11"); + await manager.start("bb22"); + const detachSession = vi.fn(async () => {}); + const onSessionsChanged = vi.fn(); + const cleanup = createDisconnectCleanup({ + manager, + sessionStopDeps: { cdp: { detachSession } }, + onSessionsChanged, + }); + + const report = await cleanup(); + + expect(report).toEqual({ stoppedSessionIds: ["aa11", "bb22"], failures: [] }); + expect(manager.list()).toEqual([]); + expect(remove).toHaveBeenCalledTimes(2); + expect(detachSession).toHaveBeenCalledWith("aa11"); + expect(detachSession).toHaveBeenCalledWith("bb22"); + expect(onSessionsChanged).toHaveBeenCalledTimes(1); + }); + + it("coalesces overlapping disconnect notifications", async () => { + let releaseRemove: () => void = () => {}; + const removeGate = new Promise((resolve) => { + releaseRemove = resolve; + }); + const manager = new SessionManager({ + agentWindow: { + create: vi.fn(async () => 100), + ensureActiveTab: vi.fn(async () => {}), + remove: vi.fn(() => removeGate), + }, + }); + await manager.start("aa11"); + const cleanup = createDisconnectCleanup({ manager }); + + const first = cleanup(); + const second = cleanup(); + expect(second).toBe(first); + releaseRemove(); + + await expect(first).resolves.toEqual({ stoppedSessionIds: ["aa11"], failures: [] }); + }); +}); diff --git a/apps/extension/src/session-manager/disconnect-cleanup.ts b/apps/extension/src/session-manager/disconnect-cleanup.ts new file mode 100644 index 0000000..59daafb --- /dev/null +++ b/apps/extension/src/session-manager/disconnect-cleanup.ts @@ -0,0 +1,75 @@ +import { handleSessionStop, type SessionStopDeps } from "@/tools/session"; +import type { SessionManager } from "./manager"; + +export interface DisconnectCleanupFailure { + sessionId: string; + message: string; +} + +export interface DisconnectCleanupReport { + stoppedSessionIds: string[]; + failures: DisconnectCleanupFailure[]; +} + +export interface DisconnectCleanupOptions { + manager: SessionManager; + sessionStopDeps?: SessionStopDeps; + onSessionsChanged?: () => void; +} + +/** + * Build a coalesced cleanup operation for transport loss. + * + * The daemon purges its registry when the extension socket disappears. To + * keep the extension-side mirror consistent, every local session must follow + * the normal safe stop path as well: return borrowed tabs, clear refs, detach + * CDP, and only then close the Agent Window. + */ +export function createDisconnectCleanup(options: DisconnectCleanupOptions) { + let inFlight: Promise | null = null; + + return (): Promise => { + if (inFlight) return inFlight; + inFlight = cleanupSessions(options).finally(() => { + inFlight = null; + }); + return inFlight; + }; +} + +async function cleanupSessions( + options: DisconnectCleanupOptions, +): Promise { + const stoppedSessionIds: string[] = []; + const failures: DisconnectCleanupFailure[] = []; + + for (const ctx of options.manager.list()) { + try { + const result = await handleSessionStop( + options.manager, + { session_id: ctx.sessionId }, + options.sessionStopDeps, + ); + if ("code" in result) { + failures.push({ sessionId: ctx.sessionId, message: result.message }); + continue; + } + if (result.return_failures?.length) { + failures.push({ + sessionId: ctx.sessionId, + message: result.return_failures.map((failure) => failure.message).join("; "), + }); + continue; + } + stoppedSessionIds.push(ctx.sessionId); + } catch (err) { + failures.push({ + sessionId: ctx.sessionId, + message: err instanceof Error ? err.message : String(err), + }); + } + } + + options.onSessionsChanged?.(); + return { stoppedSessionIds, failures }; +} diff --git a/crates/bsk-cli/src/daemon/sessions.rs b/crates/bsk-cli/src/daemon/sessions.rs index aff784f..eb67005 100644 --- a/crates/bsk-cli/src/daemon/sessions.rs +++ b/crates/bsk-cli/src/daemon/sessions.rs @@ -472,10 +472,9 @@ pub async fn stop_session( Ok(result) } Err(DispatchError::Rpc(err)) => { - // After an extension SW restart the daemon still owns the - // session entry (the generation-guard ensures we do not - // purge across reconnects), but the extension's in-memory - // SessionManager is reset and now answers `not_found`. + // A legacy extension or an unusual reconnect race may still + // leave a daemon session after the extension's in-memory + // SessionManager has reset and now answers `not_found`. // Treat that as an authoritative signal that the session is // already gone and reconcile our local state instead of // leaving an orphan row visible to `bsk session list` diff --git a/crates/bsk-cli/src/daemon/ws.rs b/crates/bsk-cli/src/daemon/ws.rs index 4073771..ee150d6 100644 --- a/crates/bsk-cli/src/daemon/ws.rs +++ b/crates/bsk-cli/src/daemon/ws.rs @@ -286,6 +286,17 @@ async fn drive_connection( // socket's cleanup path uses `remove_if_generation_matches` to // avoid clobbering the newer entry (review M4/M5 round 2 #1). let browser_id = BrowserId(params.instance_id.clone()); + // A fresh socket represents a fresh extension control plane. The + // extension tears down its local sessions before reconnecting, so any + // daemon-side sessions left under the same instance id are stale. Purge + // them before replacing the browser registration; otherwise an old WS + // cleanup racing with this handshake can preserve rows that no longer + // have an Agent Window on the extension side. + for session in state.sessions.purge_browser(&browser_id) { + state.tool_queues.remove(&session.id); + state.session_interrupts.drop_session(&session.id); + debug!(session = %session.id, "purged stale session before browser reconnect"); + } let (tx, mut rx) = mpsc::unbounded_channel::(); let generation = super::browsers::next_browser_generation(); let connected_at_ms = std::time::SystemTime::now() diff --git a/crates/bsk-cli/tests/sessions_ipc.rs b/crates/bsk-cli/tests/sessions_ipc.rs index a686520..e1561bb 100644 --- a/crates/bsk-cli/tests/sessions_ipc.rs +++ b/crates/bsk-cli/tests/sessions_ipc.rs @@ -20,7 +20,7 @@ use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; use tokio_tungstenite::tungstenite::protocol::Message; -use support::{wait_for_browser_count, wait_for_no_sessions, wait_for_session_count}; +use support::{wait_for_browser_count, wait_for_no_sessions}; const TEST_EXT_ID: &str = "abcdefghijklmnopabcdefghijklmnop"; @@ -680,11 +680,10 @@ async fn browser_disconnect_purges_sessions() { #[tokio::test] async fn session_stop_self_heals_when_extension_reports_not_found() { - // After an extension SW restart the daemon still owns the session - // entry (Round 2 generation-guard) but the extension's - // SessionManager is reset and answers `not_found`. Stop must - // reconcile local state so `session.list` does not show an orphan - // (review M4/M5 round 3 I-R3-2). + // Legacy extension versions or unusual reconnect races can leave a + // daemon session after the extension's SessionManager resets and answers + // `not_found`. Stop must still reconcile local state so `session.list` + // does not show an orphan (review M4/M5 round 3 I-R3-2). let (handle, sock) = spawn_daemon().await; let mut ws = connect_ext(handle.ws_addr()).await; @@ -778,14 +777,13 @@ async fn session_stop_self_heals_when_extension_reports_not_found() { } #[tokio::test] -async fn reconnect_with_same_instance_id_does_not_clobber_new_browser() { +async fn reconnect_with_same_instance_id_purges_stale_sessions_but_keeps_new_browser() { // Spawn a daemon, connect ext A, register a session bound to it, // then connect ext B reusing the same instance_id (mimics a SW - // restart / WS reconnect under MV3). When the OLD WS task tears - // down, the generation guard MUST keep the new entry + its - // session in the registry. Without the guard the stale cleanup - // path would call browsers.remove() + sessions.purge_browser() - // and the daemon would silently drop the live session. + // restart / WS reconnect under MV3). The extension now safely stops + // its local sessions before reconnecting, so the new handshake must + // purge the matching daemon rows. The generation guard must still keep + // the NEW browser registration when the old WS task later tears down. let (handle, _sock) = spawn_daemon().await; let mut ws_a = connect_ext(handle.ws_addr()).await; @@ -806,14 +804,14 @@ async fn reconnect_with_same_instance_id_does_not_clobber_new_browser() { let _ = handshake_as_ext(&mut ws_b).await; // Registry now holds the newer generation under the same id. assert_eq!(state.browsers.len(), 1); + wait_for_no_sessions(&state).await; // Tear down ext A only; the cleanup path will run but must // observe that the registered generation no longer matches and - // leave the new entry + session alone. + // leave the new browser entry alone. let _ = ws_a.close(None).await; drop(ws_a); wait_for_browser_count(&state, 1).await; - wait_for_session_count(&state, 1).await; assert_eq!( state.browsers.len(), @@ -822,8 +820,8 @@ async fn reconnect_with_same_instance_id_does_not_clobber_new_browser() { ); assert_eq!( state.sessions.len(), - 1, - "session must survive old browser cleanup" + 0, + "stale sessions must not survive reconnect" ); let _ = ws_b.close(None).await; From 29c063cd151da34879059c1dd763cb18a3fa1941 Mon Sep 17 00:00:00 2001 From: NianJiuZst <3235467914@qq.com> Date: Fri, 10 Jul 2026 22:40:16 +0800 Subject: [PATCH 2/4] chore(ci): match Biome 2.4 formatting --- apps/extension/vitest.config.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/extension/vitest.config.ts b/apps/extension/vitest.config.ts index 2e1c157..00060bf 100644 --- a/apps/extension/vitest.config.ts +++ b/apps/extension/vitest.config.ts @@ -21,9 +21,7 @@ export default defineConfig({ }, define: { __BSK_EXT_VERSION__: JSON.stringify(pkg.version), - __BSK_DAEMON_WS_URL__: JSON.stringify( - process.env.BSK_DAEMON_WS_URL ?? "ws://127.0.0.1:52800", - ), + __BSK_DAEMON_WS_URL__: JSON.stringify(process.env.BSK_DAEMON_WS_URL ?? "ws://127.0.0.1:52800"), }, resolve: { alias: { From a217d5a32989a5fb485e8432deee250a2bbc2a76 Mon Sep 17 00:00:00 2001 From: NianJiuZst <3235467914@qq.com> Date: Fri, 10 Jul 2026 22:50:06 +0800 Subject: [PATCH 3/4] ci: retry stalled Rust job From 8b542e1f9bebdbd46af17a74b3a65d62d66844a3 Mon Sep 17 00:00:00 2001 From: NianJiuZst <3235467914@qq.com> Date: Fri, 10 Jul 2026 23:06:02 +0800 Subject: [PATCH 4/4] test: bind daemon ports without probe races --- crates/bsk-cli/tests/browser_list_ordering.rs | 5 +---- crates/bsk-cli/tests/browser_wait.rs | 5 +---- crates/bsk-cli/tests/cancel_forwarding.rs | 5 +---- crates/bsk-cli/tests/handshake_compat.rs | 5 +---- crates/bsk-cli/tests/per_session_queue.rs | 5 +---- crates/bsk-cli/tests/session_user_interrupt.rs | 5 +---- crates/bsk-cli/tests/sessions_ipc.rs | 5 +---- crates/bsk-cli/tests/tools_ipc.rs | 5 +---- crates/bsk-cli/tests/tools_m7_ipc.rs | 5 +---- crates/bsk-cli/tests/tools_m8_ipc.rs | 5 +---- crates/bsk-cli/tests/tools_m9_ipc.rs | 5 +---- crates/bsk-cli/tests/ws_handshake.rs | 5 +---- 12 files changed, 12 insertions(+), 48 deletions(-) diff --git a/crates/bsk-cli/tests/browser_list_ordering.rs b/crates/bsk-cli/tests/browser_list_ordering.rs index 1c5199b..f19f320 100644 --- a/crates/bsk-cli/tests/browser_list_ordering.rs +++ b/crates/bsk-cli/tests/browser_list_ordering.rs @@ -16,7 +16,6 @@ use bsk_protocol::system::BrowserStatusEntry; use bsk_protocol::{ErrorCode, Method}; use rand::Rng; use serde::Deserialize; -use tokio::net::TcpListener; use tokio::sync::mpsc; fn tempfile_path(prefix: &str) -> PathBuf { @@ -30,9 +29,7 @@ fn tempfile_path(prefix: &str) -> PathBuf { } async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let config = DaemonConfig::new(port); let sock = tempfile_path("bsk-test-browser-list"); diff --git a/crates/bsk-cli/tests/browser_wait.rs b/crates/bsk-cli/tests/browser_wait.rs index 632a7e8..16513e0 100644 --- a/crates/bsk-cli/tests/browser_wait.rs +++ b/crates/bsk-cli/tests/browser_wait.rs @@ -15,7 +15,6 @@ use bsk_protocol::system::{BrowserListParams, HandshakeParams, HandshakeResult, use bsk_protocol::{BrowserPeerInfo, Method, RequestFrame, ResponseBody, ResponseFrame}; use futures_util::{SinkExt, StreamExt}; use rand::Rng; -use tokio::net::TcpListener; use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; use tokio_tungstenite::tungstenite::protocol::Message; @@ -77,9 +76,7 @@ fn tempfile_path(prefix: &str) -> PathBuf { } async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let config = DaemonConfig::new(port); let sock = tempfile_path("bsk-test-browser-wait"); diff --git a/crates/bsk-cli/tests/cancel_forwarding.rs b/crates/bsk-cli/tests/cancel_forwarding.rs index b9913ca..e8a0aaf 100644 --- a/crates/bsk-cli/tests/cancel_forwarding.rs +++ b/crates/bsk-cli/tests/cancel_forwarding.rs @@ -20,7 +20,6 @@ use bsk_protocol::{ use futures_util::{SinkExt, StreamExt}; use rand::Rng; use serde_json::json; -use tokio::net::TcpListener; use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; use tokio_tungstenite::tungstenite::protocol::Message; @@ -40,9 +39,7 @@ fn tempfile_path(prefix: &str) -> PathBuf { } async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let config = DaemonConfig::new(port); let sock = tempfile_path("bsk-test-cancel"); diff --git a/crates/bsk-cli/tests/handshake_compat.rs b/crates/bsk-cli/tests/handshake_compat.rs index 07e14e4..596d35e 100644 --- a/crates/bsk-cli/tests/handshake_compat.rs +++ b/crates/bsk-cli/tests/handshake_compat.rs @@ -10,7 +10,6 @@ use bsk_protocol::system::{HandshakeParams, HandshakeResult, StatusResult}; use bsk_protocol::{BrowserPeerInfo, ErrorCode, Method, RequestFrame, ResponseBody, ResponseFrame}; use futures_util::{SinkExt, StreamExt}; use rand::Rng; -use tokio::net::TcpListener; use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; use tokio_tungstenite::tungstenite::protocol::Message; @@ -28,9 +27,7 @@ fn tempfile_path(prefix: &str) -> PathBuf { } async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let config = DaemonConfig::new(port); let sock = tempfile_path("bsk-test-handshake"); diff --git a/crates/bsk-cli/tests/per_session_queue.rs b/crates/bsk-cli/tests/per_session_queue.rs index f548ee6..79b5355 100644 --- a/crates/bsk-cli/tests/per_session_queue.rs +++ b/crates/bsk-cli/tests/per_session_queue.rs @@ -27,7 +27,6 @@ use futures_util::{SinkExt, StreamExt}; use rand::Rng; use serde::Deserialize; use serde_json::json; -use tokio::net::TcpListener; use tokio::sync::{Mutex, mpsc}; use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; @@ -46,9 +45,7 @@ fn tempfile_path(prefix: &str) -> PathBuf { } async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let sock = tempfile_path("bsk-test-queue"); let handle = daemon::run(DaemonConfig::new(port), Some(sock.clone())) .await diff --git a/crates/bsk-cli/tests/session_user_interrupt.rs b/crates/bsk-cli/tests/session_user_interrupt.rs index 2960814..b7d6e61 100644 --- a/crates/bsk-cli/tests/session_user_interrupt.rs +++ b/crates/bsk-cli/tests/session_user_interrupt.rs @@ -26,7 +26,6 @@ use bsk_protocol::{ use futures_util::{SinkExt, StreamExt}; use rand::Rng; use serde_json::json; -use tokio::net::TcpListener; use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; use tokio_tungstenite::tungstenite::protocol::Message; @@ -46,9 +45,7 @@ fn tempfile_path(prefix: &str) -> PathBuf { } async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let config = DaemonConfig::new(port); let sock = tempfile_path("bsk-test-user-interrupt"); diff --git a/crates/bsk-cli/tests/sessions_ipc.rs b/crates/bsk-cli/tests/sessions_ipc.rs index e1561bb..9d5201a 100644 --- a/crates/bsk-cli/tests/sessions_ipc.rs +++ b/crates/bsk-cli/tests/sessions_ipc.rs @@ -15,7 +15,6 @@ use bsk_protocol::tools::{SessionStartParams, SessionStartResult, SessionStopPar use bsk_protocol::{BrowserPeerInfo, Frame, Method, RequestFrame, ResponseBody, ResponseFrame}; use futures_util::{SinkExt, StreamExt}; use rand::Rng; -use tokio::net::TcpListener; use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; use tokio_tungstenite::tungstenite::protocol::Message; @@ -39,9 +38,7 @@ async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { } async fn spawn_daemon_with_connect_wait(connect_wait: Duration) -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let config = DaemonConfig::new(port).with_extension_connect_wait(connect_wait); let sock = tempfile_path("bsk-test-ipc"); diff --git a/crates/bsk-cli/tests/tools_ipc.rs b/crates/bsk-cli/tests/tools_ipc.rs index 32b7732..2857c62 100644 --- a/crates/bsk-cli/tests/tools_ipc.rs +++ b/crates/bsk-cli/tests/tools_ipc.rs @@ -21,7 +21,6 @@ use futures_util::stream::{SplitSink, SplitStream}; use futures_util::{SinkExt, StreamExt}; use rand::Rng; use serde_json::{Value, json}; -use tokio::net::TcpListener; use tokio::sync::Mutex; use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; @@ -40,9 +39,7 @@ fn tempfile_path(prefix: &str) -> PathBuf { } async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let sock = tempfile_path("bsk-test-tools"); let handle = daemon::run(DaemonConfig::new(port), Some(sock.clone())) .await diff --git a/crates/bsk-cli/tests/tools_m7_ipc.rs b/crates/bsk-cli/tests/tools_m7_ipc.rs index acb9481..ee74b5e 100644 --- a/crates/bsk-cli/tests/tools_m7_ipc.rs +++ b/crates/bsk-cli/tests/tools_m7_ipc.rs @@ -25,7 +25,6 @@ use futures_util::stream::{SplitSink, SplitStream}; use futures_util::{SinkExt, StreamExt}; use rand::Rng; use serde_json::{Value, json}; -use tokio::net::TcpListener; use tokio::sync::Mutex; use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; @@ -44,9 +43,7 @@ fn tempfile_path(prefix: &str) -> PathBuf { } async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let sock = tempfile_path("bsk-test-tools-m7"); let handle = daemon::run(DaemonConfig::new(port), Some(sock.clone())) .await diff --git a/crates/bsk-cli/tests/tools_m8_ipc.rs b/crates/bsk-cli/tests/tools_m8_ipc.rs index 5308f7b..14e7c72 100644 --- a/crates/bsk-cli/tests/tools_m8_ipc.rs +++ b/crates/bsk-cli/tests/tools_m8_ipc.rs @@ -24,7 +24,6 @@ use futures_util::stream::{SplitSink, SplitStream}; use futures_util::{SinkExt, StreamExt}; use rand::Rng; use serde_json::Value; -use tokio::net::TcpListener; use tokio::sync::Mutex; use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; @@ -43,9 +42,7 @@ fn tempfile_path(prefix: &str) -> PathBuf { } async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let sock = tempfile_path("bsk-test-tools-m8"); let handle = daemon::run(DaemonConfig::new(port), Some(sock.clone())) .await diff --git a/crates/bsk-cli/tests/tools_m9_ipc.rs b/crates/bsk-cli/tests/tools_m9_ipc.rs index 6dd5d33..2f8b46d 100644 --- a/crates/bsk-cli/tests/tools_m9_ipc.rs +++ b/crates/bsk-cli/tests/tools_m9_ipc.rs @@ -30,7 +30,6 @@ use rand::Rng; use serde_json::{Value, json}; #[cfg(unix)] use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; -use tokio::net::TcpListener; #[cfg(unix)] use tokio::net::UnixStream; use tokio::sync::Mutex; @@ -53,9 +52,7 @@ fn tempfile_path(prefix: &str) -> PathBuf { } async fn spawn_daemon() -> (daemon::DaemonHandle, PathBuf) { - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let sock = tempfile_path("bsk-test-tools-m9"); let handle = daemon::run(DaemonConfig::new(port), Some(sock.clone())) .await diff --git a/crates/bsk-cli/tests/ws_handshake.rs b/crates/bsk-cli/tests/ws_handshake.rs index 146ef33..c6e2da1 100644 --- a/crates/bsk-cli/tests/ws_handshake.rs +++ b/crates/bsk-cli/tests/ws_handshake.rs @@ -8,7 +8,6 @@ use bsk::daemon::{self, DaemonConfig}; use bsk_protocol::system::{HandshakeParams, HandshakeResult}; use bsk_protocol::{BrowserPeerInfo, Method, RequestFrame, ResponseBody, ResponseFrame}; use futures_util::{SinkExt, StreamExt}; -use tokio::net::TcpListener; use tokio_tungstenite::tungstenite::handshake::client::generate_key; use tokio_tungstenite::tungstenite::http::Request; use tokio_tungstenite::tungstenite::protocol::Message; @@ -17,9 +16,7 @@ pub const TEST_EXT_ID: &str = "abcdefghijklmnopabcdefghijklmnop"; // 32 chars in pub async fn spawn_daemon() -> daemon::DaemonHandle { // Bind to any free TCP port. - let probe = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let port = probe.local_addr().unwrap().port(); - drop(probe); + let port = 0; let config = DaemonConfig::new(port); daemon::run(config, None).await.unwrap()