History

Add polling to desktop sidecar and fix infinite loop in findDataDirOwnershipHeld

Griffin Evans committed 4 months ago

Commit c91dcdb1 parent 9d996dc2 files · +46 −11Browse files

Unified · Split

2 files
  1. apps/desktop/src/main/sidecar.ts +33 −6
  2. apps/local/src/db/data-dir-ownership.ts +13 −5
apps/desktop/src/main/sidecar.ts+33−6
@@ -37,6 +37,8 @@ const sidecarLog = log.scope("sidecar"); · 36 unmodified lines
37const STDERR_TAIL_LIMIT = 8 * 1024;37const STDERR_TAIL_LIMIT = 8 * 1024;
38const READY_SENTINEL = "EXECUTOR_READY";38const READY_SENTINEL = "EXECUTOR_READY";
39const ATTACHED_SENTINEL = "EXECUTOR_ATTACHED";39const ATTACHED_SENTINEL = "EXECUTOR_ATTACHED";
40const ATTACH_OWNER_TIMEOUT_MS = 15_000;
41const ATTACH_OWNER_POLL_MS = 150;
40 42
41// Children deliberately stopped via stopSidecar (quit, restart, update) —43// Children deliberately stopped via stopSidecar (quit, restart, update) —
42// their exits are expected and must not be reported as crashes.44// their exits are expected and must not be reported as crashes.
@@ -281,6 +283,7 @@ export async function startSidecar(options: StartOptions = {}): Promise<SidecarC · 238 unmodified lines
281 let stdoutControlBuffer = "";283 let stdoutControlBuffer = "";
282 let resolved = false;284 let resolved = false;
283 let rejected = false;285 let rejected = false;
286 let attachedOwnerResolution: Promise<boolean> | null = null;
284 287
285 const logStdoutLine = makeLineSplitter((line) => sidecarLog.info(line));288 const logStdoutLine = makeLineSplitter((line) => sidecarLog.info(line));
286 const logStderrLine = makeLineSplitter((line) => sidecarLog.error(line));289 const logStderrLine = makeLineSplitter((line) => sidecarLog.error(line));
@@ -294,12 +297,21 @@ export async function startSidecar(options: StartOptions = {}): Promise<SidecarC · 7 unmodified lines
294 297
295 const resolveAttachedOwner = async (): Promise<boolean> => {298 const resolveAttachedOwner = async (): Promise<boolean> => {
296 if (resolved || rejected) return true;299 if (resolved || rejected) return true;
297 const attached = await attachToReachableLocalServer(dataDir);300 if (!attachedOwnerResolution) {
298 if (!attached) return false;301 attachedOwnerResolution = waitForReachableLocalServer(dataDir)
299 resolved = true;302 .then((attached) => {
300 expectedExits.add(child);303 if (resolved || rejected) return true;
301 resolveStart(attached);304 if (!attached) return false;
302 return true;305 resolved = true;
306 expectedExits.add(child);
307 resolveStart(attached);
308 return true;
309 })
310 .finally(() => {
311 attachedOwnerResolution = null;
312 });
313 }
314 return attachedOwnerResolution;
303 };315 };
304 316
305 const onStdout = (chunk: Buffer) => {317 const onStdout = (chunk: Buffer) => {
@@ -418,6 +430,21 @@ const isDaemonReachable = async (origin: string): Promise<boolean> => { · 112 unmodified lines
418 return false;430 return false;
419};431};
420 432
433const waitForReachableLocalServer = async (
434 dataDir: string,
435 options: { readonly requireCliDaemon?: boolean } = {},
436): Promise<SidecarConnection | null> => {
437 const deadline = Date.now() + ATTACH_OWNER_TIMEOUT_MS;
438 while (Date.now() <= deadline) {
439 const attached = await attachToReachableLocalServer(dataDir, options);
440 if (attached) return attached;
441 const remaining = deadline - Date.now();
442 if (remaining <= 0) return null;
443 await delay(Math.min(ATTACH_OWNER_POLL_MS, remaining));
444 }
445 return null;
446};
447
421const attachToReachableLocalServer = async (448const attachToReachableLocalServer = async (
422 dataDir: string,449 dataDir: string,
423 options: { readonly requireCliDaemon?: boolean } = {},450 options: { readonly requireCliDaemon?: boolean } = {},
apps/local/src/db/data-dir-ownership.ts+13−5
@@ -65,12 +65,20 @@ const configureOwnershipLockClient = async (client: Client): Promise<void> => { · 64 unmodified lines
65};65};
66 66
67export const findDataDirOwnershipHeld = (cause: unknown): DataDirOwnershipHeld | null => {67export const findDataDirOwnershipHeld = (cause: unknown): DataDirOwnershipHeld | null => {
68 if (cause instanceof DataDirOwnershipHeld) return cause;68 const visited = new WeakSet<object>();
69 if (!isRecord(cause)) return null;69 let current = cause;
70 70
71 const nestedCause = cause.cause;71 while (true) {
72 if (nestedCause === undefined || nestedCause === cause) return null;72 if (current instanceof DataDirOwnershipHeld) return current;
73 return findDataDirOwnershipHeld(nestedCause);73 if (!isRecord(current)) return null;
74 if (visited.has(current)) return null;
75
76 visited.add(current);
77
78 const nestedCause = current.cause;
79 if (nestedCause === undefined) return null;
80 current = nestedCause;
81 }
74};82};
75 83
76export const acquireDataDirOwnership = async (dataDir: string): Promise<DataDirOwnership> => {84export const acquireDataDirOwnership = async (dataDir: string): Promise<DataDirOwnership> => {