@graftland/plugin-remote 0.0.0-stage → 0.3.0
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.
- package/LICENSE +202 -0
- package/README.md +76 -2
- package/README.zh-CN.md +71 -0
- package/README.zh-TW.md +71 -0
- package/dist/Auth-BElTC9_X.js +6 -0
- package/dist/Auth.d.ts +49 -0
- package/dist/Auth.d.ts.map +1 -0
- package/dist/Auth.js +8 -0
- package/dist/Auth.js.map +1 -0
- package/dist/Bootstrap-MCwivGN1.js +9 -0
- package/dist/ConfigDirectory-BCC6q2R4.js +5 -0
- package/dist/Contract-CcpSIJm4.js +121 -0
- package/dist/Contract.d.ts +71 -0
- package/dist/Contract.d.ts.map +1 -0
- package/dist/Contract.js +1 -0
- package/dist/Contract.js.map +1 -0
- package/dist/Errors-oPsTLY7l.js +45 -0
- package/dist/Errors.d.ts +67 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Errors.js +47 -0
- package/dist/Errors.js.map +1 -0
- package/dist/FailureGroup-BcH0XOBi.js +53 -0
- package/dist/Framing-zaYdOJnT.js +61 -0
- package/dist/Graft-BsYq3F-J.js +985 -0
- package/dist/Instance.d.ts +7 -0
- package/dist/Instance.d.ts.map +1 -0
- package/dist/Instance.js +14 -0
- package/dist/Instance.js.map +1 -0
- package/dist/Packages-B1TPk8pa.js +111 -0
- package/dist/Peer-IJgFK6mD.js +342 -0
- package/dist/ProcessObservation-DEGrEMfP.js +38 -0
- package/dist/ServeLifecycle-CJM0gG5B.js +24 -0
- package/dist/Testing.d.ts +26 -0
- package/dist/Testing.d.ts.map +1 -0
- package/dist/Testing.js +69 -0
- package/dist/Testing.js.map +1 -0
- package/dist/Transport-DULzpsgR.js +6 -0
- package/dist/Transport.d.ts +39 -0
- package/dist/Transport.d.ts.map +1 -0
- package/dist/Transport.js +8 -0
- package/dist/Transport.js.map +1 -0
- package/dist/WatchdogProtocol-BZmVxrBP.js +17 -0
- package/dist/_virtual/_rolldown/runtime.js +12 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +28275 -0
- package/dist/bin.js.map +1 -0
- package/dist/childRuntime-DSqtFN9o.js +188 -0
- package/dist/cleanup-BPWr-5U9.js +13 -0
- package/dist/command-9zrHdbwT.js +7 -0
- package/dist/connectionEnded-DUgcobEd.js +9 -0
- package/dist/directTransport-DrexuPSm.js +5 -0
- package/dist/entry-SYsx9VNb.js +708 -0
- package/dist/host/auth/bearer.d.ts +5 -0
- package/dist/host/auth/bearer.d.ts.map +1 -0
- package/dist/host/auth/bearer.js +4 -0
- package/dist/host/auth/bearer.js.map +1 -0
- package/dist/host/auth/jwt.d.ts +5 -0
- package/dist/host/auth/jwt.d.ts.map +1 -0
- package/dist/host/auth/jwt.js +4 -0
- package/dist/host/auth/jwt.js.map +1 -0
- package/dist/host/auth/mtls.d.ts +8 -0
- package/dist/host/auth/mtls.d.ts.map +1 -0
- package/dist/host/auth/mtls.js +34 -0
- package/dist/host/auth/mtls.js.map +1 -0
- package/dist/host/auth/none.d.ts +3 -0
- package/dist/host/auth/none.d.ts.map +1 -0
- package/dist/host/auth/none.js +13 -0
- package/dist/host/auth/none.js.map +1 -0
- package/dist/host/command.d.ts +22 -0
- package/dist/host/command.d.ts.map +1 -0
- package/dist/host/command.js +61 -0
- package/dist/host/command.js.map +1 -0
- package/dist/host/local.d.ts +4 -0
- package/dist/host/local.d.ts.map +1 -0
- package/dist/host/local.js +52 -0
- package/dist/host/local.js.map +1 -0
- package/dist/host/unix.d.ts +11 -0
- package/dist/host/unix.d.ts.map +1 -0
- package/dist/host/unix.js +39 -0
- package/dist/host/unix.js.map +1 -0
- package/dist/host/websocket.d.ts +13 -0
- package/dist/host/websocket.d.ts.map +1 -0
- package/dist/host/websocket.js +36 -0
- package/dist/host/websocket.js.map +1 -0
- package/dist/host/worker.d.ts +4 -0
- package/dist/host/worker.d.ts.map +1 -0
- package/dist/host/worker.js +52 -0
- package/dist/host/worker.js.map +1 -0
- package/dist/importer-D7zSgqxq.js +347 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/Certificates.js +182 -0
- package/dist/internal/Certificates.js.map +1 -0
- package/dist/internal/FailureGroup.js +55 -0
- package/dist/internal/FailureGroup.js.map +1 -0
- package/dist/internal/Limits.d.ts +1 -0
- package/dist/internal/Limits.js +17 -0
- package/dist/internal/Limits.js.map +1 -0
- package/dist/internal/Peer.js +6 -0
- package/dist/internal/Peer.js.map +1 -0
- package/dist/internal/ServeLifecycle.js +26 -0
- package/dist/internal/ServeLifecycle.js.map +1 -0
- package/dist/internal/Supervisor.js +7 -0
- package/dist/internal/Supervisor.js.map +1 -0
- package/dist/internal/assembly.d.ts +5 -0
- package/dist/internal/assembly.js +138 -0
- package/dist/internal/assembly.js.map +1 -0
- package/dist/internal/auth/mtls.js +23 -0
- package/dist/internal/auth/mtls.js.map +1 -0
- package/dist/internal/auth/token.js +19 -0
- package/dist/internal/auth/token.js.map +1 -0
- package/dist/internal/auth/tokenCredentials.d.ts +8 -0
- package/dist/internal/auth/tokenCredentials.d.ts.map +1 -0
- package/dist/internal/auth/tokenCredentials.js +33 -0
- package/dist/internal/auth/tokenCredentials.js.map +1 -0
- package/dist/internal/child.js +125 -0
- package/dist/internal/child.js.map +1 -0
- package/dist/internal/childErrors.d.ts +33 -0
- package/dist/internal/childErrors.d.ts.map +1 -0
- package/dist/internal/childErrors.js +29 -0
- package/dist/internal/childErrors.js.map +1 -0
- package/dist/internal/childRuntime.js +59 -0
- package/dist/internal/childRuntime.js.map +1 -0
- package/dist/internal/cleanup.js +15 -0
- package/dist/internal/cleanup.js.map +1 -0
- package/dist/internal/config.js +6 -0
- package/dist/internal/config.js.map +1 -0
- package/dist/internal/control.js +42 -0
- package/dist/internal/control.js.map +1 -0
- package/dist/internal/credentials.d.ts +44 -0
- package/dist/internal/credentials.d.ts.map +1 -0
- package/dist/internal/credentials.js +54 -0
- package/dist/internal/credentials.js.map +1 -0
- package/dist/internal/directTransport.d.ts +3 -0
- package/dist/internal/directTransport.js +7 -0
- package/dist/internal/directTransport.js.map +1 -0
- package/dist/internal/entry.d.ts +9 -0
- package/dist/internal/entry.d.ts.map +1 -0
- package/dist/internal/entry.js +144 -0
- package/dist/internal/entry.js.map +1 -0
- package/dist/internal/entryConfig.d.ts +202 -0
- package/dist/internal/entryConfig.d.ts.map +1 -0
- package/dist/internal/entryConfig.js +176 -0
- package/dist/internal/entryConfig.js.map +1 -0
- package/dist/internal/execution.d.ts +5 -0
- package/dist/internal/execution.js +43 -0
- package/dist/internal/execution.js.map +1 -0
- package/dist/internal/follow.js +11 -0
- package/dist/internal/follow.js.map +1 -0
- package/dist/internal/forkTree.js +7 -0
- package/dist/internal/forkTree.js.map +1 -0
- package/dist/internal/host/local.js +28 -0
- package/dist/internal/host/local.js.map +1 -0
- package/dist/internal/host/runner.js +100 -0
- package/dist/internal/host/runner.js.map +1 -0
- package/dist/internal/host.js +71 -0
- package/dist/internal/host.js.map +1 -0
- package/dist/internal/importer.js +18 -0
- package/dist/internal/importer.js.map +1 -0
- package/dist/internal/listenerStartup.js +17 -0
- package/dist/internal/listenerStartup.js.map +1 -0
- package/dist/internal/loadModule.js +20 -0
- package/dist/internal/loadModule.js.map +1 -0
- package/dist/internal/platform.js +9 -0
- package/dist/internal/platform.js.map +1 -0
- package/dist/internal/port.d.ts +13 -0
- package/dist/internal/port.d.ts.map +1 -0
- package/dist/internal/port.js +275 -0
- package/dist/internal/port.js.map +1 -0
- package/dist/internal/release.js +25 -0
- package/dist/internal/release.js.map +1 -0
- package/dist/internal/runner/Bootstrap.js +11 -0
- package/dist/internal/runner/Bootstrap.js.map +1 -0
- package/dist/internal/runner/Connection.d.ts +11 -0
- package/dist/internal/runner/Connection.js +56 -0
- package/dist/internal/runner/Connection.js.map +1 -0
- package/dist/internal/runner/Execution.js +115 -0
- package/dist/internal/runner/Execution.js.map +1 -0
- package/dist/internal/runner/Framing.d.ts +11 -0
- package/dist/internal/runner/Framing.d.ts.map +1 -0
- package/dist/internal/runner/Framing.js +63 -0
- package/dist/internal/runner/Framing.js.map +1 -0
- package/dist/internal/runner/Local.js +46 -0
- package/dist/internal/runner/Local.js.map +1 -0
- package/dist/internal/runner/Lock.js +25 -0
- package/dist/internal/runner/Lock.js.map +1 -0
- package/dist/internal/runner/Multiplex.d.ts +5 -0
- package/dist/internal/runner/Multiplex.js +49 -0
- package/dist/internal/runner/Multiplex.js.map +1 -0
- package/dist/internal/runner/Packages.d.ts +3 -0
- package/dist/internal/runner/Packages.js +108 -0
- package/dist/internal/runner/Packages.js.map +1 -0
- package/dist/internal/runner/ProcessObservation.js +40 -0
- package/dist/internal/runner/ProcessObservation.js.map +1 -0
- package/dist/internal/runner/Processes.js +83 -0
- package/dist/internal/runner/Processes.js.map +1 -0
- package/dist/internal/runner/Retention.js +13 -0
- package/dist/internal/runner/Retention.js.map +1 -0
- package/dist/internal/runner/Store.js +78 -0
- package/dist/internal/runner/Store.js.map +1 -0
- package/dist/internal/runner/Supervisor.js +375 -0
- package/dist/internal/runner/Supervisor.js.map +1 -0
- package/dist/internal/runner/Watchdog.js +47 -0
- package/dist/internal/runner/Watchdog.js.map +1 -0
- package/dist/internal/runner/WatchdogProtocol.js +19 -0
- package/dist/internal/runner/WatchdogProtocol.js.map +1 -0
- package/dist/internal/runner/Wire.d.ts +5 -0
- package/dist/internal/runner/Wire.js +64 -0
- package/dist/internal/runner/Wire.js.map +1 -0
- package/dist/internal/runner/accept.js +57 -0
- package/dist/internal/runner/accept.js.map +1 -0
- package/dist/internal/runner/command.js +9 -0
- package/dist/internal/runner/command.js.map +1 -0
- package/dist/internal/runner/connectionEnded.js +11 -0
- package/dist/internal/runner/connectionEnded.js.map +1 -0
- package/dist/internal/runner/model.d.ts +17 -0
- package/dist/internal/runner/model.d.ts.map +1 -0
- package/dist/internal/runner/model.js +231 -0
- package/dist/internal/runner/model.js.map +1 -0
- package/dist/internal/serve.js +60 -0
- package/dist/internal/serve.js.map +1 -0
- package/dist/internal/services.d.ts +18 -0
- package/dist/internal/services.d.ts.map +1 -0
- package/dist/internal/services.js +22 -0
- package/dist/internal/services.js.map +1 -0
- package/dist/internal/socketChannel.js +24 -0
- package/dist/internal/socketChannel.js.map +1 -0
- package/dist/internal/stdioChannel.js +23 -0
- package/dist/internal/stdioChannel.js.map +1 -0
- package/dist/internal/supervisorPlugin.js +1 -0
- package/dist/internal/supervisorPlugin.js.map +1 -0
- package/dist/internal/transportOptions.d.ts +18 -0
- package/dist/internal/transportOptions.d.ts.map +1 -0
- package/dist/internal/watchdogMain.d.ts +1 -0
- package/dist/internal/watchdogMain.js +55 -0
- package/dist/internal/watchdogMain.js.map +1 -0
- package/dist/internal/websocket.js +46 -0
- package/dist/internal/websocket.js.map +1 -0
- package/dist/internal/websocketServer.js +61 -0
- package/dist/internal/websocketServer.js.map +1 -0
- package/dist/internal/workerBootstrap.js +9 -0
- package/dist/internal/workerBootstrap.js.map +1 -0
- package/dist/internal/workerChannel.js +24 -0
- package/dist/internal/workerChannel.js.map +1 -0
- package/dist/internal/workerMain.d.ts +1 -0
- package/dist/internal/workerMain.js +18 -0
- package/dist/internal/workerMain.js.map +1 -0
- package/dist/port-Blbh8tms.js +285 -0
- package/dist/release-BY4Mlluy.js +23 -0
- package/dist/rolldown-runtime-DYf_mQCB.js +34 -0
- package/dist/runner/auth/bearer.d.ts +7 -0
- package/dist/runner/auth/bearer.d.ts.map +1 -0
- package/dist/runner/auth/bearer.js +42 -0
- package/dist/runner/auth/bearer.js.map +1 -0
- package/dist/runner/auth/jwt.d.ts +19 -0
- package/dist/runner/auth/jwt.d.ts.map +1 -0
- package/dist/runner/auth/jwt.js +65 -0
- package/dist/runner/auth/jwt.js.map +1 -0
- package/dist/runner/auth/mtls.d.ts +11 -0
- package/dist/runner/auth/mtls.d.ts.map +1 -0
- package/dist/runner/auth/mtls.js +48 -0
- package/dist/runner/auth/mtls.js.map +1 -0
- package/dist/runner/auth/none.d.ts +3 -0
- package/dist/runner/auth/none.d.ts.map +1 -0
- package/dist/runner/auth/none.js +13 -0
- package/dist/runner/auth/none.js.map +1 -0
- package/dist/runner/index.d.ts +3 -0
- package/dist/runner/index.d.ts.map +1 -0
- package/dist/runner/index.js +71 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/stdio.d.ts +7 -0
- package/dist/runner/stdio.d.ts.map +1 -0
- package/dist/runner/stdio.js +39 -0
- package/dist/runner/stdio.js.map +1 -0
- package/dist/runner/unix.d.ts +7 -0
- package/dist/runner/unix.d.ts.map +1 -0
- package/dist/runner/unix.js +42 -0
- package/dist/runner/unix.js.map +1 -0
- package/dist/runner/websocket.d.ts +28 -0
- package/dist/runner/websocket.d.ts.map +1 -0
- package/dist/runner/websocket.js +77 -0
- package/dist/runner/websocket.js.map +1 -0
- package/dist/socketChannel-KmtzF-9G.js +3658 -0
- package/dist/supervisorPlugin-Inb1S5IV.js +737 -0
- package/dist/token-v5ne2I0-.js +17 -0
- package/dist/tokenCredentials-WUJm2AKm.js +31 -0
- package/dist/websocketServer-MXcCJx0v.js +58 -0
- package/dist/workerChannel-P82YdNjh.js +27 -0
- package/package.json +69 -3
- package/src/Auth.ts +62 -0
- package/src/Contract.ts +100 -0
- package/src/Errors.ts +82 -0
- package/src/Instance.ts +12 -0
- package/src/Testing.ts +135 -0
- package/src/Transport.ts +52 -0
- package/src/bin.ts +145 -0
- package/src/host/auth/bearer.ts +10 -0
- package/src/host/auth/jwt.ts +10 -0
- package/src/host/auth/mtls.ts +48 -0
- package/src/host/auth/none.ts +13 -0
- package/src/host/command.ts +131 -0
- package/src/host/local.ts +41 -0
- package/src/host/unix.ts +65 -0
- package/src/host/websocket.ts +66 -0
- package/src/host/worker.ts +90 -0
- package/src/index.ts +115 -0
- package/src/internal/Certificates.ts +330 -0
- package/src/internal/FailureGroup.ts +176 -0
- package/src/internal/Limits.ts +12 -0
- package/src/internal/Peer.ts +4 -0
- package/src/internal/ServeLifecycle.ts +53 -0
- package/src/internal/Supervisor.ts +14 -0
- package/src/internal/assembly.ts +337 -0
- package/src/internal/auth/mtls.ts +40 -0
- package/src/internal/auth/token.ts +26 -0
- package/src/internal/auth/tokenCredentials.ts +65 -0
- package/src/internal/child.ts +323 -0
- package/src/internal/childErrors.ts +44 -0
- package/src/internal/childRuntime.ts +111 -0
- package/src/internal/cleanup.ts +34 -0
- package/src/internal/config.ts +8 -0
- package/src/internal/control.ts +49 -0
- package/src/internal/credentials.ts +112 -0
- package/src/internal/directTransport.ts +38 -0
- package/src/internal/entry.ts +288 -0
- package/src/internal/entryConfig.ts +190 -0
- package/src/internal/execution.ts +121 -0
- package/src/internal/follow.ts +17 -0
- package/src/internal/forkTree.ts +13 -0
- package/src/internal/host/local.ts +53 -0
- package/src/internal/host/runner.ts +187 -0
- package/src/internal/host.ts +209 -0
- package/src/internal/importer.ts +27 -0
- package/src/internal/listenerStartup.ts +55 -0
- package/src/internal/loadModule.ts +23 -0
- package/src/internal/platform.ts +12 -0
- package/src/internal/port.ts +623 -0
- package/src/internal/release.ts +71 -0
- package/src/internal/runner/Bootstrap.ts +9 -0
- package/src/internal/runner/Connection.ts +184 -0
- package/src/internal/runner/Execution.ts +244 -0
- package/src/internal/runner/Framing.ts +122 -0
- package/src/internal/runner/Local.ts +73 -0
- package/src/internal/runner/Lock.ts +29 -0
- package/src/internal/runner/Multiplex.ts +120 -0
- package/src/internal/runner/Packages.ts +178 -0
- package/src/internal/runner/ProcessObservation.ts +63 -0
- package/src/internal/runner/Processes.ts +109 -0
- package/src/internal/runner/Retention.ts +49 -0
- package/src/internal/runner/Store.ts +160 -0
- package/src/internal/runner/Supervisor.ts +901 -0
- package/src/internal/runner/Watchdog.ts +83 -0
- package/src/internal/runner/WatchdogProtocol.ts +12 -0
- package/src/internal/runner/Wire.ts +99 -0
- package/src/internal/runner/accept.ts +103 -0
- package/src/internal/runner/command.ts +15 -0
- package/src/internal/runner/connectionEnded.ts +28 -0
- package/src/internal/runner/model.ts +364 -0
- package/src/internal/serve.ts +117 -0
- package/src/internal/services.ts +74 -0
- package/src/internal/socketChannel.ts +64 -0
- package/src/internal/stdioChannel.ts +25 -0
- package/src/internal/supervisorPlugin.ts +67 -0
- package/src/internal/transportOptions.ts +17 -0
- package/src/internal/watchdogMain.ts +72 -0
- package/src/internal/websocket.ts +81 -0
- package/src/internal/websocketServer.ts +152 -0
- package/src/internal/workerBootstrap.ts +6 -0
- package/src/internal/workerChannel.ts +54 -0
- package/src/internal/workerMain.ts +23 -0
- package/src/runner/auth/bearer.ts +66 -0
- package/src/runner/auth/jwt.ts +114 -0
- package/src/runner/auth/mtls.ts +37 -0
- package/src/runner/auth/none.ts +13 -0
- package/src/runner/index.ts +97 -0
- package/src/runner/stdio.ts +76 -0
- package/src/runner/unix.ts +53 -0
- package/src/runner/websocket.ts +143 -0
|
@@ -0,0 +1,901 @@
|
|
|
1
|
+
import * as FailureGroup from "../FailureGroup"
|
|
2
|
+
import {
|
|
3
|
+
Array,
|
|
4
|
+
Cause,
|
|
5
|
+
Clock,
|
|
6
|
+
Context,
|
|
7
|
+
Data,
|
|
8
|
+
Deferred,
|
|
9
|
+
Duration,
|
|
10
|
+
Effect,
|
|
11
|
+
Equal,
|
|
12
|
+
Exit,
|
|
13
|
+
FiberHandle,
|
|
14
|
+
FiberSet,
|
|
15
|
+
HashMap,
|
|
16
|
+
Layer,
|
|
17
|
+
Option,
|
|
18
|
+
RcMap,
|
|
19
|
+
Ref,
|
|
20
|
+
Result,
|
|
21
|
+
Scope,
|
|
22
|
+
Semaphore,
|
|
23
|
+
SynchronizedRef,
|
|
24
|
+
} from "effect"
|
|
25
|
+
import * as Release from "../release"
|
|
26
|
+
import { SecureRandom } from "effect/crypto"
|
|
27
|
+
import * as Retention from "./Retention"
|
|
28
|
+
import { cleanupCause } from "../cleanup"
|
|
29
|
+
import {
|
|
30
|
+
Execution,
|
|
31
|
+
Store,
|
|
32
|
+
StaleSession,
|
|
33
|
+
LeaseExpired,
|
|
34
|
+
SessionStopped,
|
|
35
|
+
SessionConflict,
|
|
36
|
+
StateStorageError,
|
|
37
|
+
ExecutionError,
|
|
38
|
+
PackageError,
|
|
39
|
+
TerminationUnconfirmed,
|
|
40
|
+
RunnerClosing,
|
|
41
|
+
childOf,
|
|
42
|
+
defaults,
|
|
43
|
+
keyOf,
|
|
44
|
+
negotiate,
|
|
45
|
+
stoppingRecord,
|
|
46
|
+
stoppedRecord,
|
|
47
|
+
type ActiveRecord,
|
|
48
|
+
type CodeIdentity,
|
|
49
|
+
type ExitStatus,
|
|
50
|
+
type Handle,
|
|
51
|
+
type Key,
|
|
52
|
+
type Limits,
|
|
53
|
+
type Load,
|
|
54
|
+
type SessionRecord,
|
|
55
|
+
type SessionKey,
|
|
56
|
+
type StopReason,
|
|
57
|
+
type Termination,
|
|
58
|
+
type Unit,
|
|
59
|
+
type OpenRecord,
|
|
60
|
+
type LoadedRecord,
|
|
61
|
+
type StoppingRecord,
|
|
62
|
+
type StoppedRecord,
|
|
63
|
+
} from "./model"
|
|
64
|
+
|
|
65
|
+
export type SupervisorError =
|
|
66
|
+
| StaleSession
|
|
67
|
+
| LeaseExpired
|
|
68
|
+
| SessionStopped
|
|
69
|
+
| SessionConflict
|
|
70
|
+
| StateStorageError
|
|
71
|
+
| ExecutionError
|
|
72
|
+
| PackageError
|
|
73
|
+
| TerminationUnconfirmed
|
|
74
|
+
| RunnerClosing
|
|
75
|
+
export interface Options {
|
|
76
|
+
readonly lease?: Duration.Input
|
|
77
|
+
readonly grace?: Duration.Input
|
|
78
|
+
readonly confirmationTimeout?: Duration.Input
|
|
79
|
+
readonly limits?: Limits
|
|
80
|
+
}
|
|
81
|
+
export interface Open {
|
|
82
|
+
readonly key: Key
|
|
83
|
+
readonly requestId: string
|
|
84
|
+
readonly expectedGeneration?: number | null
|
|
85
|
+
readonly lease?: Duration.Input
|
|
86
|
+
readonly limits?: Limits
|
|
87
|
+
}
|
|
88
|
+
export class Supervisor extends Context.Service<
|
|
89
|
+
Supervisor,
|
|
90
|
+
{
|
|
91
|
+
readonly await: Effect.Effect<never, SupervisorError>
|
|
92
|
+
readonly status: (
|
|
93
|
+
handle: Handle,
|
|
94
|
+
) => Effect.Effect<SessionRecord, StaleSession>
|
|
95
|
+
readonly highWater: (key: Key) => Effect.Effect<number | null>
|
|
96
|
+
readonly open: (
|
|
97
|
+
request: Open,
|
|
98
|
+
) => Effect.Effect<SessionRecord, SupervisorError>
|
|
99
|
+
readonly renew: (handle: Handle) => Effect.Effect<number, SupervisorError>
|
|
100
|
+
readonly load: (
|
|
101
|
+
handle: Handle,
|
|
102
|
+
request: Load,
|
|
103
|
+
) => Effect.Effect<CodeIdentity, SupervisorError>
|
|
104
|
+
readonly dispose: (
|
|
105
|
+
handle: Handle,
|
|
106
|
+
) => Effect.Effect<Termination, SupervisorError>
|
|
107
|
+
}
|
|
108
|
+
>()("@graftland/plugin-remote/internal/runner/Supervisor") {}
|
|
109
|
+
|
|
110
|
+
type Launch = Data.TaggedEnum<{
|
|
111
|
+
Starting: {}
|
|
112
|
+
Registered: { readonly unit: Unit }
|
|
113
|
+
}>
|
|
114
|
+
const Launch = Data.taggedEnum<Launch>()
|
|
115
|
+
type ExecutionState = Data.TaggedEnum<{
|
|
116
|
+
Absent: {}
|
|
117
|
+
Running: { readonly unit: Unit }
|
|
118
|
+
Exited: { readonly exit: ExitStatus }
|
|
119
|
+
}>
|
|
120
|
+
const ExecutionState = Data.taggedEnum<ExecutionState>()
|
|
121
|
+
type LoadReply = Deferred.Deferred<CodeIdentity, SupervisorError>
|
|
122
|
+
type StopReply = Deferred.Deferred<Termination, SupervisorError>
|
|
123
|
+
type Session = Data.TaggedEnum<{
|
|
124
|
+
Open: { readonly record: typeof OpenRecord.Type; readonly deadline: bigint }
|
|
125
|
+
Loading: {
|
|
126
|
+
readonly record: typeof OpenRecord.Type
|
|
127
|
+
readonly request: Load
|
|
128
|
+
readonly launch: Launch
|
|
129
|
+
readonly reply: LoadReply
|
|
130
|
+
readonly deadline: bigint
|
|
131
|
+
}
|
|
132
|
+
Loaded: {
|
|
133
|
+
readonly record: typeof LoadedRecord.Type
|
|
134
|
+
readonly unit: Unit
|
|
135
|
+
readonly deadline: bigint
|
|
136
|
+
}
|
|
137
|
+
Stopping: {
|
|
138
|
+
readonly record: typeof StoppingRecord.Type
|
|
139
|
+
readonly execution: ExecutionState
|
|
140
|
+
readonly reply: StopReply
|
|
141
|
+
}
|
|
142
|
+
Stopped: {
|
|
143
|
+
readonly record: typeof StoppedRecord.Type
|
|
144
|
+
readonly reply: StopReply
|
|
145
|
+
}
|
|
146
|
+
}>
|
|
147
|
+
const Session = Data.taggedEnum<Session>()
|
|
148
|
+
type Live = Extract<Session, { readonly _tag: "Open" | "Loading" | "Loaded" }>
|
|
149
|
+
type Loading = Extract<Session, { readonly _tag: "Loading" }>
|
|
150
|
+
type Stopping = Extract<Session, { readonly _tag: "Stopping" }>
|
|
151
|
+
interface Entry {
|
|
152
|
+
readonly state: SynchronizedRef.SynchronizedRef<Session>
|
|
153
|
+
readonly timer: FiberHandle.FiberHandle<void, never>
|
|
154
|
+
readonly loading: FiberHandle.FiberHandle<void, never>
|
|
155
|
+
readonly scope: Scope.Closeable
|
|
156
|
+
readonly unitScope: Scope.Closeable
|
|
157
|
+
}
|
|
158
|
+
function snapshot(state: Live): ActiveRecord
|
|
159
|
+
function snapshot(state: Session): SessionRecord
|
|
160
|
+
function snapshot(state: Session): SessionRecord {
|
|
161
|
+
return state._tag === "Loading"
|
|
162
|
+
? {
|
|
163
|
+
...state.record,
|
|
164
|
+
phase: "Loading",
|
|
165
|
+
load: state.request,
|
|
166
|
+
launch:
|
|
167
|
+
state.launch._tag === "Starting"
|
|
168
|
+
? { _tag: "Starting" }
|
|
169
|
+
: { _tag: "Registered", child: state.launch.unit.identity },
|
|
170
|
+
}
|
|
171
|
+
: state.record
|
|
172
|
+
}
|
|
173
|
+
const live = (
|
|
174
|
+
state: Session,
|
|
175
|
+
now: bigint,
|
|
176
|
+
): Result.Result<Live, LeaseExpired | SessionStopped> => {
|
|
177
|
+
if (state._tag === "Stopping" || state._tag === "Stopped")
|
|
178
|
+
return Result.fail(
|
|
179
|
+
state.record.reason._tag === "Expired"
|
|
180
|
+
? LeaseExpired.make({
|
|
181
|
+
detail: "The session lease has expired",
|
|
182
|
+
})
|
|
183
|
+
: SessionStopped.make({
|
|
184
|
+
detail: "The session is stopping or stopped",
|
|
185
|
+
}),
|
|
186
|
+
)
|
|
187
|
+
return now >= state.deadline
|
|
188
|
+
? Result.fail(
|
|
189
|
+
LeaseExpired.make({
|
|
190
|
+
detail: "The session lease has expired",
|
|
191
|
+
}),
|
|
192
|
+
)
|
|
193
|
+
: Result.succeed(state)
|
|
194
|
+
}
|
|
195
|
+
const executionOf = (state: Live): ExecutionState => {
|
|
196
|
+
switch (state._tag) {
|
|
197
|
+
case "Open":
|
|
198
|
+
return ExecutionState.Absent()
|
|
199
|
+
case "Loading":
|
|
200
|
+
return state.launch._tag === "Registered"
|
|
201
|
+
? ExecutionState.Running({ unit: state.launch.unit })
|
|
202
|
+
: ExecutionState.Absent()
|
|
203
|
+
case "Loaded":
|
|
204
|
+
return ExecutionState.Running({ unit: state.unit })
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** All state transitions are pure; the caller serializes persistence and publication. */
|
|
208
|
+
const beginLoad = (
|
|
209
|
+
state: Extract<Session, { _tag: "Open" }>,
|
|
210
|
+
request: Load,
|
|
211
|
+
reply: LoadReply,
|
|
212
|
+
): Loading =>
|
|
213
|
+
Session.Loading({
|
|
214
|
+
record: state.record,
|
|
215
|
+
deadline: state.deadline,
|
|
216
|
+
request,
|
|
217
|
+
reply,
|
|
218
|
+
launch: Launch.Starting(),
|
|
219
|
+
})
|
|
220
|
+
const registerUnit = (
|
|
221
|
+
state: Session,
|
|
222
|
+
unit: Unit,
|
|
223
|
+
): Result.Result<Session, SessionStopped | ExecutionError> => {
|
|
224
|
+
if (state._tag !== "Loading")
|
|
225
|
+
return Result.fail(
|
|
226
|
+
SessionStopped.make({
|
|
227
|
+
detail: "The session is stopping or stopped",
|
|
228
|
+
}),
|
|
229
|
+
)
|
|
230
|
+
if (state.launch._tag === "Registered")
|
|
231
|
+
return Result.fail(
|
|
232
|
+
ExecutionError.make({
|
|
233
|
+
detail: "An execution adapter registered more than one child",
|
|
234
|
+
}),
|
|
235
|
+
)
|
|
236
|
+
return Result.succeed(
|
|
237
|
+
Session.Loading({ ...state, launch: Launch.Registered({ unit }) }),
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
const loaded = (
|
|
241
|
+
state: Session,
|
|
242
|
+
code: CodeIdentity,
|
|
243
|
+
): Result.Result<Session, SessionStopped | ExecutionError> => {
|
|
244
|
+
if (state._tag !== "Loading")
|
|
245
|
+
return Result.fail(
|
|
246
|
+
SessionStopped.make({
|
|
247
|
+
detail: "The session is stopping or stopped",
|
|
248
|
+
}),
|
|
249
|
+
)
|
|
250
|
+
if (state.launch._tag !== "Registered")
|
|
251
|
+
return Result.fail(
|
|
252
|
+
ExecutionError.make({
|
|
253
|
+
detail: "Execution adapter returned without registering its child",
|
|
254
|
+
}),
|
|
255
|
+
)
|
|
256
|
+
return Result.succeed(
|
|
257
|
+
Session.Loaded({
|
|
258
|
+
deadline: state.deadline,
|
|
259
|
+
unit: state.launch.unit,
|
|
260
|
+
record: {
|
|
261
|
+
...state.record,
|
|
262
|
+
phase: "Loaded",
|
|
263
|
+
load: state.request,
|
|
264
|
+
code,
|
|
265
|
+
child: state.launch.unit.identity,
|
|
266
|
+
},
|
|
267
|
+
}),
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
const beginStop = (
|
|
271
|
+
state: Live,
|
|
272
|
+
reason: StopReason,
|
|
273
|
+
reply: StopReply,
|
|
274
|
+
exit: Option.Option<ExitStatus>,
|
|
275
|
+
): Stopping =>
|
|
276
|
+
Session.Stopping({
|
|
277
|
+
record: stoppingRecord(snapshot(state), reason),
|
|
278
|
+
reply,
|
|
279
|
+
execution: Option.match(exit, {
|
|
280
|
+
onNone: () => executionOf(state),
|
|
281
|
+
onSome: (exit) => ExecutionState.Exited({ exit }),
|
|
282
|
+
}),
|
|
283
|
+
})
|
|
284
|
+
const nanos = (millis: number) => BigInt(Math.ceil(millis * 1_000_000))
|
|
285
|
+
const emptyTermination: Termination = {
|
|
286
|
+
kind: "Exited",
|
|
287
|
+
exit: { code: 0, signal: null },
|
|
288
|
+
cause: Cause.empty,
|
|
289
|
+
}
|
|
290
|
+
const exitCause = <A, E>(exit: Exit.Exit<A, E>) =>
|
|
291
|
+
Exit.isFailure(exit) ? exit.cause : Cause.empty
|
|
292
|
+
|
|
293
|
+
/** A signal is a request; only the exited effect confirms that the execution has stopped. */
|
|
294
|
+
const terminate = (
|
|
295
|
+
execution: ExecutionState,
|
|
296
|
+
grace: number,
|
|
297
|
+
confirmation: number,
|
|
298
|
+
): Effect.Effect<Termination, TerminationUnconfirmed> => {
|
|
299
|
+
if (execution._tag === "Absent") return Effect.succeed(emptyTermination)
|
|
300
|
+
if (execution._tag === "Exited")
|
|
301
|
+
return Effect.succeed({ ...emptyTermination, exit: execution.exit })
|
|
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,
|
|
328
|
+
cause,
|
|
329
|
+
} satisfies Termination
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
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
|
+
})
|
|
376
|
+
type Runtime = Effect.Success<ReturnType<typeof initialize>>
|
|
377
|
+
const operations = (runtime: Runtime) => {
|
|
378
|
+
const {
|
|
379
|
+
store,
|
|
380
|
+
execution,
|
|
381
|
+
workers,
|
|
382
|
+
grace,
|
|
383
|
+
confirmation,
|
|
384
|
+
records,
|
|
385
|
+
entries,
|
|
386
|
+
health,
|
|
387
|
+
closing,
|
|
388
|
+
} = runtime
|
|
389
|
+
|
|
390
|
+
const poison = health.failCause
|
|
391
|
+
const observe = <A>(effect: Effect.Effect<A, SupervisorError>) =>
|
|
392
|
+
effect.pipe(Effect.asVoid, Effect.catchCause(poison))
|
|
393
|
+
const healthy = health.check.pipe(
|
|
394
|
+
Effect.andThen(
|
|
395
|
+
Effect.gen(function* () {
|
|
396
|
+
if (yield* Ref.get(closing))
|
|
397
|
+
return yield* RunnerClosing.make({ detail: "The runner is closing" })
|
|
398
|
+
}),
|
|
399
|
+
),
|
|
400
|
+
)
|
|
401
|
+
const persist = (record: SessionRecord) =>
|
|
402
|
+
store
|
|
403
|
+
.save(record)
|
|
404
|
+
.pipe(
|
|
405
|
+
Effect.tapCause(poison),
|
|
406
|
+
Effect.andThen(
|
|
407
|
+
Ref.update(records, (all) => Retention.save(all, record)),
|
|
408
|
+
),
|
|
409
|
+
)
|
|
410
|
+
const status = (handle: Handle) =>
|
|
411
|
+
Ref.get(records).pipe(
|
|
412
|
+
Effect.flatMap((all) => {
|
|
413
|
+
const found = HashMap.get(all, handle.token)
|
|
414
|
+
return Option.isSome(found) &&
|
|
415
|
+
Equal.equals(keyOf(found.value.handle.key), keyOf(handle.key)) &&
|
|
416
|
+
found.value.handle.generation === handle.generation
|
|
417
|
+
? Effect.succeed(found.value)
|
|
418
|
+
: Effect.fail(
|
|
419
|
+
StaleSession.make({
|
|
420
|
+
detail: "Unknown session or generation",
|
|
421
|
+
}),
|
|
422
|
+
)
|
|
423
|
+
}),
|
|
424
|
+
)
|
|
425
|
+
const latest = (key: Key) =>
|
|
426
|
+
Ref.get(records).pipe(
|
|
427
|
+
Effect.map((all) =>
|
|
428
|
+
Array.reduce(
|
|
429
|
+
Array.fromIterable(HashMap.values(all)),
|
|
430
|
+
0,
|
|
431
|
+
(value, record) =>
|
|
432
|
+
Equal.equals(keyOf(record.handle.key), keyOf(key))
|
|
433
|
+
? Math.max(value, record.handle.generation)
|
|
434
|
+
: value,
|
|
435
|
+
),
|
|
436
|
+
),
|
|
437
|
+
)
|
|
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
|
+
})
|
|
449
|
+
const commit = (
|
|
450
|
+
entry: Entry,
|
|
451
|
+
transition: (
|
|
452
|
+
state: Session,
|
|
453
|
+
) => Result.Result<Session, ExecutionError | SessionStopped>,
|
|
454
|
+
) =>
|
|
455
|
+
SynchronizedRef.updateEffect(entry.state, (state) =>
|
|
456
|
+
Effect.gen(function* () {
|
|
457
|
+
const next = yield* Effect.fromResult(transition(state))
|
|
458
|
+
yield* persist(snapshot(next))
|
|
459
|
+
return next
|
|
460
|
+
}),
|
|
461
|
+
)
|
|
462
|
+
const finishStop = (
|
|
463
|
+
entry: Entry,
|
|
464
|
+
state: Stopping,
|
|
465
|
+
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
|
+
})
|
|
490
|
+
/** Called with the session ref locked; the worker starts after its transition is published. */
|
|
491
|
+
const stopLocked = (
|
|
492
|
+
entry: Entry,
|
|
493
|
+
state: Session,
|
|
494
|
+
reason: StopReason,
|
|
495
|
+
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
|
+
})
|
|
514
|
+
const stop = (
|
|
515
|
+
entry: Entry,
|
|
516
|
+
reason: StopReason,
|
|
517
|
+
exit: Option.Option<ExitStatus> = Option.none(),
|
|
518
|
+
) =>
|
|
519
|
+
SynchronizedRef.modifyEffect(entry.state, (state) =>
|
|
520
|
+
stopLocked(entry, state, reason, exit),
|
|
521
|
+
).pipe(Effect.uninterruptible)
|
|
522
|
+
const scheduleLease = (entry: Entry, deadline: bigint) =>
|
|
523
|
+
FiberHandle.run(
|
|
524
|
+
entry.timer,
|
|
525
|
+
observe(
|
|
526
|
+
Effect.gen(function* () {
|
|
527
|
+
const now = yield* Clock.monotonicTimeNanos
|
|
528
|
+
yield* Effect.sleep(
|
|
529
|
+
Duration.nanos(deadline > now ? deadline - now : 0n),
|
|
530
|
+
)
|
|
531
|
+
yield* SynchronizedRef.updateEffect(entry.state, (state) => {
|
|
532
|
+
if (
|
|
533
|
+
state._tag === "Stopping" ||
|
|
534
|
+
state._tag === "Stopped" ||
|
|
535
|
+
state.deadline !== deadline
|
|
536
|
+
)
|
|
537
|
+
return Effect.succeed(state)
|
|
538
|
+
return stopLocked(
|
|
539
|
+
entry,
|
|
540
|
+
state,
|
|
541
|
+
{ _tag: "Expired" },
|
|
542
|
+
Option.none(),
|
|
543
|
+
).pipe(Effect.map(([, next]) => next))
|
|
544
|
+
})
|
|
545
|
+
}),
|
|
546
|
+
),
|
|
547
|
+
{ startImmediately: false },
|
|
548
|
+
)
|
|
549
|
+
const register = (entry: Entry, unit: Unit) =>
|
|
550
|
+
SynchronizedRef.updateEffect(entry.state, (state) =>
|
|
551
|
+
Effect.gen(function* () {
|
|
552
|
+
const now = yield* Clock.monotonicTimeNanos
|
|
553
|
+
const current = yield* Effect.fromResult(live(state, now))
|
|
554
|
+
const next = yield* Effect.fromResult(registerUnit(current, unit))
|
|
555
|
+
yield* unit.renew(
|
|
556
|
+
Number(current.deadline - (yield* Clock.monotonicTimeNanos)) /
|
|
557
|
+
1_000_000,
|
|
558
|
+
)
|
|
559
|
+
yield* persist(snapshot(next))
|
|
560
|
+
yield* FiberSet.run(
|
|
561
|
+
workers,
|
|
562
|
+
observe(
|
|
563
|
+
unit.exited.pipe(
|
|
564
|
+
Effect.flatMap((exit) =>
|
|
565
|
+
stop(entry, { _tag: "Exited" }, Option.some(exit)),
|
|
566
|
+
),
|
|
567
|
+
),
|
|
568
|
+
),
|
|
569
|
+
)
|
|
570
|
+
return next
|
|
571
|
+
}),
|
|
572
|
+
).pipe(Effect.uninterruptible)
|
|
573
|
+
const runLoad = (entry: Entry, state: Loading) =>
|
|
574
|
+
execution
|
|
575
|
+
.spawn(state.record.handle, state.request, state.record.limits)
|
|
576
|
+
.pipe(
|
|
577
|
+
Scope.provide(entry.unitScope),
|
|
578
|
+
Effect.flatMap((unit) =>
|
|
579
|
+
register(entry, unit).pipe(
|
|
580
|
+
Effect.andThen(unit.start),
|
|
581
|
+
Effect.as(unit.code),
|
|
582
|
+
),
|
|
583
|
+
),
|
|
584
|
+
Effect.tap((code) => commit(entry, (current) => loaded(current, code))),
|
|
585
|
+
Effect.onExit((result) =>
|
|
586
|
+
Effect.gen(function* () {
|
|
587
|
+
if (Exit.isSuccess(result)) {
|
|
588
|
+
yield* Deferred.done(state.reply, result)
|
|
589
|
+
return
|
|
590
|
+
}
|
|
591
|
+
const current = yield* SynchronizedRef.get(entry.state)
|
|
592
|
+
if (current._tag === "Loading")
|
|
593
|
+
yield* stop(entry, { _tag: "LoadFailed", cause: result.cause })
|
|
594
|
+
const healthCause = yield* health.cause
|
|
595
|
+
yield* Deferred.failCause(
|
|
596
|
+
state.reply,
|
|
597
|
+
Cause.combine(result.cause, healthCause),
|
|
598
|
+
)
|
|
599
|
+
}),
|
|
600
|
+
),
|
|
601
|
+
// Load's caller observes its complete exit through the reply, including cancellation.
|
|
602
|
+
Effect.exit,
|
|
603
|
+
Effect.asVoid,
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
persist,
|
|
608
|
+
status,
|
|
609
|
+
latest,
|
|
610
|
+
entryFor,
|
|
611
|
+
stop,
|
|
612
|
+
scheduleLease,
|
|
613
|
+
runLoad,
|
|
614
|
+
healthy,
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
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),
|
|
628
|
+
),
|
|
629
|
+
)
|
|
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,
|
|
659
|
+
),
|
|
660
|
+
)
|
|
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
|
+
),
|
|
676
|
+
)
|
|
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
|
+
),
|
|
683
|
+
)
|
|
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(
|
|
710
|
+
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,
|
|
764
|
+
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
|
+
}),
|
|
816
|
+
)
|
|
817
|
+
}),
|
|
818
|
+
),
|
|
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
|
+
)
|
|
889
|
+
}),
|
|
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)
|
|
899
|
+
})
|
|
900
|
+
export const layer = (options: Options = {}) =>
|
|
901
|
+
Layer.effect(Supervisor, make(options))
|