refactor: rename sockPath to listen in pid lock, add startup timing instrumentation
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useSyncExternalStore, useState } from 'react'
|
||||
import { useEffect, useRef, useSyncExternalStore, useState } from 'react'
|
||||
import { usePathname, useRouter } from 'expo-router'
|
||||
import { useDaemonRegistry } from '@/contexts/daemon-registry-context'
|
||||
import { shouldUseManagedDesktopDaemon } from '@/desktop/managed-runtime/managed-runtime'
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from './index-startup'
|
||||
|
||||
const STARTUP_TIMEOUT_MS = 30_000
|
||||
const APP_START_TIME = performance.now()
|
||||
|
||||
function useAnyHostOnline(serverIds: string[]): string | null {
|
||||
const runtime = getHostRuntimeStore()
|
||||
@@ -60,6 +61,17 @@ export default function Index() {
|
||||
const [hasTimedOut, setHasTimedOut] = useState(false)
|
||||
const isDesktopStartupRace = shouldUseManagedDesktopDaemon()
|
||||
const onlineServerId = useAnyHostOnline(daemons.map((daemon) => daemon.serverId))
|
||||
const startupLogged = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (onlineServerId && !startupLogged.current) {
|
||||
startupLogged.current = true
|
||||
console.info('[Startup] host online', {
|
||||
serverId: onlineServerId,
|
||||
elapsedMs: Math.round(performance.now() - APP_START_TIME),
|
||||
})
|
||||
}
|
||||
}, [onlineServerId])
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -80,6 +92,10 @@ export default function Index() {
|
||||
if (pathname !== '/' && pathname !== '') {
|
||||
return
|
||||
}
|
||||
console.info('[Startup] navigating to host route', {
|
||||
serverId: onlineServerId,
|
||||
elapsedMs: Math.round(performance.now() - APP_START_TIME),
|
||||
})
|
||||
router.replace(buildHostRootRoute(onlineServerId) as any)
|
||||
}, [onlineServerId, pathname, registryLoading, router])
|
||||
|
||||
|
||||
@@ -483,15 +483,20 @@ export function DaemonRegistryProvider({ children }: { children: ReactNode }) {
|
||||
let cancelled = false
|
||||
|
||||
const bootstrapDesktopStartup = async () => {
|
||||
const t0 = performance.now()
|
||||
const elapsed = () => Math.round(performance.now() - t0)
|
||||
try {
|
||||
const isE2E = await AsyncStorage.getItem(E2E_STORAGE_KEY)
|
||||
if (cancelled || isE2E) {
|
||||
return
|
||||
}
|
||||
|
||||
console.info('[DaemonRegistry] desktop bootstrap: starting', { elapsedMs: elapsed() })
|
||||
await Promise.allSettled([
|
||||
(async () => {
|
||||
console.info('[DaemonRegistry] desktop bootstrap: startManagedDaemon', { elapsedMs: elapsed() })
|
||||
const daemon = await startManagedDaemon()
|
||||
console.info('[DaemonRegistry] desktop bootstrap: managed daemon ready', { elapsedMs: elapsed(), serverId: daemon.serverId })
|
||||
if (cancelled || !daemon.serverId) {
|
||||
return
|
||||
}
|
||||
@@ -506,15 +511,18 @@ export function DaemonRegistryProvider({ children }: { children: ReactNode }) {
|
||||
label: daemon.hostname ?? undefined,
|
||||
connection,
|
||||
})
|
||||
console.info('[DaemonRegistry] desktop bootstrap: host connection upserted', { elapsedMs: elapsed() })
|
||||
})().catch((managedBootstrapError) => {
|
||||
if (!cancelled) {
|
||||
console.warn(
|
||||
'[DaemonRegistry] Failed to bootstrap desktop daemon connection',
|
||||
{ elapsedMs: elapsed() },
|
||||
managedBootstrapError
|
||||
)
|
||||
}
|
||||
}),
|
||||
(async () => {
|
||||
console.info('[DaemonRegistry] desktop bootstrap: probing localhost', { elapsedMs: elapsed() })
|
||||
const { serverId, hostname } = await probeConnection(
|
||||
{
|
||||
id: `bootstrap:${DEFAULT_LOCALHOST_ENDPOINT}`,
|
||||
@@ -523,6 +531,7 @@ export function DaemonRegistryProvider({ children }: { children: ReactNode }) {
|
||||
},
|
||||
{ timeoutMs: DEFAULT_LOCALHOST_BOOTSTRAP_TIMEOUT_MS }
|
||||
)
|
||||
console.info('[DaemonRegistry] desktop bootstrap: localhost probe succeeded', { elapsedMs: elapsed(), serverId })
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
@@ -532,8 +541,9 @@ export function DaemonRegistryProvider({ children }: { children: ReactNode }) {
|
||||
endpoint: DEFAULT_LOCALHOST_ENDPOINT,
|
||||
label: hostname ?? undefined,
|
||||
})
|
||||
console.info('[DaemonRegistry] desktop bootstrap: direct connection upserted', { elapsedMs: elapsed() })
|
||||
})().catch(() => {
|
||||
// Best-effort startup race only; ignore if localhost isn't reachable.
|
||||
console.info('[DaemonRegistry] desktop bootstrap: localhost probe failed', { elapsedMs: elapsed() })
|
||||
}),
|
||||
])
|
||||
} catch (bootstrapError) {
|
||||
|
||||
@@ -143,7 +143,9 @@ export type HostRuntimeStartOptions = {
|
||||
autoProbe?: boolean;
|
||||
};
|
||||
|
||||
const PROBE_INTERVAL_MS = 10_000;
|
||||
const PROBE_TICK_MS = 2_000;
|
||||
const PROBE_STEADY_MS = 10_000;
|
||||
const PROBE_MAX_BACKOFF_MS = 30_000;
|
||||
const ADAPTIVE_SWITCH_THRESHOLD_MS = 40;
|
||||
const ADAPTIVE_SWITCH_CONSECUTIVE_PROBES = 3;
|
||||
const DEFAULT_AGENT_DIRECTORY_PAGE_LIMIT = 200;
|
||||
@@ -419,19 +421,19 @@ function findConnectionById(
|
||||
return host.connections.find((connection) => connection.id === connectionId) ?? null;
|
||||
}
|
||||
|
||||
function selectInitialConnectionId(host: HostProfile): string | null {
|
||||
if (host.connections.length === 0) {
|
||||
return null;
|
||||
function probeIntervalForConnection(
|
||||
firstSeenAt: number,
|
||||
isActiveOnline: boolean,
|
||||
now: number
|
||||
): number {
|
||||
if (isActiveOnline) {
|
||||
return PROBE_STEADY_MS;
|
||||
}
|
||||
if (host.preferredConnectionId) {
|
||||
const preferred = host.connections.find(
|
||||
(connection) => connection.id === host.preferredConnectionId
|
||||
);
|
||||
if (preferred) {
|
||||
return preferred.id;
|
||||
}
|
||||
}
|
||||
return host.connections[0]?.id ?? null;
|
||||
const age = now - firstSeenAt;
|
||||
if (age < 10_000) return 2_000;
|
||||
if (age < 30_000) return 5_000;
|
||||
if (age < 60_000) return PROBE_STEADY_MS;
|
||||
return PROBE_MAX_BACKOFF_MS;
|
||||
}
|
||||
|
||||
function createDefaultDeps(): HostRuntimeControllerDeps {
|
||||
@@ -507,6 +509,8 @@ export class HostRuntimeController {
|
||||
private unsubscribeClientStatus: (() => void) | null = null;
|
||||
private probeIntervalHandle: ReturnType<typeof setInterval> | null = null;
|
||||
private started = false;
|
||||
private connectionFirstSeenAt = new Map<string, number>();
|
||||
private connectionLastProbedAt = new Map<string, number>();
|
||||
private switchCandidateConnectionId: string | null = null;
|
||||
private switchCandidateHitCount = 0;
|
||||
private clientIdPromise: Promise<string> | null = null;
|
||||
@@ -555,19 +559,12 @@ export class HostRuntimeController {
|
||||
return;
|
||||
}
|
||||
this.started = true;
|
||||
this.trackConnectionFirstSeen();
|
||||
await this.runProbeCycleNow();
|
||||
|
||||
if (!this.snapshot.activeConnectionId) {
|
||||
const fallbackConnectionId = selectInitialConnectionId(this.host);
|
||||
if (fallbackConnectionId) {
|
||||
await this.switchToConnection({ connectionId: fallbackConnectionId });
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.autoProbe !== false && PROBE_INTERVAL_MS > 0) {
|
||||
if (options?.autoProbe !== false) {
|
||||
this.probeIntervalHandle = setInterval(() => {
|
||||
void this.runProbeCycleNow();
|
||||
}, PROBE_INTERVAL_MS);
|
||||
}, PROBE_TICK_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,20 +594,8 @@ export class HostRuntimeController {
|
||||
|
||||
async updateHost(host: HostProfile): Promise<void> {
|
||||
this.host = host;
|
||||
const activeConnectionId = this.snapshot.activeConnectionId;
|
||||
const activeConnection = findConnectionById(host, activeConnectionId);
|
||||
if (!activeConnection) {
|
||||
await this.runProbeCycleNow();
|
||||
if (this.snapshot.activeConnectionId) {
|
||||
return;
|
||||
}
|
||||
const fallbackConnectionId = selectInitialConnectionId(host);
|
||||
if (!fallbackConnectionId) {
|
||||
await this.stop();
|
||||
return;
|
||||
}
|
||||
await this.switchToConnection({ connectionId: fallbackConnectionId });
|
||||
}
|
||||
this.trackConnectionFirstSeen();
|
||||
await this.runProbeCycleNow();
|
||||
}
|
||||
|
||||
ensureConnected(): void {
|
||||
@@ -674,9 +659,29 @@ export class HostRuntimeController {
|
||||
return;
|
||||
}
|
||||
|
||||
const probeByConnectionId = new Map<string, ConnectionProbeState>();
|
||||
const now = performance.now();
|
||||
const isOnline = this.snapshot.connectionStatus === "online";
|
||||
const activeConnectionId = this.snapshot.activeConnectionId;
|
||||
|
||||
const connectionsToProbe = this.host.connections.filter((connection) => {
|
||||
const lastProbed = this.connectionLastProbedAt.get(connection.id);
|
||||
if (lastProbed == null) {
|
||||
return true;
|
||||
}
|
||||
const firstSeen = this.connectionFirstSeenAt.get(connection.id) ?? now;
|
||||
const isActiveOnline = isOnline && connection.id === activeConnectionId;
|
||||
const interval = probeIntervalForConnection(firstSeen, isActiveOnline, now);
|
||||
return now - lastProbed >= interval;
|
||||
});
|
||||
|
||||
if (connectionsToProbe.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const probeByConnectionId = new Map(this.snapshot.probeByConnectionId);
|
||||
await Promise.all(
|
||||
this.host.connections.map(async (connection) => {
|
||||
connectionsToProbe.map(async (connection) => {
|
||||
this.connectionLastProbedAt.set(connection.id, performance.now());
|
||||
try {
|
||||
const latencyMs = await this.deps.measureLatency({
|
||||
host: this.host,
|
||||
@@ -700,12 +705,12 @@ export class HostRuntimeController {
|
||||
}
|
||||
this.updateSnapshot({ probeByConnectionId });
|
||||
|
||||
const activeConnectionId = this.snapshot.activeConnectionId;
|
||||
const activeProbe = activeConnectionId
|
||||
? probeByConnectionId.get(activeConnectionId)
|
||||
const currentActiveConnectionId = this.snapshot.activeConnectionId;
|
||||
const activeProbe = currentActiveConnectionId
|
||||
? probeByConnectionId.get(currentActiveConnectionId)
|
||||
: null;
|
||||
|
||||
if (!activeConnectionId || !findConnectionById(this.host, activeConnectionId)) {
|
||||
if (!currentActiveConnectionId || !findConnectionById(this.host, currentActiveConnectionId)) {
|
||||
const nextConnectionId = selectBestConnection({
|
||||
candidates: buildConnectionCandidates(this.host),
|
||||
probeByConnectionId,
|
||||
@@ -724,7 +729,7 @@ export class HostRuntimeController {
|
||||
candidates: buildConnectionCandidates(this.host),
|
||||
probeByConnectionId,
|
||||
});
|
||||
if (nextConnectionId && nextConnectionId !== activeConnectionId) {
|
||||
if (nextConnectionId && nextConnectionId !== currentActiveConnectionId) {
|
||||
await this.switchToConnection({
|
||||
connectionId: nextConnectionId,
|
||||
expectedProbeVersion: requestVersion,
|
||||
@@ -745,7 +750,7 @@ export class HostRuntimeController {
|
||||
.sort((left, right) => left.latencyMs - right.latencyMs);
|
||||
|
||||
const fastest = available[0] ?? null;
|
||||
if (!fastest || fastest.connectionId === activeConnectionId) {
|
||||
if (!fastest || fastest.connectionId === currentActiveConnectionId) {
|
||||
this.switchCandidateConnectionId = null;
|
||||
this.switchCandidateHitCount = 0;
|
||||
return;
|
||||
@@ -839,6 +844,22 @@ export class HostRuntimeController {
|
||||
});
|
||||
}
|
||||
|
||||
private trackConnectionFirstSeen(): void {
|
||||
const now = performance.now();
|
||||
const currentIds = new Set(this.host.connections.map((c) => c.id));
|
||||
for (const id of this.connectionFirstSeenAt.keys()) {
|
||||
if (!currentIds.has(id)) {
|
||||
this.connectionFirstSeenAt.delete(id);
|
||||
this.connectionLastProbedAt.delete(id);
|
||||
}
|
||||
}
|
||||
for (const connection of this.host.connections) {
|
||||
if (!this.connectionFirstSeenAt.has(connection.id)) {
|
||||
this.connectionFirstSeenAt.set(connection.id, now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isCurrentSwitchRequest(version: number): boolean {
|
||||
return version === this.switchRequestVersion;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user