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,87 @@ 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+ isCurrentInvoke ( key , token ) . pipe (
286+ Effect . flatMap ( ( isCurrent ) => {
287+ if ( ! isCurrent || outcome . _tag === "Stopped" ) {
288+ return Effect . void
289+ }
290+ if ( outcome . _tag === "Done" ) {
291+ const mappedEvent = config . onDone === undefined
292+ ? outcome . output
293+ : config . onDone ( { id : config . id , output : outcome . output } )
294+ return mappedEvent === undefined
295+ ? Effect . void
296+ : context . self . send ( mappedEvent as Machine . EventOf < Events > ) . pipe (
297+ Effect . catchTag ( "StoppedError" , ( ) => Effect . void )
298+ )
299+ }
300+ return context . failCause ( outcome . cause )
301+ } )
302+ )
303+ const startInvokeSnapshotWatcher = Effect . fnUntraced ( function * (
304+ config : AnyInvokeConfig ,
305+ child : internalRuntime . MachineRef < any , any , any , any > ,
306+ key : string ,
307+ token : symbol
283308 ) {
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- )
309+ if ( config . snapshot === undefined ) {
310+ return
306311 }
307- yield * internalRuntime . watch ( child ) . pipe (
308- Stream . runForEach ( ( outcome ) =>
312+ const mapSnapshot = config . snapshot
313+ const watcher = yield * child . changes . pipe (
314+ Stream . filter ( ( snapshot ) => snapshot . status === "active" ) ,
315+ Stream . runForEach ( ( snapshot ) =>
309316 isCurrentInvoke ( key , token ) . pipe (
310317 Effect . flatMap ( ( isCurrent ) => {
311- if ( ! isCurrent || outcome . _tag === "Stopped" ) {
318+ if ( ! isCurrent ) {
312319 return Effect . void
313320 }
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 )
321+ const mappedEvent = mapSnapshot ( { id : config . id , snapshot } )
322+ return mappedEvent === undefined
323+ ? Effect . void
324+ : context . self . send ( mappedEvent as Machine . EventOf < Events > ) . pipe (
325+ Effect . catchTag ( "StoppedError" , ( ) => Effect . void )
326+ )
325327 } )
326328 )
327329 ) ,
328- Effect . forkIn ( scope ) ,
329- Effect . asVoid
330+ Effect . forkChild
330331 )
332+ const installed = yield * Ref . modify ( invokeSessions , ( sessions ) => {
333+ const current = HashMap . get ( sessions , key )
334+ if ( Option . isNone ( current ) || current . value . token !== token ) {
335+ return [ false , sessions ] as const
336+ }
337+ return [
338+ true ,
339+ HashMap . set ( sessions , key , { ...current . value , watcher } )
340+ ] as const
341+ } )
342+ if ( ! installed ) {
343+ yield * Fiber . interrupt ( watcher )
344+ }
331345 } )
332346 const startInvoke = Effect . fnUntraced ( function * < StateId extends Machine . StateIdentifier < States > > (
333347 path : StateId ,
@@ -337,13 +351,11 @@ const makeProcessLogic: <
337351 const invokeId = String ( config . id )
338352 const key = makeInvokeSessionKey ( path , invokeId )
339353 const childId = config . address === undefined ? makeInvokeChildId ( path , invokeId ) : String ( config . address )
340- const scope = yield * Scope . make ( "parallel" )
341354 const reserved = yield * Ref . modify ( invokeSessions , ( sessions ) =>
342355 HashMap . has ( sessions , key )
343356 ? [ false , sessions ] as const
344- : [ true , HashMap . set ( sessions , key , { token, scope , childId, path } ) ] as const )
357+ : [ true , HashMap . set ( sessions , key , { token, watcher : undefined , childId, path } ) ] as const )
345358 if ( ! reserved ) {
346- yield * Scope . close ( scope , Exit . void )
347359 return yield * Effect . fail ( new ChildAlreadyExistsError ( { id : invokeId } ) )
348360 }
349361 const logic = config . src ( )
@@ -358,24 +370,19 @@ const makeProcessLogic: <
358370 initial : ( childScope ) => logic . initial ( { ...childScope , sendParent } ) ,
359371 run : ( childContext ) => logic . run ( { ...childContext , sendParent } )
360372 } ,
361- config . descriptor === undefined
362- ? { id : childId }
363- : { id : childId , descriptor : config . descriptor }
373+ {
374+ id : childId ,
375+ ...( config . descriptor === undefined ? undefined : { descriptor : config . descriptor } ) ,
376+ onOutcome : ( outcome ) => handleInvokeOutcome ( config , key , token , outcome )
377+ }
364378 ) . pipe (
365379 Effect . onExit ( ( exit ) =>
366380 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- )
381+ ? removeInvoke ( key , token )
375382 : Effect . void
376383 )
377384 )
378- yield * startInvokeWatchers ( config , child , key , token , scope )
385+ yield * startInvokeSnapshotWatcher ( config , child , key , token )
379386 } )
380387 const startInvokes : (
381388 configuration : Model . ActiveConfiguration ,
@@ -412,7 +419,7 @@ const makeProcessLogic: <
412419 internalPlanner . sortExitPaths ( machine , paths ) . flatMap ( ( path ) =>
413420 HashMap . toEntries ( sessions )
414421 . filter ( ( [ , session ] ) => session . path === path )
415- . map ( ( [ key ] ) => stopInvoke ( key , Exit . void ) )
422+ . map ( ( [ key ] ) => stopInvoke ( key ) )
416423 ) ,
417424 { discard : true , concurrency : "unbounded" }
418425 )
@@ -481,7 +488,7 @@ const makeProcessLogic: <
481488
482489 if ( planned . done ) {
483490 terminal = { output : planned . output as Output }
484- yield * stopAllInvokes ( Exit . succeed ( planned . output ) )
491+ yield * stopAllInvokes
485492 } else {
486493 if ( changed ) {
487494 for ( const [ path , entryEvent ] of entryEvents ) {
@@ -509,7 +516,7 @@ const makeProcessLogic: <
509516 }
510517 return terminal . output
511518 } ) . pipe (
512- Effect . onExit ( ( exit ) => stopAllInvokes ( exit ) )
519+ Effect . onExit ( ( ) => stopAllInvokes )
513520 )
514521 } ) ,
515522 context
0 commit comments