@graftland/plugin-remote 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/dist/Auth.d.ts +2 -2
  2. package/dist/Auth.d.ts.map +1 -1
  3. package/dist/Auth.js.map +1 -1
  4. package/dist/{Bootstrap-MCwivGN1.js → Bootstrap-H9gDsgsC.js} +2 -2
  5. package/dist/{Contract-CcpSIJm4.js → Contract-DsJtisvL.js} +8 -46
  6. package/dist/Contract.d.ts +50 -53
  7. package/dist/Contract.d.ts.map +1 -1
  8. package/dist/Contract.js +1 -1
  9. package/dist/Contract.js.map +1 -1
  10. package/dist/{Errors-oPsTLY7l.js → Errors-DYhsGZcZ.js} +14 -1
  11. package/dist/Errors.d.ts +19 -0
  12. package/dist/Errors.d.ts.map +1 -1
  13. package/dist/Errors.js +14 -1
  14. package/dist/Errors.js.map +1 -1
  15. package/dist/{FailureGroup-BcH0XOBi.js → FailureGroup-BgT73AaR.js} +14 -4
  16. package/dist/{Framing-zaYdOJnT.js → Framing-G9eSaeLb.js} +1 -1
  17. package/dist/{Graft-BsYq3F-J.js → Graft-B71CPA2Y.js} +46 -35
  18. package/dist/{Packages-B1TPk8pa.js → Packages-AgoCGotL.js} +23 -26
  19. package/dist/{Peer-IJgFK6mD.js → Peer-DXeVrGBh.js} +5 -5
  20. package/dist/{ProcessObservation-DEGrEMfP.js → ProcessObservation-gLgGirYv.js} +1 -1
  21. package/dist/{ServeLifecycle-CJM0gG5B.js → ServeLifecycle-Bd1FPJ6e.js} +1 -1
  22. package/dist/Testing.d.ts +4 -4
  23. package/dist/Testing.d.ts.map +1 -1
  24. package/dist/Testing.js +10 -4
  25. package/dist/Testing.js.map +1 -1
  26. package/dist/Transport.d.ts +2 -2
  27. package/dist/Transport.d.ts.map +1 -1
  28. package/dist/Transport.js.map +1 -1
  29. package/dist/bin.js +48 -47
  30. package/dist/bin.js.map +1 -1
  31. package/dist/{childRuntime-DSqtFN9o.js → childRuntime-Bm3iSg_N.js} +30 -26
  32. package/dist/connectionEnded-BwcxQ7W8.js +54 -0
  33. package/dist/{entry-SYsx9VNb.js → entry-psOpX3Bj.js} +76 -48
  34. package/dist/host/auth/bearer.d.ts +1 -2
  35. package/dist/host/auth/bearer.d.ts.map +1 -1
  36. package/dist/host/auth/bearer.js +1 -1
  37. package/dist/host/auth/bearer.js.map +1 -1
  38. package/dist/host/auth/jwt.d.ts +1 -2
  39. package/dist/host/auth/jwt.d.ts.map +1 -1
  40. package/dist/host/auth/jwt.js +1 -1
  41. package/dist/host/auth/jwt.js.map +1 -1
  42. package/dist/host/auth/mtls.js +2 -2
  43. package/dist/host/auth/none.js +1 -1
  44. package/dist/host/command.d.ts +1 -1
  45. package/dist/host/command.d.ts.map +1 -1
  46. package/dist/host/command.js +4 -4
  47. package/dist/host/command.js.map +1 -1
  48. package/dist/host/local.d.ts +1 -2
  49. package/dist/host/local.d.ts.map +1 -1
  50. package/dist/host/local.js +3 -3
  51. package/dist/host/local.js.map +1 -1
  52. package/dist/host/unix.d.ts +1 -1
  53. package/dist/host/unix.d.ts.map +1 -1
  54. package/dist/host/unix.js +3 -3
  55. package/dist/host/unix.js.map +1 -1
  56. package/dist/host/websocket.d.ts +1 -1
  57. package/dist/host/websocket.d.ts.map +1 -1
  58. package/dist/host/websocket.js +3 -3
  59. package/dist/host/websocket.js.map +1 -1
  60. package/dist/host/worker.d.ts +1 -2
  61. package/dist/host/worker.d.ts.map +1 -1
  62. package/dist/host/worker.js +4 -5
  63. package/dist/host/worker.js.map +1 -1
  64. package/dist/{importer-D7zSgqxq.js → importer-Cvf88eAY.js} +5 -8
  65. package/dist/index.d.ts +2 -3
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +10 -10
  68. package/dist/index.js.map +1 -1
  69. package/dist/internal/Certificates.js +4 -4
  70. package/dist/internal/Certificates.js.map +1 -1
  71. package/dist/internal/FailureGroup.js +3 -2
  72. package/dist/internal/FailureGroup.js.map +1 -1
  73. package/dist/internal/assembly.js +9 -2
  74. package/dist/internal/assembly.js.map +1 -1
  75. package/dist/internal/auth/mtls.js +1 -1
  76. package/dist/internal/auth/mtls.js.map +1 -1
  77. package/dist/internal/auth/tokenCredentials.d.ts +0 -1
  78. package/dist/internal/auth/tokenCredentials.d.ts.map +1 -1
  79. package/dist/internal/auth/tokenCredentials.js.map +1 -1
  80. package/dist/internal/child.js +20 -17
  81. package/dist/internal/child.js.map +1 -1
  82. package/dist/internal/childRuntime.js +6 -4
  83. package/dist/internal/childRuntime.js.map +1 -1
  84. package/dist/internal/contract.js +26 -0
  85. package/dist/internal/contract.js.map +1 -0
  86. package/dist/internal/credentials.d.ts +6 -6
  87. package/dist/internal/credentials.d.ts.map +1 -1
  88. package/dist/internal/credentials.js.map +1 -1
  89. package/dist/internal/directTransport.d.ts +1 -1
  90. package/dist/internal/entry.d.ts +1 -2
  91. package/dist/internal/entry.d.ts.map +1 -1
  92. package/dist/internal/entry.js +4 -14
  93. package/dist/internal/entry.js.map +1 -1
  94. package/dist/internal/execution.js +44 -7
  95. package/dist/internal/execution.js.map +1 -1
  96. package/dist/internal/host/runner.js +7 -6
  97. package/dist/internal/host/runner.js.map +1 -1
  98. package/dist/internal/host.js +5 -5
  99. package/dist/internal/host.js.map +1 -1
  100. package/dist/internal/port.d.ts +2 -10
  101. package/dist/internal/port.js +5 -5
  102. package/dist/internal/port.js.map +1 -1
  103. package/dist/internal/release.js +1 -1
  104. package/dist/internal/release.js.map +1 -1
  105. package/dist/internal/runner/Connection.d.ts +1 -1
  106. package/dist/internal/runner/Connection.js +4 -4
  107. package/dist/internal/runner/Connection.js.map +1 -1
  108. package/dist/internal/runner/Execution.js +3 -3
  109. package/dist/internal/runner/Execution.js.map +1 -1
  110. package/dist/internal/runner/Framing.d.ts +1 -11
  111. package/dist/internal/runner/Framing.js +1 -1
  112. package/dist/internal/runner/Framing.js.map +1 -1
  113. package/dist/internal/runner/Local.js +1 -1
  114. package/dist/internal/runner/Local.js.map +1 -1
  115. package/dist/internal/runner/Lock.js +1 -1
  116. package/dist/internal/runner/Lock.js.map +1 -1
  117. package/dist/internal/runner/Packages.d.ts +1 -1
  118. package/dist/internal/runner/Packages.js +22 -25
  119. package/dist/internal/runner/Packages.js.map +1 -1
  120. package/dist/internal/runner/Processes.js +7 -9
  121. package/dist/internal/runner/Processes.js.map +1 -1
  122. package/dist/internal/runner/Store.js +3 -3
  123. package/dist/internal/runner/Store.js.map +1 -1
  124. package/dist/internal/runner/Supervisor.js +46 -47
  125. package/dist/internal/runner/Supervisor.js.map +1 -1
  126. package/dist/internal/runner/accept.js +3 -3
  127. package/dist/internal/runner/accept.js.map +1 -1
  128. package/dist/internal/runner/model.js +1 -1
  129. package/dist/internal/runner/model.js.map +1 -1
  130. package/dist/internal/serve.js +1 -1
  131. package/dist/internal/serve.js.map +1 -1
  132. package/dist/internal/services.d.ts +2 -12
  133. package/dist/internal/services.d.ts.map +1 -1
  134. package/dist/internal/services.js.map +1 -1
  135. package/dist/internal/supervisorPlugin.js +1 -1
  136. package/dist/internal/watchdogMain.js +5 -5
  137. package/dist/internal/watchdogMain.js.map +1 -1
  138. package/dist/internal/websocketServer.js +3 -1
  139. package/dist/internal/websocketServer.js.map +1 -1
  140. package/dist/internal/workerMain.js +3 -3
  141. package/dist/{port-Blbh8tms.js → port--3x2zJ4D.js} +8 -8
  142. package/dist/{release-BY4Mlluy.js → release-DfZPuIcK.js} +1 -1
  143. package/dist/runner/auth/bearer.js +2 -2
  144. package/dist/runner/auth/jwt.js +4 -4
  145. package/dist/runner/auth/jwt.js.map +1 -1
  146. package/dist/runner/auth/mtls.js +3 -3
  147. package/dist/runner/auth/none.js +1 -1
  148. package/dist/runner/index.js +5 -5
  149. package/dist/runner/stdio.js +3 -3
  150. package/dist/runner/unix.js +3 -3
  151. package/dist/runner/websocket.js +4 -4
  152. package/dist/{socketChannel-KmtzF-9G.js → socketChannel-CJvL2DiB.js} +2 -2
  153. package/dist/{supervisorPlugin-Inb1S5IV.js → supervisorPlugin-t6Z6PrhD.js} +72 -76
  154. package/dist/{token-v5ne2I0-.js → token-CYBNNlhS.js} +1 -1
  155. package/dist/{tokenCredentials-WUJm2AKm.js → tokenCredentials-CbCuk-Rt.js} +3 -3
  156. package/dist/{websocketServer-MXcCJx0v.js → websocketServer-NQhODlEt.js} +5 -3
  157. package/dist/{workerChannel-P82YdNjh.js → workerChannel-C8hhBJzA.js} +1 -1
  158. package/package.json +3 -3
  159. package/src/Auth.ts +9 -2
  160. package/src/Contract.ts +67 -51
  161. package/src/Errors.ts +23 -0
  162. package/src/Testing.ts +16 -2
  163. package/src/Transport.ts +6 -2
  164. package/src/bin.ts +28 -30
  165. package/src/host/auth/bearer.ts +1 -6
  166. package/src/host/auth/jwt.ts +1 -6
  167. package/src/host/command.ts +2 -0
  168. package/src/host/local.ts +5 -9
  169. package/src/host/unix.ts +2 -0
  170. package/src/host/websocket.ts +2 -0
  171. package/src/host/worker.ts +4 -6
  172. package/src/index.ts +8 -4
  173. package/src/internal/Certificates.ts +10 -13
  174. package/src/internal/FailureGroup.ts +128 -121
  175. package/src/internal/assembly.ts +19 -10
  176. package/src/internal/auth/mtls.ts +19 -20
  177. package/src/internal/auth/tokenCredentials.ts +1 -7
  178. package/src/internal/child.ts +249 -240
  179. package/src/internal/childRuntime.ts +84 -81
  180. package/src/internal/contract.ts +47 -0
  181. package/src/internal/credentials.ts +12 -9
  182. package/src/internal/entry.ts +11 -25
  183. package/src/internal/execution.ts +73 -14
  184. package/src/internal/host/runner.ts +153 -153
  185. package/src/internal/host.ts +123 -130
  186. package/src/internal/port.ts +100 -103
  187. package/src/internal/release.ts +3 -2
  188. package/src/internal/runner/Connection.ts +52 -60
  189. package/src/internal/runner/Execution.ts +163 -164
  190. package/src/internal/runner/Framing.ts +9 -10
  191. package/src/internal/runner/Local.ts +26 -27
  192. package/src/internal/runner/Lock.ts +23 -25
  193. package/src/internal/runner/Packages.ts +67 -70
  194. package/src/internal/runner/Processes.ts +89 -92
  195. package/src/internal/runner/Store.ts +110 -114
  196. package/src/internal/runner/Supervisor.ts +392 -405
  197. package/src/internal/runner/accept.ts +76 -84
  198. package/src/internal/runner/model.ts +20 -21
  199. package/src/internal/serve.ts +15 -13
  200. package/src/internal/services.ts +1 -19
  201. package/src/internal/watchdogMain.ts +16 -11
  202. package/src/internal/websocketServer.ts +18 -3
  203. package/src/runner/auth/jwt.ts +10 -6
  204. package/dist/cleanup-BPWr-5U9.js +0 -13
  205. package/dist/connectionEnded-DUgcobEd.js +0 -9
  206. package/dist/internal/childErrors.d.ts +0 -33
  207. package/dist/internal/childErrors.d.ts.map +0 -1
  208. package/dist/internal/port.d.ts.map +0 -1
  209. package/dist/internal/runner/Framing.d.ts.map +0 -1
