Repository navigation
feat(serve): Propagate session list cancellation #8954
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -787,16 +787,22 @@ export function registerSessionRoutes( | |
| return runtime; | ||
| }; | ||
|
|
||
| const hasActivePersistedSessions = async (runtime: WorkspaceRuntime) => { | ||
| const hasActivePersistedSessions = async ( | ||
| runtime: WorkspaceRuntime, | ||
| signal: AbortSignal, | ||
| ) => { | ||
| try { | ||
| const page = await createWorkspaceRuntimeSessionService( | ||
| runtime, | ||
| ).listSessions({ | ||
| archiveState: 'active', | ||
| size: 1, | ||
| signal, | ||
| }); | ||
| signal.throwIfAborted(); | ||
| return page.items.length > 0; | ||
| } catch { | ||
| signal.throwIfAborted(); | ||
| return false; | ||
| } | ||
|
doudouOUC marked this conversation as resolved.
|
||
| }; | ||
|
|
@@ -4371,53 +4377,82 @@ export function registerSessionRoutes( | |
| ...(parentSessionId !== undefined ? { parentSessionId } : {}), | ||
| ...parsedSource, | ||
| }; | ||
| // Organized/archived views always need the persisted store: organized | ||
| // cursors are opaque (non-numeric) and archived-only workspaces have no | ||
| // active persisted sessions, so the live-only fallback would drop them. | ||
| // Metadata filters gather the whole workspace | ||
| // (persisted + live) to filter completely and paginates with an opaque | ||
| // activity cursor, so the numeric-cursor live fallback can't serve it. | ||
| const usePersisted = | ||
| readOnlySecondary || | ||
| runtime.primary || | ||
| view === 'organized' || | ||
| archiveState === 'archived' || | ||
| parentSessionId !== undefined || | ||
| parsedSource.sourceType !== undefined || | ||
| (cursor !== undefined && cursor !== '' | ||
| ? isNumericSessionCursor(cursor) | ||
| : await hasActivePersistedSessions(runtime)); | ||
| // The live path only reads cursor/size; persisted-only options | ||
| // (organized view or archived state) would be silently dropped there. | ||
| // usePersisted already routes those to the persisted path — assert it so | ||
| // a future option added to validation but not to that gate fails loudly. | ||
| if ( | ||
| !usePersisted && | ||
| (view !== undefined || | ||
| const controller = new AbortController(); | ||
| const onRequestAborted = () => { | ||
| controller.abort(new DOMException('Request aborted', 'AbortError')); | ||
| }; | ||
|
doudouOUC marked this conversation as resolved.
doudouOUC marked this conversation as resolved.
|
||
| const onResponseClosed = () => { | ||
| if (!res.writableEnded) { | ||
| controller.abort(new DOMException('Response closed', 'AbortError')); | ||
| } | ||
|
doudouOUC marked this conversation as resolved.
doudouOUC marked this conversation as resolved.
|
||
| }; | ||
| req.once('aborted', onRequestAborted); | ||
| res.once('close', onResponseClosed); | ||
| if (req.aborted || res.destroyed) onRequestAborted(); | ||
|
Comment on lines
+4389
to
+4391
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks — not taking this one. An immediate Supertest abort would not prove the pre-check because the route may never run; a valid test needs contrived middleware, and this handler has no await before listener installation. That complexity is not justified as a Critical fix after the current review depth. |
||
| try { | ||
| // Organized/archived views always need the persisted store: organized | ||
| // cursors are opaque (non-numeric) and archived-only workspaces have no | ||
| // active persisted sessions, so the live-only fallback would drop them. | ||
| // Metadata filters gather the whole workspace | ||
| // (persisted + live) to filter completely and paginates with an opaque | ||
| // activity cursor, so the numeric-cursor live fallback can't serve it. | ||
| const usePersisted = | ||
| readOnlySecondary || | ||
| runtime.primary || | ||
| view === 'organized' || | ||
| archiveState === 'archived' || | ||
| parentSessionId !== undefined || | ||
| parsedSource.sourceType !== undefined) | ||
| ) { | ||
| throw new Error( | ||
| 'session list live path received persisted-only options', | ||
| ); | ||
| parsedSource.sourceType !== undefined || | ||
| (cursor !== undefined && cursor !== '' | ||
| ? isNumericSessionCursor(cursor) | ||
| : await hasActivePersistedSessions(runtime, controller.signal)); | ||
| // The live path only reads cursor/size; persisted-only options | ||
| // (organized view or archived state) would be silently dropped there. | ||
| // usePersisted already routes those to the persisted path — assert it so | ||
| // a future option added to validation but not to that gate fails loudly. | ||
| if ( | ||
| !usePersisted && | ||
| (view !== undefined || | ||
| archiveState === 'archived' || | ||
| parentSessionId !== undefined || | ||
| parsedSource.sourceType !== undefined) | ||
| ) { | ||
| throw new Error( | ||
| 'session list live path received persisted-only options', | ||
| ); | ||
| } | ||
| const result = usePersisted | ||
| ? await runWorkspaceInspectionWithLogPolicy(runtime, () => | ||
| listWorkspaceSessionsForResponse(runtime.bridge, key, options, { | ||
| mergeLive: !readOnlySecondary, | ||
| runtimeBaseDir: runtime.sessionRuntimeBaseDir, | ||
| signal: controller.signal, | ||
| }), | ||
|
doudouOUC marked this conversation as resolved.
|
||
| ) | ||
| : listLiveWorkspaceSessionsForResponse( | ||
| runtime.bridge, | ||
| key, | ||
| options, | ||
| ); | ||
| controller.signal.throwIfAborted(); | ||
| if (res.destroyed) return; | ||
| res.status(200).json({ | ||
| sessions: result.sessions, | ||
| ...(result.nextCursor != null | ||
| ? { nextCursor: result.nextCursor } | ||
| : {}), | ||
| ...(result.liveMergeFailed ? { liveMergeFailed: true } : {}), | ||
| ...(result.truncated ? { truncated: true } : {}), | ||
| }); | ||
| } catch (err) { | ||
| if (controller.signal.aborted || res.destroyed) { | ||
| return; | ||
| } | ||
| throw err; | ||
|
doudouOUC marked this conversation as resolved.
|
||
| } finally { | ||
| req.off('aborted', onRequestAborted); | ||
| res.off('close', onResponseClosed); | ||
| } | ||
| const result = usePersisted | ||
| ? await runWorkspaceInspectionWithLogPolicy(runtime, () => | ||
| listWorkspaceSessionsForResponse(runtime.bridge, key, options, { | ||
| mergeLive: !readOnlySecondary, | ||
| runtimeBaseDir: runtime.sessionRuntimeBaseDir, | ||
| }), | ||
| ) | ||
| : listLiveWorkspaceSessionsForResponse(runtime.bridge, key, options); | ||
| res.status(200).json({ | ||
| sessions: result.sessions, | ||
| ...(result.nextCursor != null | ||
| ? { nextCursor: result.nextCursor } | ||
| : {}), | ||
| ...(result.liveMergeFailed ? { liveMergeFailed: true } : {}), | ||
| ...(result.truncated ? { truncated: true } : {}), | ||
| }); | ||
| } catch (err) { | ||
| if (err instanceof InvalidCursorError) { | ||
| res.status(400).json({ | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.