-
-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathAutoStartConflictResolver.tsx
More file actions
111 lines (101 loc) · 3.92 KB
/
Copy pathAutoStartConflictResolver.tsx
File metadata and controls
111 lines (101 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { useEffect } from 'react';
import {
takePendingPortConflict,
getGatewayStatus,
} from '@/lib/api/gateway';
import { useGatewayControl } from './useGatewayControl';
/**
* Polling schedule (ms after mount). Covers the realistic window for the
* Rust auto-start task to complete its port probe. Short early polls catch
* the common case; longer tails catch cold-start machines / slow disks.
*
* The tail must outlast the backend's port-conflict wait: on a self-update
* restart the auto-start task retries a busy port for up to ~6s (riding out
* the prior process's listener teardown) before it either starts the gateway
* or records a conflict. If we stopped polling first, a conflict raised at the
* end of that window would never reach the prompt. Total max wait: ~10s.
*/
const POLL_SCHEDULE_MS = [0, 200, 400, 800, 1500, 2400, 2400, 2400];
/**
* Mounts at the app root and resolves any auto-start port conflict the
* backend deferred during launch.
*
* ## Why polling, not events
*
* Tauri events aren't buffered — if the Rust auto-start task emits
* `gateway-autostart-port-conflict` before `listen()` has attached the
* frontend listener, the event is dropped. Combined with React
* StrictMode's double-mount in dev, the probability of this race is
* noticeable.
*
* Polling `take_pending_port_conflict` (atomic read-and-clear on the
* backend) plus `get_gateway_status` together covers all three
* launch-time outcomes:
*
* 1. **Silent success** — port free, gateway auto-started. `getGatewayStatus`
* returns `running: true` → we exit.
* 2. **Port conflict** — backend set `pending_port_conflict`. The take
* consumes it; we show the prompt.
* 3. **Auto-start disabled** — neither a conflict nor a running gateway.
* We exhaust the poll schedule and exit quietly; user can start
* manually from the Dashboard.
*
* The backend `take` is atomic so the StrictMode double-mount never
* produces duplicate prompts.
*/
export function AutoStartConflictResolver() {
const gatewayControl = useGatewayControl();
useEffect(() => {
let cancelled = false;
(async () => {
for (let i = 0; i < POLL_SCHEDULE_MS.length; i++) {
if (cancelled) return;
const delay = POLL_SCHEDULE_MS[i];
if (delay > 0) {
await new Promise((resolve) => setTimeout(resolve, delay));
}
if (cancelled) return;
try {
// If the gateway auto-started silently (port was free), we're
// done — no need to keep probing.
const status = await getGatewayStatus();
if (cancelled) return;
if (status.running) {
console.log(
`[AutoStart] attempt ${i + 1}: gateway already running (${status.url}) — nothing to resolve`
);
return;
}
const conflict = await takePendingPortConflict();
if (cancelled) return;
console.log(
`[AutoStart] attempt ${i + 1}: takePendingPortConflict →`,
conflict
);
if (conflict) {
const outcome = await gatewayControl.start();
console.log('[AutoStart] prompt outcome:', outcome);
return;
}
// Otherwise keep polling — backend auto-start task may not have
// run yet. Last iteration just bails (user can start manually).
} catch (err) {
console.error(
`[AutoStart] attempt ${i + 1} failed — will retry:`,
err
);
}
}
console.log(
'[AutoStart] poll schedule exhausted — no conflict, no running gateway (likely auto-start disabled)'
);
})();
return () => {
cancelled = true;
};
// `gatewayControl` is stable for the lifetime of this component; we
// deliberately run this once on mount.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <>{gatewayControl.ConfirmDialogElement}</>;
}