66
77import * as Effect from "effect/Effect"
88import * as Exit from "effect/Exit"
9+ import * as Fiber from "effect/Fiber"
910import * as HashMap from "effect/HashMap"
1011import * as Option from "effect/Option"
1112import * as Queue from "effect/Queue"
1213import * as Ref from "effect/Ref"
1314import type * as Schema from "effect/Schema"
14- import * as Scope from "effect/Scope"
1515import * as Stream from "effect/Stream"
1616import type { ActionError , ExecutionServices , Machine , Runtime } from "../Machine.js"
1717import { ChildAlreadyExistsError , InfiniteTransitionError , MachineSchemaDecodeError } from "./machineErrors.js"
@@ -34,7 +34,7 @@ type AnyInvokeConfig = Machine.InvokeConfig<any, any, any, any, any, any, any, a
3434
3535interface InvokeSession {
3636 readonly token : symbol
37- readonly scope : Scope . Closeable
37+ readonly watcher : Fiber . Fiber < void > | undefined
3838 readonly childId : string
3939 readonly path : string
4040}
@@ -240,10 +240,17 @@ const makeProcessLogic: <
240240 return Option . isSome ( current ) && current . value . token === token
241241 } )
242242 )
243+ const stopInvokeSession = ( session : InvokeSession ) : Effect . Effect < void > =>
244+ Effect . all (
245+ [
246+ ...( session . watcher === undefined ? [ ] : [ Fiber . interrupt ( session . watcher ) ] ) ,
247+ context . stopChild ( session . childId )
248+ ] ,
249+ { discard : true , concurrency : "unbounded" }
250+ )
243251 const removeInvoke = (
244252 key : string ,
245- token : symbol | undefined ,
246- exit : Exit . Exit < unknown , unknown >
253+ token : symbol | undefined
247254 ) : Effect . Effect < void > =>
248255 Ref . modify ( invokeSessions , ( sessions ) => {
249256 const current = HashMap . get ( sessions , key )
@@ -254,80 +261,91 @@ const makeProcessLogic: <
254261 Effect . flatMap ( ( session ) =>
255262 session === undefined
256263 ? Effect . void
257- : Scope . close ( session . scope , exit ) . pipe (
258- Effect . andThen ( context . stopChild ( session . childId ) )
259- )
264+ : stopInvokeSession ( session )
260265 )
261266 )
262- const stopInvoke = ( key : string , exit : Exit . Exit < unknown , unknown > ) : Effect . Effect < void > =>
263- removeInvoke ( key , undefined , exit )
264- const stopAllInvokes = ( exit : Exit . Exit < unknown , unknown > ) : Effect . Effect < void > =>
265- Ref . modify ( invokeSessions , ( sessions ) => [ HashMap . toEntries ( sessions ) , HashMap . empty ( ) ] as const ) . pipe (
267+ const stopInvoke = ( key : string ) : Effect . Effect < void > => removeInvoke ( key , undefined )
268+ const stopAllInvokes : Effect . Effect < void > = Ref . modify ( invokeSessions , ( sessions ) =>
269+ [ HashMap . toEntries ( sessions ) , HashMap . empty ( ) ] as const ) . pipe (
266270 Effect . flatMap ( ( sessions ) =>
267271 Effect . all (
268272 sessions . map ( ( [ , session ] ) =>
269- Scope . close ( session . scope , exit ) . pipe (
270- Effect . andThen ( context . stopChild ( session . childId ) )
271- )
273+ stopInvokeSession ( session )
272274 ) ,
273275 { discard : true , concurrency : "unbounded" }
274276 )
275277 )
276278 )
277- const startInvokeWatchers = Effect . fnUntraced ( function * (
279+ const handleInvokeOutcome = (
278280 config : AnyInvokeConfig ,
279- child : internalRuntime . MachineRef < any , any , any , any > ,
280281 key : string ,
281282 token : symbol ,
282- scope : Scope . Closeable
283+ outcome : internalRuntime . RuntimeOutcome < any , any , any >
284+ ) : Effect . Effect < void > => {
285+ if ( outcome . _tag === "Stopped" ) {
286+ return Effect . void
287+ }
288+ return isCurrentInvoke ( key , token ) . pipe (
289+ Effect . flatMap ( ( isCurrent ) => {
290+ if ( ! isCurrent ) {
291+ return Effect . void
292+ }
293+ if ( outcome . _tag === "Done" ) {
294+ const mappedEvent = config . onDone === undefined
295+ ? outcome . output
296+ : config . onDone ( { id : config . id , output : outcome . output } )
297+ return mappedEvent === undefined
298+ ? Effect . void
299+ : context . self . send ( mappedEvent as Machine . EventOf < Events > ) . pipe (
300+ Effect . catchTag ( "StoppedError" , ( ) => Effect . void )
301+ )
302+ }
303+ return context . failCause ( outcome . cause )
304+ } )
305+ )
306+ }
307+ const startInvokeSnapshotWatcher = Effect . fnUntraced ( function * (
308+ config : AnyInvokeConfig ,
309+ child : internalRuntime . MachineRef < any , any , any , any > ,
310+ key : string ,
311+ token : symbol
283312 ) {
284- if ( config . snapshot !== undefined ) {
285- const mapSnapshot = config . snapshot
286- yield * child . changes . pipe (
287- Stream . filter ( ( snapshot ) => snapshot . status === "active" ) ,
288- Stream . runForEach ( ( snapshot ) =>
289- isCurrentInvoke ( key , token ) . pipe (
290- Effect . flatMap ( ( isCurrent ) => {
291- if ( ! isCurrent ) {
292- return Effect . void
293- }
294- const mappedEvent = mapSnapshot ( { id : config . id , snapshot } )
295- return mappedEvent === undefined
296- ? Effect . void
297- : context . self . send ( mappedEvent as Machine . EventOf < Events > ) . pipe (
298- Effect . catchTag ( "StoppedError" , ( ) => Effect . void )
299- )
300- } )
301- )
302- ) ,
303- Effect . forkIn ( scope ) ,
304- Effect . asVoid
305- )
313+ if ( config . snapshot === undefined ) {
314+ return
306315 }
307- yield * internalRuntime . watch ( child ) . pipe (
308- Stream . runForEach ( ( outcome ) =>
316+ const mapSnapshot = config . snapshot
317+ const watcher = yield * child . changes . pipe (
318+ Stream . filter ( ( snapshot ) => snapshot . status === "active" ) ,
319+ Stream . runForEach ( ( snapshot ) =>
309320 isCurrentInvoke ( key , token ) . pipe (
310321 Effect . flatMap ( ( isCurrent ) => {
311- if ( ! isCurrent || outcome . _tag === "Stopped" ) {
322+ if ( ! isCurrent ) {
312323 return Effect . void
313324 }
314- if ( outcome . _tag === "Done" ) {
315- const mappedEvent = config . onDone === undefined
316- ? outcome . output
317- : config . onDone ( { id : config . id , output : outcome . output } )
318- return mappedEvent === undefined
319- ? Effect . void
320- : context . self . send ( mappedEvent as Machine . EventOf < Events > ) . pipe (
321- Effect . catchTag ( "StoppedError" , ( ) => Effect . void )
322- )
323- }
324- return context . failCause ( outcome . cause )
325+ const mappedEvent = mapSnapshot ( { id : config . id , snapshot } )
326+ return mappedEvent === undefined
327+ ? Effect . void
328+ : context . self . send ( mappedEvent as Machine . EventOf < Events > ) . pipe (
329+ Effect . catchTag ( "StoppedError" , ( ) => Effect . void )
330+ )
325331 } )
326332 )
327333 ) ,
328- Effect . forkIn ( scope ) ,
329- Effect . asVoid
334+ Effect . forkChild
330335 )
336+ const installed = yield * Ref . modify ( invokeSessions , ( sessions ) => {
337+ const current = HashMap . get ( sessions , key )
338+ if ( Option . isNone ( current ) || current . value . token !== token ) {
339+ return [ false , sessions ] as const
340+ }
341+ return [
342+ true ,
343+ HashMap . set ( sessions , key , { ...current . value , watcher } )
344+ ] as const
345+ } )
346+ if ( ! installed ) {
347+ yield * Fiber . interrupt ( watcher )
348+ }
331349 } )
332350 const startInvoke = Effect . fnUntraced ( function * < StateId extends Machine . StateIdentifier < States > > (
333351 path : StateId ,
@@ -337,13 +355,11 @@ const makeProcessLogic: <
337355 const invokeId = String ( config . id )
338356 const key = makeInvokeSessionKey ( path , invokeId )
339357 const childId = config . address === undefined ? makeInvokeChildId ( path , invokeId ) : String ( config . address )
340- const scope = yield * Scope . make ( "parallel" )
341358 const reserved = yield * Ref . modify ( invokeSessions , ( sessions ) =>
342359 HashMap . has ( sessions , key )
343360 ? [ false , sessions ] as const
344- : [ true , HashMap . set ( sessions , key , { token, scope , childId, path } ) ] as const )
361+ : [ true , HashMap . set ( sessions , key , { token, watcher : undefined , childId, path } ) ] as const )
345362 if ( ! reserved ) {
346- yield * Scope . close ( scope , Exit . void )
347363 return yield * Effect . fail ( new ChildAlreadyExistsError ( { id : invokeId } ) )
348364 }
349365 const logic = config . src ( )
@@ -358,24 +374,19 @@ const makeProcessLogic: <
358374 initial : ( childScope ) => logic . initial ( { ...childScope , sendParent } ) ,
359375 run : ( childContext ) => logic . run ( { ...childContext , sendParent } )
360376 } ,
361- config . descriptor === undefined
362- ? { id : childId }
363- : { id : childId , descriptor : config . descriptor }
377+ {
378+ id : childId ,
379+ ...( config . descriptor === undefined ? undefined : { descriptor : config . descriptor } ) ,
380+ onOutcome : ( outcome ) => handleInvokeOutcome ( config , key , token , outcome )
381+ }
364382 ) . pipe (
365383 Effect . onExit ( ( exit ) =>
366384 Exit . isFailure ( exit )
367- ? Ref . update ( invokeSessions , ( sessions ) => {
368- const current = HashMap . get ( sessions , key )
369- return Option . isSome ( current ) && current . value . token === token
370- ? HashMap . remove ( sessions , key )
371- : sessions
372- } ) . pipe (
373- Effect . andThen ( Scope . close ( scope , Exit . failCause ( exit . cause ) ) )
374- )
385+ ? removeInvoke ( key , token )
375386 : Effect . void
376387 )
377388 )
378- yield * startInvokeWatchers ( config , child , key , token , scope )
389+ yield * startInvokeSnapshotWatcher ( config , child , key , token )
379390 } )
380391 const startInvokes : (
381392 configuration : Model . ActiveConfiguration ,
@@ -412,7 +423,7 @@ const makeProcessLogic: <
412423 internalPlanner . sortExitPaths ( machine , paths ) . flatMap ( ( path ) =>
413424 HashMap . toEntries ( sessions )
414425 . filter ( ( [ , session ] ) => session . path === path )
415- . map ( ( [ key ] ) => stopInvoke ( key , Exit . void ) )
426+ . map ( ( [ key ] ) => stopInvoke ( key ) )
416427 ) ,
417428 { discard : true , concurrency : "unbounded" }
418429 )
@@ -481,7 +492,7 @@ const makeProcessLogic: <
481492
482493 if ( planned . done ) {
483494 terminal = { output : planned . output as Output }
484- yield * stopAllInvokes ( Exit . succeed ( planned . output ) )
495+ yield * stopAllInvokes
485496 } else {
486497 if ( changed ) {
487498 for ( const [ path , entryEvent ] of entryEvents ) {
@@ -509,7 +520,7 @@ const makeProcessLogic: <
509520 }
510521 return terminal . output
511522 } ) . pipe (
512- Effect . onExit ( ( exit ) => stopAllInvokes ( exit ) )
523+ Effect . onExit ( ( ) => stopAllInvokes )
513524 )
514525 } ) ,
515526 context
0 commit comments