@@ -291,88 +291,83 @@ const exitCause = <A, E>(exit: Exit.Exit<A, E>) =>
291
291
  Exit.isFailure(exit) ? exit.cause : Cause.empty
292
292
 
293
293
  /** A signal is a request; only the exited effect confirms that the execution has stopped. */
294
- const terminate = (
294
+ const terminate = Effect.fnUntraced(function* (
295
295
  execution: ExecutionState,
296
296
  grace: number,
297
297
  confirmation: number,
298
- ): Effect.Effect<Termination, TerminationUnconfirmed> => {
299
- if (execution._tag === "Absent") return Effect.succeed(emptyTermination)
298
+ ): Effect.fn.Return<Termination, TerminationUnconfirmed> {
299
+ if (execution._tag === "Absent") return emptyTermination
300
300
  if (execution._tag === "Exited")
301
- return Effect.succeed({ ...emptyTermination, exit: execution.exit })
301
+ return { ...emptyTermination, exit: execution.exit }
302
302
  const child = execution.unit
303
- return Effect.gen(function* () {
304
- const graceful = yield* child.interrupt.pipe(
305
- Effect.andThen(child.exited),
306
- Effect.timeoutOption(grace),
307
- Effect.exit,
308
- )
309
- if (Exit.isSuccess(graceful) && Option.isSome(graceful.value))
310
- return { ...emptyTermination, exit: graceful.value.value }
311
- const killed = yield* Effect.exit(child.kill)
312
- const forced = yield* child.exited.pipe(
313
- Effect.timeoutOption(confirmation),
314
- Effect.exit,
315
- )
316
- const cause = Cause.combine(
317
- Cause.combine(exitCause(graceful), exitCause(killed)),
318
- exitCause(forced),
319
- )
320
- if (Exit.isFailure(forced) || Option.isNone(forced.value))
321
- return yield* TerminationUnconfirmed.make({
322
- detail: "The child exit could not be confirmed",
323
- cause,
324
- })
325
- return {
326
- kind: "Killed",
327
- exit: forced.value.value,
303
+ const graceful = yield* child.interrupt.pipe(
304
+ Effect.andThen(child.exited),
305
+ Effect.timeoutOption(grace),
306
+ Effect.exit,
307
+ )
308
+ if (Exit.isSuccess(graceful) && Option.isSome(graceful.value))
309
+ return { ...emptyTermination, exit: graceful.value.value }
310
+ const killed = yield* Effect.exit(child.kill)
311
+ const forced = yield* child.exited.pipe(
312
+ Effect.timeoutOption(confirmation),
313
+ Effect.exit,
314
+ )
315
+ const cause = Cause.combine(
316
+ Cause.combine(exitCause(graceful), exitCause(killed)),
317
+ exitCause(forced),
318
+ )
319
+ if (Exit.isFailure(forced) || Option.isNone(forced.value))
320
+ return yield* TerminationUnconfirmed.make({
321
+ detail: "The child exit could not be confirmed",
328
322
  cause,
329
- } satisfies Termination
330
- })
331
- }
323
+ })
324
+ return {
325
+ kind: "Killed",
326
+ exit: forced.value.value,
327
+ cause,
328
+ } satisfies Termination
329
+ })
332
330
 
