Skip to content

Commit 73e1539

Browse files
Unify invoked child ownership
1 parent 0189ba7 commit 73e1539

3 files changed

Lines changed: 272 additions & 158 deletions

File tree

.changeset/compiled-machine-startup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"@typeonce/effect-machine": patch
33
---
44

5-
Compile eligible machine initial-state normalization and reuse the validated startup configuration when initializing invoked children.
5+
Compile eligible machine initial-state normalization, reuse the validated startup configuration, and unify invoked-child ownership with the runtime child registry.

src/internal/machineProcess.ts

Lines changed: 24 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,6 @@ const makeChildlessCompiledDrain = (
251251
}
252252

253253
class InvokeExecutionKernel {
254-
readonly sessions: Map<string, InvokeSession>
255254
initialized = false
256255
initial:
257256
| {
@@ -266,7 +265,6 @@ class InvokeExecutionKernel {
266265
readonly activeConfiguration: Model.ActiveConfiguration
267266
readonly entryPaths: ReadonlyArray<string>
268267
}) {
269-
this.sessions = new Map()
270268
this.initial = initial
271269
}
272270

@@ -278,84 +276,46 @@ class InvokeExecutionKernel {
278276
return `Machine.invoke:${this.makeSessionKey(path, id)}`
279277
}
280278

281-
private stopSession(
282-
scope: internalRuntime.ProcessScope<any>,
283-
session: InvokeSession
284-
): Effect.Effect<void> {
285-
return scope.stopChild(session.childId)
286-
}
287-
288-
private remove(
289-
scope: internalRuntime.ProcessScope<any>,
290-
key: string,
291-
token: symbol | undefined
292-
): Effect.Effect<void> {
293-
return Effect.suspend(() => {
294-
const current = this.sessions.get(key)
295-
if (current === undefined || (token !== undefined && current.token !== token)) {
296-
return Effect.void
297-
}
298-
this.sessions.delete(key)
299-
return this.stopSession(scope, current)
300-
})
301-
}
302-
303-
stop(scope: internalRuntime.ProcessScope<any>, key: string): Effect.Effect<void> {
304-
return this.remove(scope, key, undefined)
305-
}
306-
307-
stopAll(scope: internalRuntime.ProcessScope<any>): Effect.Effect<void> {
308-
return Effect.suspend(() => {
309-
const effects = Array.from(this.sessions.values(), (session) => this.stopSession(scope, session))
310-
this.sessions.clear()
311-
return runParallelDiscard(effects)
312-
})
313-
}
314-
315279
start(
316280
context: internalRuntime.CompiledProcessContext<any, any>,
317281
path: string,
318282
config: AnyInvokeConfig
319283
): Effect.Effect<void, any, any> {
320284
return Effect.suspend(() => {
321-
const token = Symbol()
322285
const invokeId = String(config.id)
323286
const key = this.makeSessionKey(path, invokeId)
324287
const childId = config.address === undefined ? this.makeChildId(path, invokeId) : String(config.address)
325-
if (this.sessions.has(key)) {
326-
return Effect.fail(new ChildAlreadyExistsError({ id: invokeId }))
327-
}
328-
this.sessions.set(key, { token, childId, path })
329288
const logic = config.src() as internalRuntime.ProcessLogic<any, any, any, any, any, any>
330289
const scope = context.scope
331-
const sendParent = makeInvokeSendParent(this.sessions, scope.self, key, token)
332-
return scope.spawn(
290+
return context.ownedChildren.spawn(
333291
logic,
334292
{
293+
key,
294+
path,
335295
id: childId,
296+
duplicateId: invokeId,
336297
...(config.descriptor === undefined ? undefined : { descriptor: config.descriptor }),
337-
[internalRuntime.sendParentOverride]: sendParent,
338-
onOutcome: makeInvokeOutcomeHandler(
339-
this.sessions,
340-
scope.self,
341-
scope.failCause,
342-
config,
343-
key,
344-
token
345-
),
298+
sendParent: (isCurrent, event) => isCurrent() ? scope.self.send(event) : Effect.void,
299+
onOutcome: (isCurrent, outcome) => {
300+
if (outcome._tag === "Stopped" || !isCurrent()) return Effect.void
301+
if (outcome._tag !== "Done") return scope.failCause(outcome.cause)
302+
const mappedEvent = config.onDone === undefined
303+
? outcome.output
304+
: config.onDone({ id: config.id, output: outcome.output })
305+
return mappedEvent === undefined
306+
? Effect.void
307+
: scope.self.send(mappedEvent).pipe(Effect.catchTag("StoppedError", () => Effect.void))
308+
},
346309
...(config.snapshot === undefined ? undefined : {
347-
[internalRuntime.activeSnapshotObserver]: makeInvokeSnapshotHandler(
348-
this.sessions,
349-
scope.self,
350-
config,
351-
key,
352-
token
353-
)
310+
onSnapshot: (isCurrent: () => boolean, snapshot: any) => {
311+
if (!isCurrent()) return Effect.void
312+
const mappedEvent = config.snapshot!({ id: config.id, snapshot })
313+
return mappedEvent === undefined
314+
? Effect.void
315+
: scope.self.send(mappedEvent).pipe(Effect.catchTag("StoppedError", () => Effect.void))
316+
}
354317
})
355318
}
356-
).pipe(
357-
Effect.onExit((exit) => Exit.isFailure(exit) ? this.remove(scope, key, token) : Effect.void),
358-
Effect.asVoid
359319
)
360320
})
361321
}
@@ -379,18 +339,6 @@ class InvokeExecutionKernel {
379339
)
380340
return effects.length === 0 ? undefined : runSequentialDiscard(effects)
381341
}
382-
383-
stopPaths(
384-
machine: Machine.Any,
385-
scope: internalRuntime.ProcessScope<any>,
386-
paths: ReadonlyArray<string>
387-
): Effect.Effect<void> | undefined {
388-
const keys = new Set(internalPlanner.sortExitPaths(machine, paths))
389-
const effects = Array.from(this.sessions.entries())
390-
.filter(([, session]) => keys.has(session.path))
391-
.map(([key]) => this.stop(scope, key))
392-
return effects.length === 0 ? undefined : runParallelDiscard(effects)
393-
}
394342
}
395343

396344
const makeInvokingCompiledDrain = (
@@ -464,7 +412,7 @@ const makeInvokingCompiledDrain = (
464412
beforeCommit.push(internalPlanner.runCommands(planned.commands, scope))
465413
}
466414
if (changed) {
467-
const stopping = execution.stopPaths(machine, scope, exitPaths)
415+
const stopping = context.ownedChildren.stopPaths(exitPaths)
468416
if (stopping !== undefined) beforeCommit.push(stopping)
469417
}
470418
const afterCommit: Array<Effect.Effect<void, any, any>> = []
@@ -477,7 +425,7 @@ const makeInvokingCompiledDrain = (
477425
)
478426
}
479427
if (planned.done) {
480-
afterCommit.push(execution.stopAll(scope))
428+
afterCommit.push(context.ownedChildren.stopAll())
481429
} else if (changed) {
482430
for (const [path, entryEvent] of entryEvents) {
483431
const starting = execution.startAll(machine, context, activeConfiguration, [path], entryEvent)

0 commit comments

Comments
 (0)