@@ -10,6 +10,7 @@ import * as Context from "effect/Context"
1010import * as Deferred from "effect/Deferred"
1111import * as Effect from "effect/Effect"
1212import * as Exit from "effect/Exit"
13+ import * as Fiber from "effect/Fiber"
1314import * as HashMap from "effect/HashMap"
1415import * as Option from "effect/Option"
1516import * as PubSub from "effect/PubSub"
@@ -304,13 +305,17 @@ const startInternal: <
304305 logic : ProcessLogic < State , Event , Error , Requirements , Output , InitialError > ,
305306 options : StartInternalOptions
306307) {
308+ type ProcessTermination =
309+ | { readonly _tag : "Stopped" }
310+ | { readonly _tag : "Done" ; readonly output : Output }
311+ | { readonly _tag : "Failure" ; readonly cause : Cause . Cause < Error > }
312+
307313 const sessionId = yield * options . runtime . nextSessionId
308314 const id = options . id ?? sessionId
309315 const queue = yield * Queue . unbounded < Event > ( )
310- const stopRequested = yield * Deferred . make < void > ( )
311- const terminalized = yield * Deferred . make < void > ( )
312- const externalFailure = yield * Deferred . make < never , Error > ( )
316+ const termination = yield * Deferred . make < ProcessTermination > ( )
313317 const done = yield * Deferred . make < Output , Error | StoppedError > ( )
318+ const awaitCompletion = Deferred . await ( done ) . pipe ( Effect . exit , Effect . asVoid )
314319 const childResourcesState = yield * SynchronizedRef . make < ChildResourcesState > ( {
315320 closed : false ,
316321 resources : undefined
@@ -345,9 +350,7 @@ const startInternal: <
345350
346351 const cleanupStartupFailure = < A , E > ( exit : Exit . Exit < A , E > ) : Effect . Effect < void > =>
347352 Exit . isFailure ( exit )
348- ? closeChildren ( exit ) . pipe (
349- Effect . ensuring ( Deferred . succeed ( terminalized , void 0 ) )
350- )
353+ ? closeChildren ( exit )
351354 : Effect . void
352355
353356 const cleanup = options . onStop ?? Effect . void
@@ -594,7 +597,7 @@ const startInternal: <
594597 }
595598
596599 let initializing = true
597- const requestStop = Deferred . succeed ( stopRequested , void 0 ) . pipe ( Effect . asVoid )
600+ const requestStop = Deferred . succeed ( termination , { _tag : "Stopped" } ) . pipe ( Effect . asVoid )
598601 const self : ProcessAddress < Event > = {
599602 id,
600603 sessionId,
@@ -621,7 +624,11 @@ const startInternal: <
621624 sendParent,
622625 sendTo,
623626 stopChild,
624- failCause : ( cause ) => Deferred . failCause ( externalFailure , cause as Cause . Cause < Error > )
627+ failCause : ( cause ) =>
628+ Deferred . succeed ( termination , {
629+ _tag : "Failure" ,
630+ cause : cause as Cause . Cause < Error >
631+ } ) . pipe ( Effect . asVoid )
625632 }
626633
627634 const initial = yield * logic . initial ( scope ) . pipe (
@@ -769,8 +776,7 @@ const startInternal: <
769776 Effect . andThen ( closeChildren ( exit ) ) ,
770777 Effect . andThen ( setAndPublishSnapshot ( snapshot ) ) ,
771778 Effect . andThen ( cleanup ) ,
772- Effect . andThen ( completeDone ) ,
773- Effect . ensuring ( Deferred . succeed ( terminalized , void 0 ) )
779+ Effect . andThen ( completeDone )
774780 )
775781 )
776782
@@ -820,18 +826,8 @@ const startInternal: <
820826 return terminalizeWith ( snapshot , exit , Deferred . succeed ( done , output ) )
821827 }
822828
823- const terminalizeStop : Effect . Effect < void > = Effect . uninterruptible (
824- reserveStoppedSnapshot . pipe (
825- Effect . flatMap ( ( snapshot ) =>
826- snapshot === undefined
827- ? Deferred . await ( terminalized )
828- : terminalizeReservedStop ( snapshot )
829- )
830- )
831- )
832-
833829 const stop : Effect . Effect < void > = Effect . uninterruptible (
834- requestStop . pipe ( Effect . andThen ( Deferred . await ( terminalized ) ) )
830+ requestStop . pipe ( Effect . andThen ( awaitCompletion ) )
835831 )
836832
837833 const context : ProcessContext < State , Event > = {
@@ -907,93 +903,77 @@ const startInternal: <
907903 yield * options . onReady ( ref )
908904 }
909905
910- type ProcessTermination =
911- | { readonly _tag : "Stopped" ; readonly snapshot : RuntimeSnapshot < State , Error , Output > }
912- | {
913- readonly _tag : "Done"
914- readonly snapshot : RuntimeSnapshot < State , Error , Output >
915- readonly output : Output
906+ const reserveTermination = ( termination : ProcessTermination ) => {
907+ switch ( termination . _tag ) {
908+ case "Stopped" :
909+ return reserveStoppedSnapshot
910+ case "Done" :
911+ return reserveSuccessSnapshot ( termination . output )
912+ case "Failure" :
913+ return reserveFailureSnapshot ( termination . cause )
916914 }
917- | {
918- readonly _tag : "Failure"
919- readonly snapshot : RuntimeSnapshot < State , Error , Output >
920- readonly cause : Cause . Cause < Error >
915+ }
916+
917+ const completeTermination = (
918+ termination : ProcessTermination ,
919+ snapshot : RuntimeSnapshot < State , Error , Output >
920+ ) => {
921+ switch ( termination . _tag ) {
922+ case "Stopped" :
923+ return terminalizeReservedStop ( snapshot )
924+ case "Done" :
925+ return terminalizeReservedSuccess ( snapshot , termination . output )
926+ case "Failure" :
927+ return terminalizeReservedFailure ( snapshot , termination . cause )
921928 }
929+ }
922930
923- const arbitration = yield * Deferred . make < ProcessTermination > ( )
924- // Only the actual worker and stop waiter are restored to interruptibility.
925- // Reserving a terminal snapshot, publishing the shared arbitration result,
926- // and terminalizing stay masked so scope interruption cannot abandon a
927- // reservation. Both contenders read the same result: a contender that loses
928- // the reservation cannot finish the race with a different outcome.
929- const runFiber : Effect . Effect < void , never , Requirements > = Effect . uninterruptibleMask ( ( restore ) =>
930- Deferred . poll ( stopRequested ) . pipe (
931- Effect . flatMap ( ( requested ) => {
932- if ( Option . isSome ( requested ) ) {
933- return terminalizeStop
934- }
935- const awaitArbitration = Deferred . await ( arbitration )
936- const completeArbitration = ( termination : ProcessTermination ) =>
937- Deferred . succeed ( arbitration , termination ) . pipe (
938- Effect . andThen ( awaitArbitration )
939- )
940- const stopContender = restore ( Deferred . await ( stopRequested ) ) . pipe (
941- Effect . andThen ( reserveStoppedSnapshot ) ,
942- Effect . flatMap ( ( snapshot ) =>
943- snapshot === undefined
944- ? awaitArbitration
945- : completeArbitration ( { _tag : "Stopped" , snapshot } )
946- )
947- )
948- const workerContender : Effect . Effect < ProcessTermination , never , Requirements > = restore (
949- Effect . raceFirst (
950- Effect . suspend ( ( ) => logic . run ( context ) ) ,
951- Deferred . await ( externalFailure )
952- )
953- ) . pipe (
954- Effect . exit ,
955- Effect . flatMap ( ( exit ) =>
931+ const forkRuntime = < A , E , R > ( effect : Effect . Effect < A , E , R > ) =>
932+ options . fiberScope !== undefined
933+ ? Effect . forkIn ( effect , options . fiberScope )
934+ : options . detached === true
935+ ? Effect . forkDetach ( effect )
936+ : Effect . forkChild ( effect )
937+
938+ const pendingTermination = yield * Deferred . poll ( termination )
939+ const worker = Option . isNone ( pendingTermination )
940+ ? yield * Effect . uninterruptibleMask ( ( restore ) =>
941+ restore ( Effect . suspend ( ( ) => logic . run ( context ) ) ) . pipe (
942+ Effect . exit ,
943+ Effect . flatMap ( ( exit ) =>
944+ Deferred . succeed (
945+ termination ,
956946 Exit . isFailure ( exit )
957- ? reserveFailureSnapshot ( exit . cause ) . pipe (
958- Effect . flatMap ( ( snapshot ) =>
959- snapshot === undefined
960- ? awaitArbitration
961- : completeArbitration ( { _tag : "Failure" , snapshot, cause : exit . cause } )
962- )
963- )
964- : reserveSuccessSnapshot ( exit . value ) . pipe (
965- Effect . flatMap ( ( snapshot ) =>
966- snapshot === undefined
967- ? awaitArbitration
968- : completeArbitration ( { _tag : "Done" , snapshot, output : exit . value } )
969- )
970- )
947+ ? { _tag : "Failure" , cause : exit . cause }
948+ : { _tag : "Done" , output : exit . value }
971949 )
972950 )
973- return Effect . raceFirst ( workerContender , stopContender ) . pipe (
974- Effect . flatMap ( ( termination ) => {
975- switch ( termination . _tag ) {
976- case "Stopped" :
977- return terminalizeReservedStop ( termination . snapshot )
978- case "Done" :
979- return terminalizeReservedSuccess ( termination . snapshot , termination . output )
980- case "Failure" :
981- return terminalizeReservedFailure ( termination . snapshot , termination . cause )
982- }
983- } )
984- )
985- } )
986- )
987- )
951+ )
952+ ) . pipe ( forkRuntime )
953+ : undefined
954+
955+ // One Deferred arbitrates all terminal causes. The supervisor reserves the
956+ // terminal snapshot before interrupting the worker, so worker finalizers
957+ // cannot mutate the frozen state. It then waits for those finalizers before
958+ // publishing and completing `join` / `stop`.
959+ const runFiber : Effect . Effect < void , never , Requirements > = Effect . uninterruptibleMask ( ( restore ) =>
960+ Effect . gen ( function * ( ) {
961+ const requested = Option . isSome ( pendingTermination )
962+ ? yield * pendingTermination . value
963+ : yield * restore ( Deferred . await ( termination ) )
988964
989- yield * runFiber . pipe (
990- ( effect ) =>
991- options . fiberScope !== undefined ?
992- Effect . forkIn ( effect , options . fiberScope )
993- : options . detached === true ?
994- Effect . forkDetach ( effect )
995- : Effect . forkChild ( effect )
965+ const snapshot = yield * reserveTermination ( requested )
966+ if ( worker !== undefined ) {
967+ yield * Fiber . interrupt ( worker )
968+ }
969+ if ( snapshot === undefined ) {
970+ return yield * awaitCompletion
971+ }
972+ return yield * completeTermination ( requested , snapshot )
973+ } )
996974 )
975+
976+ yield * forkRuntime ( runFiber )
997977 yield * Effect . yieldNow
998978
999979 return ref
0 commit comments