333
- const initialize = (options: Options = {}) =>
334
- Effect.gen(function* () {
335
- const store = yield* Store
336
- const execution = yield* Execution
337
- const random = yield* SecureRandom.SecureRandom
338
- const parent = yield* Effect.scope
339
- const owner = yield* Scope.fork(parent)
340
- // Cleanup must continue after a health failure so every registered process is reaped.
341
- const workers = yield* FiberSet.make<void, never>().pipe(
342
- Scope.provide(owner),
343
- )
344
- const locks = yield* RcMap.make({
345
- lookup: (_key: SessionKey) => Semaphore.make(1),
346
- }).pipe(Scope.provide(owner))
347
- const lease = Duration.toMillis(options.lease ?? "15 seconds")
348
- const grace = Duration.toMillis(options.grace ?? "5 seconds")
349
- const confirmation = Duration.toMillis(
350
- options.confirmationTimeout ?? "5 seconds",
351
- )
352
- const limits = options.limits ?? defaults
353
- const records = yield* Ref.make(HashMap.empty<string, SessionRecord>())
354
- const entries = yield* Ref.make(HashMap.empty<string, Entry>())
355
- const health = yield* FailureGroup.make<SupervisorError>().pipe(
356
- Scope.provide(owner),
357
- )
358
- const closing = yield* Ref.make(false)
359
- return {
360
- store,
361
- execution,
362
- random,
363
- owner,
364
- workers,
365
- locks,
366
- lease,
367
- grace,
368
- confirmation,
369
- limits,
370
- records,
371
- entries,
372
- health,
373
- closing,
374
- }
375
- })
331
+ const initialize = Effect.fnUntraced(function* (options: Options = {}) {
332
+ const store = yield* Store
333
+ const execution = yield* Execution
334
+ const random = yield* SecureRandom.SecureRandom
335
+ const parent = yield* Effect.scope
336
+ const owner = yield* Scope.fork(parent)
337
+ // Cleanup must continue after a health failure so every registered process is reaped.
338
+ const workers = yield* FiberSet.make<void, never>().pipe(Scope.provide(owner))
339
+ const locks = yield* RcMap.make({
340
+ lookup: (_key: SessionKey) => Semaphore.make(1),
341
+ }).pipe(Scope.provide(owner))
342
+ const lease = Duration.toMillis(options.lease ?? "15 seconds")
343
+ const grace = Duration.toMillis(options.grace ?? "5 seconds")
344
+ const confirmation = Duration.toMillis(
345
+ options.confirmationTimeout ?? "5 seconds",
346
+ )
347
+ const limits = options.limits ?? defaults
348
+ const records = yield* Ref.make(HashMap.empty<string, SessionRecord>())
349
+ const entries = yield* Ref.make(HashMap.empty<string, Entry>())
350
+ const health = yield* FailureGroup.make<SupervisorError>().pipe(
351
+ Scope.provide(owner),
352
+ )
353
+ const closing = yield* Ref.make(false)
354
+ return {
355
+ store,
356
+ execution,
357
+ random,
358
+ owner,
359
+ workers,
360
+ locks,
361
+ lease,
362
+ grace,
363
+ confirmation,
364
+ limits,
365
+ records,
366
+ entries,
367
+ health,
368
+ closing,
369
+ }
370
+ })
376
371
  type Runtime = Effect.Success<ReturnType<typeof initialize>>
