refactor(code-runtime): generate shared subprocess runner
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { inspect } from 'node:util'
|
||||
import type { CodeJsonValue } from '@deepseek-ai/dsh-code-runtime'
|
||||
import type { DoneMessage, ReplyMessage, WorkerBootData, WorkerToHost } from './protocol.ts'
|
||||
import { jsonStringBytesUpTo, jsonValueBytesUpTo, truncateJsonStringBytes } from './output-json.ts'
|
||||
import { decodeWorkerJson, encodeWorkerJson, snapshotCodeJsonValue } from './worker-json.ts'
|
||||
@@ -310,6 +311,7 @@ export function wireReplies(port: BootstrapPort, pending: Map<number, PendingCal
|
||||
* @param pending - the id-keyed map each posted call parks its handles in.
|
||||
* @param nextId - the shared mutable id counter (worker-issued correlation ids).
|
||||
* @param errorClasses - per-namespace constructors shared with program globals.
|
||||
* @param maxFrameBytes - optional serialized transport cap checked before posting.
|
||||
* @returns one namespace object per declaration, in declaration order.
|
||||
*/
|
||||
export function makeNamespaces(
|
||||
@@ -318,6 +320,7 @@ export function makeNamespaces(
|
||||
pending: Map<number, PendingCall>,
|
||||
nextId: { value: number },
|
||||
errorClasses: Map<string, BindingErrorConstructor> = makeBindingErrorClasses(data),
|
||||
maxFrameBytes?: number,
|
||||
): Record<string, unknown>[] {
|
||||
return data.namespaces.map(({ global, names }) => {
|
||||
const errorClass = errorClasses.get(global)
|
||||
@@ -335,6 +338,11 @@ export function makeNamespaces(
|
||||
if (detached === undefined) {
|
||||
return Promise.reject(bindingFailure(errorClass, name, 'binding arguments must be lossless JSON'))
|
||||
}
|
||||
const call = { type: 'call' as const, id: nextId.value, global, name, args: encodeWorkerJson(detached) }
|
||||
if (maxFrameBytes !== undefined
|
||||
&& jsonValueBytesUpTo(call as unknown as CodeJsonValue, maxFrameBytes) === undefined) {
|
||||
return Promise.reject(bindingFailure(errorClass, name, 'binding arguments exceed maxFrameBytes'))
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = nextId.value++
|
||||
pending.set(id, {
|
||||
@@ -344,7 +352,7 @@ export function makeNamespaces(
|
||||
},
|
||||
})
|
||||
try {
|
||||
port.postMessage({ type: 'call', id, global, name, args: encodeWorkerJson(detached) })
|
||||
port.postMessage(call)
|
||||
} catch (error: unknown) {
|
||||
pending.delete(id)
|
||||
const message = `binding arguments must be structured-cloneable: ${error instanceof CapturedError ? error.message : String(error)}`
|
||||
@@ -364,12 +372,14 @@ export function makeNamespaces(
|
||||
* @param port - host message port or test double.
|
||||
* @param data - the boot payload the host sent.
|
||||
* @param streams - stdout/stderr objects captured as program logs.
|
||||
* @param maxFrameBytes - optional serialized transport cap checked before posting.
|
||||
* @returns after posting the done message.
|
||||
*/
|
||||
export async function runWorkerMain(
|
||||
port: BootstrapPort,
|
||||
data: WorkerBootData,
|
||||
streams: { stdout: PatchableStream; stderr: PatchableStream },
|
||||
maxFrameBytes?: number,
|
||||
): Promise<void> {
|
||||
const logs = new LogBuffer(
|
||||
data.maxOutputBytes,
|
||||
@@ -384,7 +394,7 @@ export async function runWorkerMain(
|
||||
|
||||
const nextId = { value: 1 }
|
||||
const errorClasses = makeBindingErrorClasses(data)
|
||||
const namespaces = makeNamespaces(data, port, pending, nextId, errorClasses)
|
||||
const namespaces = makeNamespaces(data, port, pending, nextId, errorClasses, maxFrameBytes)
|
||||
const errorClassParameters: string[] = []
|
||||
const errorClassValues: BindingErrorConstructor[] = []
|
||||
for (const namespace of data.namespaces) {
|
||||
@@ -420,5 +430,8 @@ export async function runWorkerMain(
|
||||
...prepareException(error, logs.remainingOutputBytes(), data.maxOutputBytes),
|
||||
}
|
||||
}
|
||||
port.postMessage(done)
|
||||
port.postMessage(maxFrameBytes !== undefined
|
||||
&& jsonValueBytesUpTo(done as unknown as CodeJsonValue, maxFrameBytes) === undefined
|
||||
? { type: 'output-limit' }
|
||||
: done)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Shared host mechanics for local and subprocess-hosted TypeScript worker runtimes. */
|
||||
|
||||
import { stripTypeScriptTypes } from 'node:module'
|
||||
import type { Readable } from 'node:stream'
|
||||
import type {
|
||||
CodeBindingNamespace,
|
||||
CodeJsonValue,
|
||||
@@ -15,6 +16,28 @@ import type { WorkerJsonWire } from './worker-json.ts'
|
||||
/** Smallest cap that can represent an empty log array and failure message. */
|
||||
export const MIN_RUNTIME_OUTPUT_BYTES = 4
|
||||
|
||||
/**
|
||||
* Resolve after a worker pipe emits queued data or closes during termination.
|
||||
* @param stream - captured worker or child-process pipe.
|
||||
* @returns after no more queued bytes can arrive.
|
||||
*/
|
||||
export function waitForRuntimePipeDrain(stream: Readable): Promise<void> {
|
||||
if (stream.readableEnded || stream.destroyed) return Promise.resolve()
|
||||
return new Promise((resolve) => {
|
||||
const done = (): void => {
|
||||
stream.off('end', done)
|
||||
stream.off('close', done)
|
||||
stream.off('error', done)
|
||||
resolve()
|
||||
}
|
||||
stream.once('end', done)
|
||||
stream.once('close', done)
|
||||
stream.once('error', done)
|
||||
/* v8 ignore next -- termination can win the adjacent listener-registration race. */
|
||||
if (stream.readableEnded || stream.destroyed) done()
|
||||
})
|
||||
}
|
||||
|
||||
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/
|
||||
const RESERVED_WORDS = new Set([
|
||||
'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do',
|
||||
@@ -223,5 +246,6 @@ export class RuntimeOutputLedger {
|
||||
}
|
||||
|
||||
export { decodeWorkerJson, encodeWorkerJson, snapshotCodeJsonValue } from './worker-json.ts'
|
||||
export { jsonValueBytesUpTo } from './output-json.ts'
|
||||
export { jsonStringBytesUpTo, jsonValueBytesUpTo } from './output-json.ts'
|
||||
export { runWorkerMain } from './bootstrap.ts'
|
||||
export type { WorkerJsonWire } from './worker-json.ts'
|
||||
Reference in New Issue
Block a user