377
372
  const operations = (runtime: Runtime) => {
378
373
  const {
@@ -435,82 +430,78 @@ const operations = (runtime: Runtime) => {
435
430
  ),
436
431
  ),
437
432
  )
438
- const entryFor = (handle: Handle) =>
439
- Effect.gen(function* () {
440
- yield* status(handle)
441
- const all = yield* Ref.get(entries)
442
- const found = HashMap.get(all, handle.token)
443
- if (Option.isNone(found))
444
- return yield* SessionStopped.make({
445
- detail: "The session was retired during recovery",
446
- })
447
- return found.value
448
- })
433
+ const entryFor = Effect.fnUntraced(function* (handle: Handle) {
434
+ yield* status(handle)
435
+ const all = yield* Ref.get(entries)
436
+ const found = HashMap.get(all, handle.token)
437
+ if (Option.isNone(found))
438
+ return yield* SessionStopped.make({
439
+ detail: "The session was retired during recovery",
440
+ })
441
+ return found.value
442
+ })
449
443
  const commit = (
450
444
  entry: Entry,
451
445
  transition: (
452
446
  state: Session,
453
447
  ) => Result.Result<Session, ExecutionError | SessionStopped>,
454
448
  ) =>
455
- SynchronizedRef.updateEffect(entry.state, (state) =>
456
- Effect.gen(function* () {
449
+ SynchronizedRef.updateEffect(
450
+ entry.state,
451
+ Effect.fnUntraced(function* (state: Session) {
457
452
  const next = yield* Effect.fromResult(transition(state))
458
453
  yield* persist(snapshot(next))
459
454
  return next
460
455
  }),
461
456
  )
462
- const finishStop = (
457
+ const finishStop = Effect.fnUntraced(function* (
463
458
  entry: Entry,
464
459
  state: Stopping,
465
460
  saved: Exit.Exit<void, StateStorageError>,
466
- ) =>
467
- Effect.gen(function* () {
468
- yield* FiberHandle.clear(entry.loading)
469
- const result = yield* terminate(state.execution, grace, confirmation)
470
- const closed = yield* Effect.exit(
471
- Release.close(entry.unitScope, Exit.void),
472
- )
473
- const termination: Termination = {
474
- ...result,
475
- cause: Cause.combine(result.cause, exitCause(closed)),
476
- }
477
- const record = stoppedRecord(
478
- state.record.previous,
479
- state.record.reason,
480
- termination,
481
- )
482
- yield* SynchronizedRef.updateEffect(entry.state, () =>
483
- persist(record).pipe(
484
- Effect.as(Session.Stopped({ record, reply: state.reply })),
485
- ),
486
- )
487
- if (Exit.isFailure(saved)) return yield* Effect.failCause(saved.cause)
488
- return termination
489
- })
461
+ ) {
462
+ yield* FiberHandle.clear(entry.loading)
463
+ const result = yield* terminate(state.execution, grace, confirmation)
464
+ const closed = yield* Effect.exit(Release.close(entry.unitScope, Exit.void))
465
+ const termination: Termination = {
466
+ ...result,
467
+ cause: Cause.combine(result.cause, exitCause(closed)),
468
+ }
469
+ const record = stoppedRecord(
470
+ state.record.previous,
471
+ state.record.reason,
472
+ termination,
473
+ )
474
+ yield* SynchronizedRef.updateEffect(entry.state, () =>
475
+ persist(record).pipe(
476
+ Effect.as(Session.Stopped({ record, reply: state.reply })),
477
+ ),
478
+ )
479
+ if (Exit.isFailure(saved)) return yield* Effect.failCause(saved.cause)
480
+ return termination
481
+ })
490
482
  /** Called with the session ref locked; the worker starts after its transition is published. */
491
- const stopLocked = (
483
+ const stopLocked = Effect.fnUntraced(function* (
492
484
  entry: Entry,
493
485
  state: Session,
494
486
  reason: StopReason,
495
487
  exit: Option.Option<ExitStatus>,
496
- ): Effect.Effect<readonly [StopReply, Session]> =>
497
- Effect.gen(function* () {
498
- if (state._tag === "Stopping" || state._tag === "Stopped")
499
- return [state.reply, state]
500
- const reply = yield* Deferred.make<Termination, SupervisorError>()
501
- const next = beginStop(state, reason, reply, exit)
502
- // Fence in memory even when the journal cannot acknowledge the transition.
503
- const saved = yield* Effect.exit(persist(next.record))
504
- yield* FiberSet.run(
505
- workers,
506
- finishStop(entry, next, saved).pipe(
507
- Effect.onExit((result) => Deferred.done(reply, result)),
508
- observe,
509
- ),
510
- { startImmediately: false },
511
- )
512
- return [reply, next]
513
- })
488
+ ): Effect.fn.Return<readonly [StopReply, Session]> {
489
+ if (state._tag === "Stopping" || state._tag === "Stopped")
490
+ return [state.reply, state] as const
491
+ const reply = yield* Deferred.make<Termination, SupervisorError>()
492
+ const next = beginStop(state, reason, reply, exit)
493
+ // Fence in memory even when the journal cannot acknowledge the transition.
494
+ const saved = yield* Effect.exit(persist(next.record))
495
+ yield* FiberSet.run(
496
+ workers,
497
+ finishStop(entry, next, saved).pipe(
498
+ Effect.onExit((result) => Deferred.done(reply, result)),
499
+ observe,
500
+ ),
501
+ { startImmediately: false },
502
+ )
503
+ return [reply, next] as const
504
+ })
514
505
  const stop = (
515
506
  entry: Entry,
516
507
  reason: StopReason,
@@ -547,8 +538,9 @@ const operations = (runtime: Runtime) => {
547
538
  { startImmediately: false },
548
539
  )
549
540
  const register = (entry: Entry, unit: Unit) =>
550
- SynchronizedRef.updateEffect(entry.state, (state) =>
551
- Effect.gen(function* () {
541
+ SynchronizedRef.updateEffect(
542
+ entry.state,
543
+ Effect.fnUntraced(function* (state: Session) {
552
544
  const now = yield* Clock.monotonicTimeNanos
553
545
  const current = yield* Effect.fromResult(live(state, now))
554
546
  const next = yield* Effect.fromResult(registerUnit(current, unit))
@@ -582,8 +574,10 @@ const operations = (runtime: Runtime) => {
582
574
  ),
583
575
  ),
584
576
  Effect.tap((code) => commit(entry, (current) => loaded(current, code))),
585
- Effect.onExit((result) =>
586
- Effect.gen(function* () {
577
+ Effect.onExit(
578
+ Effect.fnUntraced(function* (
579
+ result: Exit.Exit<CodeIdentity, SupervisorError>,
580
+ ) {
587
581
  if (Exit.isSuccess(result)) {
588
582
  yield* Deferred.done(state.reply, result)
589
583
  return
@@ -615,287 +609,280 @@ const operations = (runtime: Runtime) => {
615
609
  }
616
610
  }
617
611
  type Operations = ReturnType<typeof operations>
618
- const recover = (runtime: Runtime, actions: Operations) =>
619
- Effect.gen(function* () {
620
- const { store, execution, confirmation, records } = runtime
621
- const { persist } = actions
622
- // Recover before the service is published. Construction does not resume old leases.
623
- const stored = yield* store.list
624
- yield* Ref.set(
625
- records,
626
- HashMap.fromIterable(
627
- Array.map(stored, (record) => [record.handle.token, record] as const),
612
+ const recover = Effect.fnUntraced(function* (
613
+ runtime: Runtime,
614
+ actions: Operations,
615
+ ) {
616
+ const { store, execution, confirmation, records } = runtime
617
+ const { persist } = actions
618
+ // Recover before the service is published. Construction does not resume old leases.
619
+ const stored = yield* store.list
620
+ yield* Ref.set(
621
+ records,
622
+ HashMap.fromIterable(
623
+ Array.map(stored, (record) => [record.handle.token, record] as const),
624
+ ),
625
+ )
626
+ for (const record of stored) {
627
+ if (record.phase === "Stopped") continue
628
+ const child = childOf(record)
629
+ const termination = Option.isNone(child)
630
+ ? emptyTermination
631
+ : yield* execution.recover(child.value).pipe(
632
+ Effect.timeout(confirmation),
633
+ Effect.catchTags({
634
+ ExecutionError: (error) =>
635
+ Effect.fail(
636
+ TerminationUnconfirmed.make({
637
+ detail: "Orphan recovery did not confirm exit",
638
+ cause: Cause.fail(error),
639
+ }),
640
+ ),
641
+ TimeoutError: (error) =>
642
+ Effect.fail(
643
+ TerminationUnconfirmed.make({
644
+ detail: "Orphan recovery did not confirm exit",
645
+ cause: Cause.fail(error),
646
+ }),
647
+ ),
648
+ }),
649
+ )
650
+ yield* persist(
651
+ stoppedRecord(
652
+ record.phase === "Stopping" ? record.previous : record,
653
+ { _tag: "Recovery" },
654
+ termination,
628
655
  ),
629
656
  )
630
- for (const record of stored) {
631
- if (record.phase === "Stopped") continue
632
- const child = childOf(record)
633
- const termination = Option.isNone(child)
634
- ? emptyTermination
635
- : yield* execution.recover(child.value).pipe(
636
- Effect.timeout(confirmation),
637
- Effect.catchTags({
638
- ExecutionError: (error) =>
639
- Effect.fail(
640
- TerminationUnconfirmed.make({
641
- detail: "Orphan recovery did not confirm exit",
642
- cause: Cause.fail(error),
643
- }),
644
- ),
645
- TimeoutError: (error) =>
646
- Effect.fail(
647
- TerminationUnconfirmed.make({
648
- detail: "Orphan recovery did not confirm exit",
649
- cause: Cause.fail(error),
650
- }),
651
- ),
652
- }),
653
- )
654
- yield* persist(
655
- stoppedRecord(
656
- record.phase === "Stopping" ? record.previous : record,
657
- { _tag: "Recovery" },
658
- termination,
657
+ }
658
+ })
659
+ const ownShutdown = Effect.fnUntraced(function* (
660
+ runtime: Runtime,
661
+ actions: Operations,
662
+ ) {
663
+ const { entries, health, closing } = runtime
664
+ const { stop } = actions
665
+ yield* Effect.addFinalizer(
666
+ Effect.fnUntraced(function* () {
667
+ yield* Ref.set(closing, true)
668
+ const all = yield* Ref.get(entries)
669
+ const exits = yield* Effect.forEach(HashMap.values(all), (entry) =>
670
+ stop(entry, { _tag: "Shutdown" }).pipe(
671
+ Effect.flatMap(Deferred.await),
672
+ Effect.exit,
659
673
  ),
660
674
  )
661
- }
662
- })
663
- const ownShutdown = (runtime: Runtime, actions: Operations) =>
664
- Effect.gen(function* () {
665
- const { entries, health, closing } = runtime
666
- const { stop } = actions
667
- yield* Effect.addFinalizer(() =>
668
- Effect.gen(function* () {
669
- yield* Ref.set(closing, true)
670
- const all = yield* Ref.get(entries)
671
- const exits = yield* Effect.forEach(HashMap.values(all), (entry) =>
672
- stop(entry, { _tag: "Shutdown" }).pipe(
673
- Effect.flatMap(Deferred.await),
674
- Effect.exit,
675
- ),
675
+ const initial: Cause.Cause<unknown> = yield* health.cause
676
+ const cause = Array.reduce(exits, initial, (cause, exit) =>
677
+ Cause.combine(
678
+ cause,
679
+ Exit.isFailure(exit) ? exit.cause : exit.value.cause,
680
+ ),
681
+ )
682
+ if (cause.reasons.length > 0)
683
+ return yield* Effect.failCause(cleanupCause(cause))
684
+ }),
685
+ )
686
+ })
687
+ const protocol = Effect.fnUntraced(function* (
688
+ runtime: Runtime,
689
+ actions: Operations,
690
+ ) {
691
+ const { random, owner, locks, lease, limits, records, entries, health } =
692
+ runtime
693
+ const {
694
+ persist,
695
+ status,
696
+ latest,
697
+ entryFor,
698
+ stop,
699
+ scheduleLease,
700
+ runLoad,
701
+ healthy,
702
+ } = actions
703
+ return Supervisor.of({
704
+ await: health.await,
705
+ status,
706
+ highWater: (key) =>
707
+ latest(key).pipe(Effect.map((value) => (value === 0 ? null : value))),
708
+ open: Effect.fnUntraced(function* (request: Open) {
709
+ yield* healthy
710
+ const leaseMillis = Duration.toMillis(request.lease ?? lease)
711
+ const negotiated = negotiate(limits, request.limits ?? defaults)
712
+ const lock = yield* RcMap.get(locks, keyOf(request.key))
713
+ return yield* Effect.gen(function* () {
714
+ yield* healthy
715
+ const allRecords = yield* Ref.get(records)
716
+ const same = Array.filter(
717
+ Array.fromIterable(HashMap.values(allRecords)),
718
+ (row) => Equal.equals(keyOf(row.handle.key), keyOf(request.key)),
676
719
  )
677
- const initial: Cause.Cause<unknown> = yield* health.cause
678
- const cause = Array.reduce(exits, initial, (cause, exit) =>
679
- Cause.combine(
680
- cause,
681
- Exit.isFailure(exit) ? exit.cause : exit.value.cause,
682
- ),
720
+ const prior = Array.findFirst(
721
+ same,
722
+ (row) => row.requestId === request.requestId,
683
723
  )
684
- if (cause.reasons.length > 0)
685
- return yield* Effect.failCause(cleanupCause(cause))
686
- }),
687
- )
688
- })
689
- const protocol = (runtime: Runtime, actions: Operations) =>
690
- Effect.gen(function* () {
691
- const { random, owner, locks, lease, limits, records, entries, health } =
692
- runtime
693
- const {
694
- persist,
695
- status,
696
- latest,
697
- entryFor,
698
- stop,
699
- scheduleLease,
700
- runLoad,
701
- healthy,
702
- } = actions
703
- return Supervisor.of({
704
- await: health.await,
705
- status,
706
- highWater: (key) =>
707
- latest(key).pipe(Effect.map((value) => (value === 0 ? null : value))),
708
- open: (request) =>
709
- Effect.scoped(
724
+ if (Option.isSome(prior)) return prior.value
725
+ const generation = yield* latest(request.key)
726
+ if (generation !== (request.expectedGeneration ?? 0))
727
+ return yield* StaleSession.make({
728
+ detail:
729
+ "Open expectedGeneration does not match the generation high-water mark",
730
+ })
731
+ for (const record of same) {
732
+ const allEntries = yield* Ref.get(entries)
733
+ const entry = HashMap.get(allEntries, record.handle.token)
734
+ if (Option.isSome(entry))
735
+ yield* stop(entry.value, { _tag: "Superseded" }).pipe(
736
+ Effect.flatMap(Deferred.await),
737
+ Effect.tap((result) =>
738
+ result.cause.reasons.length === 0
739
+ ? Effect.void
740
+ : Effect.logWarning(
741
+ "Superseded remote execution cleanup failed",
742
+ result.cause,
743
+ ).pipe(
744
+ Effect.annotateLogs({
745
+ "remote.identity": record.handle.key.identity,
746
+ "remote.instance": record.handle.key.instance,
747
+ "remote.generation": record.handle.generation,
748
+ }),
749
+ ),
750
+ ),
751
+ )
752
+ }
753
+ yield* healthy
754
+ const token = yield* random.uuidV4()
755
+ const record: typeof OpenRecord.Type = {
756
+ version: 1,
757
+ phase: "Open",
758
+ handle: {
759
+ key: request.key,
760
+ token,
761
+ generation: generation + 1,
762
+ },
763
+ requestId: request.requestId,
764
+ leaseMillis,
765
+ limits: negotiated,
766
+ }
767
+ return yield* Effect.uninterruptible(
710
768
  Effect.gen(function* () {
711
- yield* healthy
712
- const leaseMillis = Duration.toMillis(request.lease ?? lease)
713
- const negotiated = negotiate(limits, request.limits ?? defaults)
714
- const lock = yield* RcMap.get(locks, keyOf(request.key))
715
- return yield* lock.withPermit(
716
- Effect.gen(function* () {
717
- yield* healthy
718
- const allRecords = yield* Ref.get(records)
719
- const same = Array.filter(
720
- Array.fromIterable(HashMap.values(allRecords)),
721
- (row) =>
722
- Equal.equals(keyOf(row.handle.key), keyOf(request.key)),
723
- )
724
- const prior = Array.findFirst(
725
- same,
726
- (row) => row.requestId === request.requestId,
727
- )
728
- if (Option.isSome(prior)) return prior.value
729
- const generation = yield* latest(request.key)
730
- if (generation !== (request.expectedGeneration ?? 0))
731
- return yield* StaleSession.make({
732
- detail:
733
- "Open expectedGeneration does not match the generation high-water mark",
734
- })
735
- for (const record of same) {
736
- const allEntries = yield* Ref.get(entries)
737
- const entry = HashMap.get(allEntries, record.handle.token)
738
- if (Option.isSome(entry))
739
- yield* stop(entry.value, { _tag: "Superseded" }).pipe(
740
- Effect.flatMap(Deferred.await),
741
- Effect.tap((result) =>
742
- result.cause.reasons.length === 0
743
- ? Effect.void
744
- : Effect.logWarning(
745
- "Superseded remote execution cleanup failed",
746
- result.cause,
747
- ).pipe(
748
- Effect.annotateLogs({
749
- "remote.identity": record.handle.key.identity,
750
- "remote.instance": record.handle.key.instance,
751
- "remote.generation": record.handle.generation,
752
- }),
753
- ),
754
- ),
755
- )
756
- }
757
- yield* healthy
758
- const token = yield* random.uuidV4()
759
- const record: typeof OpenRecord.Type = {
760
- version: 1,
761
- phase: "Open",
762
- handle: {
763
- key: request.key,
769
+ yield* persist(record)
770
+ const deadline =
771
+ (yield* Clock.monotonicTimeNanos) + nanos(leaseMillis)
772
+ const scope = yield* Scope.fork(owner)
773
+ const entry: Entry = {
774
+ state: yield* SynchronizedRef.make<Session>(
775
+ Session.Open({ record, deadline }),
776
+ ),
777
+ scope,
778
+ unitScope: yield* Scope.fork(scope),
779
+ timer: yield* FiberHandle.make<void, never>().pipe(
780
+ Scope.provide(scope),
781
+ ),
782
+ loading: yield* FiberHandle.make<void, never>().pipe(
783
+ Scope.provide(scope),
784
+ ),
785
+ }
786
+ // The store's retention policy is also the in-memory eviction policy.
787
+ const retained = yield* Ref.get(records)
788
+ const old = yield* Ref.modify(
789
+ entries,
790
+ (all) =>
791
+ [
792
+ Array.filter(
793
+ Array.fromIterable(HashMap.entries(all)),
794
+ ([key]) => !HashMap.has(retained, key),
795
+ ),
796
+ HashMap.set(
797
+ HashMap.filter(all, (_, key) => HashMap.has(retained, key)),
764
798
  token,
765
- generation: generation + 1,
766
- },
767
- requestId: request.requestId,
768
- leaseMillis,
769
- limits: negotiated,
770
- }
771
- return yield* Effect.uninterruptible(
772
- Effect.gen(function* () {
773
- yield* persist(record)
774
- const deadline =
775
- (yield* Clock.monotonicTimeNanos) + nanos(leaseMillis)
776
- const scope = yield* Scope.fork(owner)
777
- const entry: Entry = {
778
- state: yield* SynchronizedRef.make<Session>(
779
- Session.Open({ record, deadline }),
780
- ),
781
- scope,
782
- unitScope: yield* Scope.fork(scope),
783
- timer: yield* FiberHandle.make<void, never>().pipe(
784
- Scope.provide(scope),
785
- ),
786
- loading: yield* FiberHandle.make<void, never>().pipe(
787
- Scope.provide(scope),
788
- ),
789
- }
790
- // The store's retention policy is also the in-memory eviction policy.
791
- const retained = yield* Ref.get(records)
792
- const old = yield* Ref.modify(
793
- entries,
794
- (all) =>
795
- [
796
- Array.filter(
797
- Array.fromIterable(HashMap.entries(all)),
798
- ([key]) => !HashMap.has(retained, key),
799
- ),
800
- HashMap.set(
801
- HashMap.filter(all, (_, key) =>
802
- HashMap.has(retained, key),
803
- ),
804
- token,
805
- entry,
806
- ),
807
- ] as const,
808
- )
809
- for (const [, retired] of old)
810
- yield* Release.close(retired.scope, Exit.void)
811
- yield* scheduleLease(entry, deadline)
812
- return record
813
- }),
814
- )
815
- }),
799
+ entry,
800
+ ),
801
+ ] as const,
816
802
  )
803
+ for (const [, retired] of old)
804
+ yield* Release.close(retired.scope, Exit.void)
805
+ yield* scheduleLease(entry, deadline)
806
+ return record
807
+ }),
808
+ )
809
+ }).pipe(lock.withPermit)
810
+ }, Effect.scoped),
811
+ renew: Effect.fnUntraced(function* (handle: Handle) {
812
+ yield* healthy
813
+ const entry = yield* entryFor(handle)
814
+ // Arming the new timer interrupts the old one, so the new deadline must be stored too.
815
+ // Telling the child may block on its control pipe, so that step stays interruptible:
816
+ // if it is cut short, nothing is stored and the old timer keeps its deadline.
817
+ return yield* Effect.uninterruptibleMask((restore) =>
818
+ SynchronizedRef.modifyEffect(
819
+ entry.state,
820
+ Effect.fnUntraced(function* (state: Session) {
821
+ const now = yield* Clock.monotonicTimeNanos
822
+ const current = yield* Effect.fromResult(live(state, now))
823
+ const running = executionOf(current)
824
+ if (running._tag === "Running")
825
+ yield* restore(running.unit.renew(current.record.leaseMillis))
826
+ const deadline = now + nanos(current.record.leaseMillis)
827
+ yield* scheduleLease(entry, deadline)
828
+ return [
829
+ current.record.leaseMillis,
830
+ { ...current, deadline },
831
+ ] as const
817
832
  }),
818
833
  ),
819
- renew: (handle) =>
820
- Effect.gen(function* () {
821
- yield* healthy
822
- const entry = yield* entryFor(handle)
823
- return yield* SynchronizedRef.modifyEffect(entry.state, (state) =>
824
- Effect.gen(function* () {
825
- const now = yield* Clock.monotonicTimeNanos
826
- const current = yield* Effect.fromResult(live(state, now))
827
- const running = executionOf(current)
828
- if (running._tag === "Running")
829
- yield* running.unit.renew(current.record.leaseMillis)
830
- const deadline = now + nanos(current.record.leaseMillis)
831
- yield* scheduleLease(entry, deadline)
832
- return [
833
- current.record.leaseMillis,
834
- { ...current, deadline },
835
- ] as const
836
- }),
837
- )
838
- }),
839
- load: (handle, request) =>
840
- Effect.gen(function* () {
841
- yield* healthy
842
- const entry = yield* entryFor(handle)
843
- const reply = yield* SynchronizedRef.modifyEffect(
844
- entry.state,
845
- (state) =>
846
- Effect.gen(function* () {
847
- const now = yield* Clock.monotonicTimeNanos
848
- const current = yield* Effect.fromResult(live(state, now))
849
- if (current._tag !== "Open") {
850
- const before =
851
- current._tag === "Loading"
852
- ? current.request
853
- : current.record.load
854
- if (before.specifier !== request.specifier)
855
- return yield* SessionConflict.make({
856
- detail:
857
- "Load was already requested with a different specifier",
858
- })
859
- return [
860
- current._tag === "Loading"
861
- ? Deferred.await(current.reply)
862
- : Effect.succeed(current.record.code),
863
- current,
864
- ] as const
865
- }
866
- const done = yield* Deferred.make<
867
- CodeIdentity,
868
- SupervisorError
869
- >()
870
- const next = beginLoad(current, request, done)
871
- yield* persist(snapshot(next))
872
- if (next._tag === "Loading")
873
- yield* FiberHandle.run(entry.loading, runLoad(entry, next), {
874
- startImmediately: false,
875
- })
876
- return [Deferred.await(done), next] as const
877
- }),
878
- ).pipe(Effect.uninterruptible)
879
- return yield* reply
880
- }),
881
- dispose: (handle) =>
882
- Effect.gen(function* () {
883
- const record = yield* status(handle)
884
- if (record.phase === "Stopped") return record.termination
885
- const entry = yield* entryFor(handle)
886
- return yield* stop(entry, { _tag: "Disposed" }).pipe(
887
- Effect.flatMap(Deferred.await),
888
- )
834
+ )
835
+ }),
836
+ load: Effect.fnUntraced(function* (handle: Handle, request: Load) {
837
+ yield* healthy
838
+ const entry = yield* entryFor(handle)
839
+ const reply = yield* SynchronizedRef.modifyEffect(
840
+ entry.state,
841
+ Effect.fnUntraced(function* (state: Session) {
842
+ const now = yield* Clock.monotonicTimeNanos
843
+ const current = yield* Effect.fromResult(live(state, now))
844
+ if (current._tag !== "Open") {
845
+ const before =
846
+ current._tag === "Loading" ? current.request : current.record.load
847
+ if (before.specifier !== request.specifier)
848
+ return yield* SessionConflict.make({
849
+ detail: "Load was already requested with a different specifier",
850
+ })
851
+ return [
852
+ current._tag === "Loading"
853
+ ? Deferred.await(current.reply)
854
+ : Effect.succeed(current.record.code),
855
+ current,
856
+ ] as const
857
+ }
858
+ const done = yield* Deferred.make<CodeIdentity, SupervisorError>()
859
+ const next = beginLoad(current, request, done)
860
+ yield* persist(snapshot(next))
861
+ if (next._tag === "Loading")
862
+ yield* FiberHandle.run(entry.loading, runLoad(entry, next), {
863
+ startImmediately: false,
864
+ })
865
+ return [Deferred.await(done), next] as const
889
866
  }),
890
- })
891
- })
892
- export const make = (options: Options = {}) =>
893
- Effect.gen(function* () {
894
- const runtime = yield* initialize(options)
895
- const actions = operations(runtime)
896
- yield* recover(runtime, actions)
897
- yield* ownShutdown(runtime, actions)
898
- return yield* protocol(runtime, actions)
867
+ ).pipe(Effect.uninterruptible)
868
+ return yield* reply
869
+ }),
870
+ dispose: Effect.fnUntraced(function* (handle: Handle) {
871
+ const record = yield* status(handle)
872
+ if (record.phase === "Stopped") return record.termination
873
+ const entry = yield* entryFor(handle)
874
+ return yield* stop(entry, { _tag: "Disposed" }).pipe(
875
+ Effect.flatMap(Deferred.await),
876
+ )
877
+ }),
899
878
  })
879
+ })
880
+ export const make = Effect.fnUntraced(function* (options: Options = {}) {
881
+ const runtime = yield* initialize(options)
882
+ const actions = operations(runtime)
883
+ yield* recover(runtime, actions)
884
+ yield* ownShutdown(runtime, actions)
885
+ return yield* protocol(runtime, actions)
886
+ })
900
887
  export const layer = (options: Options = {}) =>
901
888
  Layer.effect(Supervisor, make(options))