@graftland/plugin-remote 0.3.1 → 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 (147) 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-Dr6sX7el.js → Bootstrap-H9gDsgsC.js} +2 -2
  5. package/dist/{Contract-DL2mHvaT.js → Contract-DsJtisvL.js} +2 -2
  6. package/dist/Contract.js +1 -1
  7. package/dist/{Errors-BMKmu5Y7.js → Errors-DYhsGZcZ.js} +3 -1
  8. package/dist/Errors.d.ts +7 -0
  9. package/dist/Errors.d.ts.map +1 -1
  10. package/dist/Errors.js +3 -1
  11. package/dist/Errors.js.map +1 -1
  12. package/dist/{FailureGroup-BcH0XOBi.js → FailureGroup-BgT73AaR.js} +14 -4
  13. package/dist/{Framing-zaYdOJnT.js → Framing-G9eSaeLb.js} +1 -1
  14. package/dist/{Graft-CVyueNOf.js → Graft-B71CPA2Y.js} +20 -16
  15. package/dist/{Packages-JNL34JpS.js → Packages-AgoCGotL.js} +23 -26
  16. package/dist/{Peer-BT7eAGmh.js → Peer-DXeVrGBh.js} +5 -5
  17. package/dist/{ProcessObservation-DEGrEMfP.js → ProcessObservation-gLgGirYv.js} +1 -1
  18. package/dist/{ServeLifecycle-CJM0gG5B.js → ServeLifecycle-Bd1FPJ6e.js} +1 -1
  19. package/dist/Transport.d.ts +2 -2
  20. package/dist/Transport.d.ts.map +1 -1
  21. package/dist/Transport.js.map +1 -1
  22. package/dist/bin.js +48 -47
  23. package/dist/bin.js.map +1 -1
  24. package/dist/{childRuntime-An0HsIzl.js → childRuntime-Bm3iSg_N.js} +29 -26
  25. package/dist/{connectionEnded-DCpFCjMX.js → connectionEnded-BwcxQ7W8.js} +4 -4
  26. package/dist/{entry-B89sepMU.js → entry-psOpX3Bj.js} +72 -35
  27. package/dist/host/auth/bearer.js +1 -1
  28. package/dist/host/auth/jwt.js +1 -1
  29. package/dist/host/auth/mtls.js +2 -2
  30. package/dist/host/auth/none.js +1 -1
  31. package/dist/host/command.js +4 -4
  32. package/dist/host/local.js +3 -3
  33. package/dist/host/unix.js +3 -3
  34. package/dist/host/websocket.js +3 -3
  35. package/dist/host/worker.js +4 -5
  36. package/dist/{importer-Bk6U_sn4.js → importer-Cvf88eAY.js} +5 -8
  37. package/dist/index.js +9 -9
  38. package/dist/index.js.map +1 -1
  39. package/dist/internal/Certificates.js +4 -4
  40. package/dist/internal/Certificates.js.map +1 -1
  41. package/dist/internal/FailureGroup.js +3 -2
  42. package/dist/internal/FailureGroup.js.map +1 -1
  43. package/dist/internal/assembly.js +9 -2
  44. package/dist/internal/assembly.js.map +1 -1
  45. package/dist/internal/auth/mtls.js +1 -1
  46. package/dist/internal/auth/mtls.js.map +1 -1
  47. package/dist/internal/child.js +20 -17
  48. package/dist/internal/child.js.map +1 -1
  49. package/dist/internal/childRuntime.js +4 -4
  50. package/dist/internal/childRuntime.js.map +1 -1
  51. package/dist/internal/directTransport.d.ts +1 -1
  52. package/dist/internal/entry.d.ts.map +1 -1
  53. package/dist/internal/entry.js +1 -1
  54. package/dist/internal/entry.js.map +1 -1
  55. package/dist/internal/execution.js +40 -5
  56. package/dist/internal/execution.js.map +1 -1
  57. package/dist/internal/host/runner.js +7 -6
  58. package/dist/internal/host/runner.js.map +1 -1
  59. package/dist/internal/host.js +5 -5
  60. package/dist/internal/host.js.map +1 -1
  61. package/dist/internal/port.js +5 -5
  62. package/dist/internal/port.js.map +1 -1
  63. package/dist/internal/release.js +1 -1
  64. package/dist/internal/release.js.map +1 -1
  65. package/dist/internal/runner/Connection.d.ts +1 -1
  66. package/dist/internal/runner/Connection.js +4 -4
  67. package/dist/internal/runner/Connection.js.map +1 -1
  68. package/dist/internal/runner/Execution.js +3 -3
  69. package/dist/internal/runner/Execution.js.map +1 -1
  70. package/dist/internal/runner/Framing.js +1 -1
  71. package/dist/internal/runner/Framing.js.map +1 -1
  72. package/dist/internal/runner/Local.js +1 -1
  73. package/dist/internal/runner/Local.js.map +1 -1
  74. package/dist/internal/runner/Lock.js +1 -1
  75. package/dist/internal/runner/Lock.js.map +1 -1
  76. package/dist/internal/runner/Packages.d.ts +1 -1
  77. package/dist/internal/runner/Packages.js +22 -25
  78. package/dist/internal/runner/Packages.js.map +1 -1
  79. package/dist/internal/runner/Processes.js +7 -9
  80. package/dist/internal/runner/Processes.js.map +1 -1
  81. package/dist/internal/runner/Store.js +3 -3
  82. package/dist/internal/runner/Store.js.map +1 -1
  83. package/dist/internal/runner/Supervisor.js +46 -47
  84. package/dist/internal/runner/Supervisor.js.map +1 -1
  85. package/dist/internal/runner/accept.js +3 -3
  86. package/dist/internal/runner/accept.js.map +1 -1
  87. package/dist/internal/runner/model.js +1 -1
  88. package/dist/internal/runner/model.js.map +1 -1
  89. package/dist/internal/serve.js +1 -1
  90. package/dist/internal/serve.js.map +1 -1
  91. package/dist/internal/supervisorPlugin.js +1 -1
  92. package/dist/internal/watchdogMain.js +5 -5
  93. package/dist/internal/watchdogMain.js.map +1 -1
  94. package/dist/internal/websocketServer.js +3 -1
  95. package/dist/internal/websocketServer.js.map +1 -1
  96. package/dist/internal/workerMain.js +3 -3
  97. package/dist/{port-DSVUY8Bq.js → port--3x2zJ4D.js} +8 -8
  98. package/dist/{release-BY4Mlluy.js → release-DfZPuIcK.js} +1 -1
  99. package/dist/runner/auth/bearer.js +2 -2
  100. package/dist/runner/auth/jwt.js +4 -4
  101. package/dist/runner/auth/jwt.js.map +1 -1
  102. package/dist/runner/auth/mtls.js +3 -3
  103. package/dist/runner/auth/none.js +1 -1
  104. package/dist/runner/index.js +5 -5
  105. package/dist/runner/stdio.js +3 -3
  106. package/dist/runner/unix.js +3 -3
  107. package/dist/runner/websocket.js +4 -4
  108. package/dist/{socketChannel-C_ppdSzz.js → socketChannel-CJvL2DiB.js} +2 -2
  109. package/dist/{supervisorPlugin-CivhM7x5.js → supervisorPlugin-t6Z6PrhD.js} +72 -76
  110. package/dist/{token-Cc_1CTbE.js → token-CYBNNlhS.js} +1 -1
  111. package/dist/{tokenCredentials-CI6mMRRd.js → tokenCredentials-CbCuk-Rt.js} +3 -3
  112. package/dist/{websocketServer-D2Hk7wsA.js → websocketServer-NQhODlEt.js} +5 -3
  113. package/dist/{workerChannel-cmLc13vm.js → workerChannel-C8hhBJzA.js} +1 -1
  114. package/package.json +3 -3
  115. package/src/Auth.ts +9 -2
  116. package/src/Errors.ts +8 -0
  117. package/src/Transport.ts +6 -2
  118. package/src/bin.ts +28 -30
  119. package/src/index.ts +5 -3
  120. package/src/internal/Certificates.ts +10 -13
  121. package/src/internal/FailureGroup.ts +128 -121
  122. package/src/internal/assembly.ts +19 -10
  123. package/src/internal/auth/mtls.ts +19 -20
  124. package/src/internal/child.ts +248 -239
  125. package/src/internal/childRuntime.ts +81 -80
  126. package/src/internal/entry.ts +10 -11
  127. package/src/internal/execution.ts +69 -12
  128. package/src/internal/host/runner.ts +153 -153
  129. package/src/internal/host.ts +116 -120
  130. package/src/internal/port.ts +100 -103
  131. package/src/internal/release.ts +3 -2
  132. package/src/internal/runner/Connection.ts +52 -60
  133. package/src/internal/runner/Execution.ts +163 -164
  134. package/src/internal/runner/Framing.ts +9 -10
  135. package/src/internal/runner/Local.ts +26 -27
  136. package/src/internal/runner/Lock.ts +23 -25
  137. package/src/internal/runner/Packages.ts +67 -70
  138. package/src/internal/runner/Processes.ts +89 -92
  139. package/src/internal/runner/Store.ts +110 -114
  140. package/src/internal/runner/Supervisor.ts +392 -405
  141. package/src/internal/runner/accept.ts +76 -84
  142. package/src/internal/runner/model.ts +20 -21
  143. package/src/internal/serve.ts +15 -13
  144. package/src/internal/watchdogMain.ts +16 -11
  145. package/src/internal/websocketServer.ts +18 -3
  146. package/src/runner/auth/jwt.ts +10 -6
  147. package/dist/cleanup-CXLa1Wd9.js +0 -13
@@ -16,98 +16,99 @@ import { closedNormally } from "./runner/connectionEnded"
16
16
  import { endpoint } from "./control"
17
17
 
18
18
  /** Import a contract in the runner installation; importing this module performs no I/O. */
19
- export const load = (specifier: string) =>
20
- Effect.gen(function* () {
21
- const module = yield* loadModule(specifier)
22
- const contract = fromModule(module)
23
- if (Option.isNone(contract))
24
- return yield* ContractNotFound.make({ module: specifier })
25
- return contract.value
26
- })
19
+ export const load = Effect.fnUntraced(function* (specifier: string) {
20
+ const module = yield* loadModule(specifier)
21
+ const contract = fromModule(module)
22
+ if (Option.isNone(contract))
23
+ return yield* ContractNotFound.make({ module: specifier })
24
+ return contract.value
25
+ })
27
26
 
28
- const connect = (
27
+ const connect = Effect.fnUntraced(function* (
29
28
  contract: PreparedContract,
30
29
  channel: Port.Channel,
31
30
  session: string,
32
31
  bootstrap: Bootstrap | undefined,
33
- ) =>
34
- Effect.gen(function* () {
35
- const mux =
36
- bootstrap === undefined ? undefined : yield* Multiplex.make(channel)
37
- return yield* Port.make(
38
- mux === undefined ? channel : Wire.channel(mux, 1),
39
- {
40
- session,
41
- serve: {
42
- ...Services.endpoints(contract.provides),
43
- [endpoint]: contract.control,
44
- },
45
- call: Services.endpoints(contract.inject),
46
- ...(bootstrap === undefined ? {} : { limits: bootstrap.limits }),
47
- },
48
- )
32
+ ) {
33
+ const mux =
34
+ bootstrap === undefined ? undefined : yield* Multiplex.make(channel)
35
+ return yield* Port.make(mux === undefined ? channel : Wire.channel(mux, 1), {
36
+ session,
37
+ serve: {
38
+ ...Services.endpoints(contract.provides),
39
+ [endpoint]: contract.control,
40
+ },
41
+ call: Services.endpoints(contract.inject),
42
+ ...(bootstrap === undefined ? {} : { limits: bootstrap.limits }),
49
43
  })
44
+ })
50
45
 
51
46
  /** One execution unit; the executable supplies the platform and Graft services. */
52
- export const run = (
47
+ export const run: (
53
48
  descriptor: RunnerContract,
54
49
  channel: Port.Channel,
55
- session = "local",
50
+ session?: string,
56
51
  bootstrap?: Bootstrap,
57
- stop: Effect.Effect<unknown, ExecutionError> = Effect.never,
58
- ): Effect.Effect<void, ExecutionError, Graft.Graft> =>
59
- Effect.scoped(
60
- Effect.gen(function* () {
61
- const contract = prepare(descriptor)
62
- const telemetry = yield* Child.telemetry(contract)
63
- const loggers = yield* Logger.CurrentLoggers
64
- yield* Effect.gen(function* () {
65
- const peer = yield* connect(contract, channel, session, bootstrap)
66
- const instance = yield* Child.lifecycle(
67
- contract,
68
- peer,
69
- bootstrap?.identity ?? { id: session, generation: 1 },
70
- telemetry,
71
- )
72
- const handlers = yield* contract.control.toHandlers({
73
- Load: ({ input }) => instance.load(input),
74
- Dispose: () => instance.dispose,
75
- Events: () => telemetry.eventStream,
76
- Logs: () => telemetry.logStream,
77
- })
78
- yield* RpcServer.make(contract.control, {
79
- disableFatalDefects: true,
80
- }).pipe(
81
- Effect.provideContext(handlers),
82
- Effect.provideService(
83
- RpcServer.Protocol,
84
- peer.serverProtocol(endpoint),
85
- ),
86
- Effect.forkScoped,
87
- )
88
- yield* Effect.raceFirst(
89
- peer.await.pipe(
90
- Effect.catchIf(
91
- (error) => closedNormally(error.cause),
92
- () => Effect.void,
93
- ),
94
- Effect.mapError((error) =>
95
- ExecutionError.make({
96
- detail: "Child transport failed",
97
- cause: error.cause,
98
- }),
99
- ),
100
- ),
101
- stop,
102
- ).pipe(
103
- // Release exports before closing the instance scope, including on interruption.
104
- Effect.ensuring(instance.release),
105
- )
52
+ stop?: Effect.Effect<unknown, ExecutionError>,
53
+ ) => Effect.Effect<void, ExecutionError, Graft.Graft> = Effect.fnUntraced(
54
+ function* (
55
+ descriptor: RunnerContract,
56
+ channel: Port.Channel,
57
+ session: string = "local",
58
+ bootstrap?: Bootstrap,
59
+ stop: Effect.Effect<unknown, ExecutionError> = Effect.never,
60
+ ) {
61
+ const contract = prepare(descriptor)
62
+ const telemetry = yield* Child.telemetry(contract)
63
+ const loggers = yield* Logger.CurrentLoggers
64
+ yield* Effect.gen(function* () {
65
+ const peer = yield* connect(contract, channel, session, bootstrap)
66
+ const instance = yield* Child.lifecycle(
67
+ contract,
68
+ peer,
69
+ bootstrap?.identity ?? { id: session, generation: 1 },
70
+ telemetry,
71
+ )
72
+ const handlers = yield* contract.control.toHandlers({
73
+ Load: ({ input }) => instance.load(input),
74
+ Dispose: () => instance.dispose,
75
+ Events: () => telemetry.eventStream,
76
+ Logs: () => telemetry.logStream,
77
+ })
78
+ yield* RpcServer.make(contract.control, {
79
+ disableFatalDefects: true,
106
80
  }).pipe(
81
+ Effect.provideContext(handlers),
107
82
  Effect.provideService(
108
- Logger.CurrentLoggers,
109
- new Set([...loggers, telemetry.logger]),
83
+ RpcServer.Protocol,
84
+ peer.serverProtocol(endpoint),
110
85
  ),
86
+ Effect.forkScoped,
111
87
  )
112
- }),
113
- )
88
+ yield* Effect.raceFirst(
89
+ peer.await.pipe(
90
+ Effect.catchIf(
91
+ (error) => closedNormally(error.cause),
92
+ () => Effect.void,
93
+ ),
94
+ Effect.mapError((error) =>
95
+ ExecutionError.make({
96
+ detail: "Child transport failed",
97
+ cause: error.cause,
98
+ }),
99
+ ),
100
+ ),
101
+ stop,
102
+ ).pipe(
103
+ // Release exports before closing the instance scope, including on interruption.
104
+ Effect.ensuring(instance.release),
105
+ )
106
+ }).pipe(
107
+ Effect.provideService(
108
+ Logger.CurrentLoggers,
109
+ new Set([...loggers, telemetry.logger]),
110
+ ),
111
+ )
112
+ },
113
+ Effect.scoped,
114
+ )
@@ -41,17 +41,16 @@ export const resolve = Effect.fnUntraced(function* (
41
41
  const directory = path.resolve(Option.getOrElse(source, () => "."))
42
42
  const resolver = yield* Option.match(existing, {
43
43
  onSome: Effect.succeed,
44
- onNone: () =>
45
- Effect.gen(function* () {
46
- const importer = yield* Importer.make(directory)
47
- return yield* ModuleResolver.make({
48
- base: directory,
49
- importer: (specifier) =>
50
- String.startsWith("@graftland/plugin-remote/")(specifier)
51
- ? import(specifier)
52
- : importer(specifier),
53
- })
54
- }),
44
+ onNone: Effect.fnUntraced(function* () {
45
+ const importer = yield* Importer.make(directory)
46
+ return yield* ModuleResolver.make({
47
+ base: directory,
48
+ importer: (specifier) =>
49
+ String.startsWith("@graftland/plugin-remote/")(specifier)
50
+ ? import(specifier)
51
+ : importer(specifier),
52
+ })
53
+ }),
55
54
  })
56
55
  const fail = yield* boundary(field)
57
56
  const plugin = yield* resolver
@@ -1,6 +1,15 @@
1
1
  import { Plugin } from "@graftland/graft"
2
- import { Array, Cause, Effect, Fiber, Schema, Scope } from "effect"
2
+ import {
3
+ Array,
4
+ Cause,
5
+ Effect,
6
+ Fiber,
7
+ PlatformError,
8
+ Schema,
9
+ Scope,
10
+ } from "effect"
3
11
  import { SecureRandom } from "effect/crypto"
12
+ import { RpcClientError } from "effect/rpc"
4
13
  import type {
5
14
  LocalTarget as Local,
6
15
  WorkerTarget as Worker,
@@ -12,7 +21,12 @@ import type { Duration } from "effect"
12
21
  import * as Connection from "./runner/Connection"
13
22
  import * as Services from "./services"
14
23
  import * as Host from "./host"
24
+ import * as Errors from "../Errors"
15
25
  import { RemoteWithdrawn, RemoteError, MissingInstance } from "../Errors"
26
+ import * as ChildErrors from "./childErrors"
27
+ import * as Assembly from "./assembly"
28
+ import * as Model from "./runner/model"
29
+ import { ReleaseInterrupted } from "./release"
16
30
  import * as Contract from "../Contract"
17
31
  import * as Control from "./control"
18
32
  import { prepare } from "./contract"
@@ -64,8 +78,8 @@ export const executionPlugin = <
64
78
  ...Array.map(contract.inject, (service) => service.key),
65
79
  ],
66
80
  config: Schema.toEncoded(contract.config),
67
- apply: (input) =>
68
- Effect.gen(function* () {
81
+ apply: Effect.fnUntraced(
82
+ function* (input: Schema.Json) {
69
83
  const prepared = prepare(contract)
70
84
  const random = yield* SecureRandom.SecureRandom
71
85
  const session = yield* random.uuidV4()
@@ -98,12 +112,57 @@ export const executionPlugin = <
98
112
  ),
99
113
  )
100
114
  return () => Effect.succeed(false)
101
- }).pipe(
102
- Effect.annotateLogs(annotations),
103
- Effect.mapError((error) => publicError(contract.error, error)),
104
- ),
115
+ },
116
+ Effect.annotateLogs(annotations),
117
+ Effect.mapError((error) => publicError(contract.error, error)),
118
+ ),
105
119
  })
106
120
 
121
+ /** Failures raised by the host machinery; a permissive declared schema must not claim them. */
122
+ const internalErrors: ReadonlyArray<abstract new (...args: never) => unknown> =
123
+ [
124
+ Errors.ProcessExited,
125
+ Errors.TransportError,
126
+ Errors.LocalTerminationTimeout,
127
+ Errors.CredentialsError,
128
+ Errors.AuthenticationFailed,
129
+ Errors.RemoteEntryError,
130
+ ChildErrors.AlreadyLoaded,
131
+ ChildErrors.ContractNotFound,
132
+ ChildErrors.ModuleImportFailed,
133
+ ChildErrors.ActivationFailed,
134
+ Model.PackageError,
135
+ Model.SessionAccessDenied,
136
+ Model.StaleSession,
137
+ Model.LeaseExpired,
138
+ Model.SessionStopped,
139
+ Model.SessionConflict,
140
+ Model.StateStorageError,
141
+ Model.ExecutionError,
142
+ Model.TerminationUnconfirmed,
143
+ Model.RunnerClosing,
144
+ Model.StateDirectoryLocked,
145
+ Model.RemoteTerminationUnconfirmed,
146
+ ReleaseInterrupted,
147
+ RpcClientError.RpcClientError,
148
+ ...Assembly.errors,
149
+ ]
150
+ const isInternal = (error: unknown) =>
151
+ Schema.isSchemaError(error) ||
152
+ PlatformError.isPlatformError(error) ||
153
+ Array.some(internalErrors, (Class) => error instanceof Class)
154
+ const isPublic = (
155
+ error: unknown,
156
+ ): error is
157
+ | Contract.UndeclaredFailure
158
+ | RemoteWithdrawn
159
+ | RemoteError
160
+ | MissingInstance =>
161
+ Schema.is(Contract.UndeclaredFailure)(error) ||
162
+ Schema.is(RemoteWithdrawn)(error) ||
163
+ Schema.is(RemoteError)(error) ||
164
+ Schema.is(MissingInstance)(error)
165
+
107
166
  export const publicError = <E extends Schema.Top>(
108
167
  declared: E,
109
168
  error: unknown,
@@ -113,11 +172,9 @@ export const publicError = <E extends Schema.Top>(
113
172
  | RemoteWithdrawn
114
173
  | RemoteError
115
174
  | MissingInstance =>
116
- Schema.is(declared)(error)
175
+ isPublic(error)
117
176
  ? error
118
- : Schema.is(Contract.UndeclaredFailure)(error) ||
119
- Schema.is(RemoteWithdrawn)(error) ||
120
- Schema.is(RemoteError)(error) ||
121
- Schema.is(MissingInstance)(error)
177
+ : // Classify host failures before the declared schema, which may be structural.
178
+ !isInternal(error) && Schema.is(declared)(error)
122
179
  ? error
123
180
  : RemoteError.make({ cause: Cause.fail(error) })
@@ -19,169 +19,169 @@ import * as Multiplex from "../runner/Multiplex"
19
19
  import * as Packages from "../runner/Packages"
20
20
  import * as Wire from "../runner/Wire"
21
21
  import { awaitTermination, defaults, childOf } from "../runner/model"
22
- export const connect = (
22
+ export const connect = Effect.fnUntraced(function* (
23
23
  target: ConfiguredRunner,
24
24
  contractModule: string,
25
25
  options: Port.Options,
26
26
  resources: Scope.Scope,
27
27
  processScope: Scope.Scope,
28
- ) =>
29
- Effect.gen(function* () {
30
- const instance = target.instance
28
+ ) {
29
+ const instance = target.instance
31
30
 
32
- const margin = Duration.toMillis(target.margin ?? "5 seconds")
33
- const channel = yield* connectCapability(target.explicitAuth).pipe(
34
- Scope.provide(processScope),
35
- )
36
- const mux = yield* Multiplex.make(channel).pipe(Scope.provide(resources))
37
- const control = yield* Port.make(Wire.channel(mux, 0), {
38
- session: "session-v1",
39
- serve: {},
40
- call: { session: Wire.Session },
41
- }).pipe(Scope.provide(resources))
42
- const session = yield* RpcClient.make(Wire.Session).pipe(
43
- Effect.provideService(
44
- RpcClient.Protocol,
45
- control.clientProtocol("session"),
46
- ),
47
- Scope.provide(resources),
48
- )
49
- const expectedGeneration = yield* session.HighWater({ instance })
50
- const record = yield* session.Open({
51
- instance,
52
- expectedGeneration,
53
- requestId: options.session,
54
- limits: defaults,
55
- ...(target.lease === undefined
56
- ? {}
57
- : {
58
- leaseMillis: Duration.toMillis(target.lease),
59
- }),
60
- })
61
- const failures = yield* FailureGroup.make<TransportError>().pipe(
62
- Scope.provide(resources),
63
- )
64
- const finishStarted = yield* Ref.make(false)
65
- const terminate = yield* Effect.cached(
66
- Effect.gen(function* () {
67
- yield* Ref.set(finishStarted, true)
68
- yield* failures.close
69
- const result = yield* awaitTermination(
70
- record.handle,
71
- session.Dispose(record.handle),
72
- record.leaseMillis,
73
- margin,
31
+ const margin = Duration.toMillis(target.margin ?? "5 seconds")
32
+ const channel = yield* connectCapability(target.explicitAuth).pipe(
33
+ Scope.provide(processScope),
34
+ )
35
+ const mux = yield* Multiplex.make(channel).pipe(Scope.provide(resources))
36
+ const control = yield* Port.make(Wire.channel(mux, 0), {
37
+ session: "session-v1",
38
+ serve: {},
39
+ call: { session: Wire.Session },
40
+ }).pipe(Scope.provide(resources))
41
+ const session = yield* RpcClient.make(Wire.Session).pipe(
42
+ Effect.provideService(
43
+ RpcClient.Protocol,
44
+ control.clientProtocol("session"),
45
+ ),
46
+ Scope.provide(resources),
47
+ )
48
+ const expectedGeneration = yield* session.HighWater({ instance })
49
+ const record = yield* session.Open({
50
+ instance,
51
+ expectedGeneration,
52
+ requestId: options.session,
53
+ limits: defaults,
54
+ ...(target.lease === undefined
55
+ ? {}
56
+ : {
57
+ leaseMillis: Duration.toMillis(target.lease),
58
+ }),
59
+ })
60
+ const failures = yield* FailureGroup.make<TransportError>().pipe(
61
+ Scope.provide(resources),
62
+ )
63
+ const finishStarted = yield* Ref.make(false)
64
+ const terminate = yield* Effect.cached(
65
+ Effect.gen(function* () {
66
+ yield* Ref.set(finishStarted, true)
67
+ // A failed close must not skip Dispose; both outcomes are reported together below.
68
+ const closed = yield* Effect.exit(failures.close)
69
+ const result = yield* awaitTermination(
70
+ record.handle,
71
+ session.Dispose(record.handle),
72
+ record.leaseMillis,
73
+ margin,
74
+ )
75
+ const { code, signal, stderr, cause } = result.exit
76
+ yield* Effect.logInfo("Remote execution terminated", {
77
+ code,
78
+ ...(signal === null ? {} : { signal }),
79
+ ...(stderr === undefined || stderr === "" ? {} : { stderr }),
80
+ ...(cause === undefined || cause.reasons.length === 0 ? {} : { cause }),
81
+ }).pipe(
82
+ Effect.annotateLogs({
83
+ "remote.instance": record.handle.key.instance,
84
+ "remote.generation": record.handle.generation,
85
+ "remote.termination": result.kind,
86
+ }),
87
+ )
88
+ const failure = Exit.isFailure(closed)
89
+ ? Cause.combine(closed.cause, result.cause)
90
+ : result.cause
91
+ if (failure.reasons.length > 0)
92
+ return yield* Effect.failCause(cleanupCause(failure))
93
+ }),
94
+ )
95
+ yield* Scope.addFinalizer(
96
+ processScope,
97
+ Effect.gen(function* () {
98
+ if (!(yield* Ref.get(finishStarted)))
99
+ yield* terminate.pipe(
100
+ Effect.catchCause((cause) => Effect.failCause(cleanupCause(cause))),
74
101
  )
75
- const { code, signal, stderr, cause } = result.exit
76
- yield* Effect.logInfo("Remote execution terminated", {
77
- code,
78
- ...(signal === null ? {} : { signal }),
79
- ...(stderr === undefined || stderr === "" ? {} : { stderr }),
80
- ...(cause === undefined || cause.reasons.length === 0
81
- ? {}
82
- : { cause }),
83
- }).pipe(
84
- Effect.annotateLogs({
85
- "remote.instance": record.handle.key.instance,
86
- "remote.generation": record.handle.generation,
87
- "remote.termination": result.kind,
88
- }),
102
+ }),
103
+ )
104
+ const renewed = yield* Effect.forever(
105
+ Effect.sleep(record.leaseMillis / 3).pipe(
106
+ Effect.andThen(session.Renew(record.handle)),
107
+ ),
108
+ ).pipe(Effect.forkIn(resources))
109
+ const code = yield* session.Load({
110
+ handle: record.handle,
111
+ specifier: contractModule,
112
+ digest: target.expect?.digest !== undefined,
113
+ })
114
+ yield* Packages.verify(code, target.expect)
115
+ const loaded = yield* session.Status(record.handle)
116
+ const peer = yield* Port.make(Wire.channel(mux, 1), {
117
+ ...options,
118
+ session: record.handle.token,
119
+ limits: record.limits,
120
+ }).pipe(Scope.provide(resources))
121
+ // RPC forwarding cancellation is expected; resource cleanup is collected by Connection's scopes.
122
+ const report = (cause: Cause.Cause<unknown>) =>
123
+ Cause.hasInterruptsOnly(cause)
124
+ ? Effect.void
125
+ : failures.failCause(
126
+ Cause.map(cause, (error) =>
127
+ TransportError.make({ cause: Cause.fail(error) }),
128
+ ),
89
129
  )
90
- if (result.cause.reasons.length > 0)
91
- return yield* Effect.failCause(cleanupCause(result.cause))
92
- }),
93
- )
94
- yield* Scope.addFinalizer(
95
- processScope,
96
- Effect.gen(function* () {
97
- if (!(yield* Ref.get(finishStarted)))
98
- yield* terminate.pipe(
99
- Effect.catchCause((cause) => Effect.failCause(cleanupCause(cause))),
100
- )
101
- }),
102
- )
103
- const renewed = yield* Effect.forever(
104
- Effect.sleep(record.leaseMillis / 3).pipe(
105
- Effect.andThen(session.Renew(record.handle)),
106
- ),
107
- ).pipe(Effect.forkIn(resources))
108
- const code = yield* session.Load({
109
- handle: record.handle,
110
- specifier: contractModule,
111
- digest: target.expect?.digest !== undefined,
112
- })
113
- yield* Packages.verify(code, target.expect)
114
- const loaded = yield* session.Status(record.handle)
115
- const peer = yield* Port.make(Wire.channel(mux, 1), {
116
- ...options,
117
- session: record.handle.token,
118
- limits: record.limits,
119
- }).pipe(Scope.provide(resources))
120
- // RPC forwarding cancellation is expected; resource cleanup is collected by Connection's scopes.
121
- const report = (cause: Cause.Cause<unknown>) =>
130
+ yield* failures.await.pipe(
131
+ Effect.catchCause((cause) =>
122
132
  Cause.hasInterruptsOnly(cause)
123
133
  ? Effect.void
124
- : failures.failCause(
125
- Cause.map(cause, (error) =>
126
- TransportError.make({ cause: Cause.fail(error) }),
127
- ),
128
- )
129
- yield* failures.await.pipe(
130
- Effect.catchCause((cause) =>
131
- Cause.hasInterruptsOnly(cause)
132
- ? Effect.void
133
- : peer.fail(TransportError.make({ cause })),
134
- ),
135
- Effect.forkIn(resources),
136
- )
134
+ : peer.fail(TransportError.make({ cause })),
135
+ ),
136
+ Effect.forkIn(resources),
137
+ )
137
138
 
138
- yield* failures.run(
139
- Fiber.join(renewed).pipe(
140
- Effect.catchCause((cause) =>
141
- Effect.gen(function* () {
142
- if (Cause.hasInterruptsOnly(cause)) return
143
- const status = yield* Effect.exit(
144
- session.Status(record.handle).pipe(Effect.timeout("5 seconds")),
145
- )
146
- if (Exit.isSuccess(status) && status.value.phase === "Stopped") {
147
- const exit = status.value.termination.exit
148
- yield* report(
149
- Cause.combine(
150
- cause,
151
- Cause.fail(
152
- ProcessExited.make({
153
- pid: Option.map(childOf(loaded), (child) => child.pid),
154
- exitCode: exit.code,
155
- stderr: exit.stderr ?? "",
156
- statusCause: exit.cause ?? Cause.empty,
157
- }),
158
- ),
139
+ yield* failures.run(
140
+ Fiber.join(renewed).pipe(
141
+ Effect.catchCause(
142
+ Effect.fnUntraced(function* (cause) {
143
+ if (Cause.hasInterruptsOnly(cause)) return
144
+ const status = yield* Effect.exit(
145
+ session.Status(record.handle).pipe(Effect.timeout("5 seconds")),
146
+ )
147
+ if (Exit.isSuccess(status) && status.value.phase === "Stopped") {
148
+ const exit = status.value.termination.exit
149
+ yield* report(
150
+ Cause.combine(
151
+ cause,
152
+ Cause.fail(
153
+ ProcessExited.make({
154
+ pid: Option.map(childOf(loaded), (child) => child.pid),
155
+ exitCode: exit.code,
156
+ stderr: exit.stderr ?? "",
157
+ statusCause: exit.cause ?? Cause.empty,
158
+ }),
159
159
  ),
160
- )
161
- } else
162
- yield* report(
163
- Exit.isFailure(status)
164
- ? Cause.combine(cause, status.cause)
165
- : cause,
166
- )
167
- }),
168
- ),
169
- ),
170
- )
171
- return {
172
- resources,
173
- pid: Option.map(childOf(loaded), (child) => child.pid),
174
- diagnostics: failures.cause,
175
- terminate: () =>
176
- Effect.gen(function* () {
177
- // No renewal may extend a disconnected session while termination waits out its lease.
178
- yield* Fiber.interrupt(renewed)
179
- yield* terminate
160
+ ),
161
+ )
162
+ } else
163
+ yield* report(
164
+ Exit.isFailure(status)
165
+ ? Cause.combine(cause, status.cause)
166
+ : cause,
167
+ )
180
168
  }),
181
- transport: {
182
- ...peer,
183
- failure: peer.await,
184
- report,
185
- },
186
- } satisfies Connected
187
- })
169
+ ),
170
+ ),
171
+ )
172
+ return {
173
+ resources,
174
+ pid: Option.map(childOf(loaded), (child) => child.pid),
175
+ diagnostics: failures.cause,
176
+ terminate: Effect.fnUntraced(function* () {
177
+ // No renewal may extend a disconnected session while termination waits out its lease.
178
+ yield* Fiber.interrupt(renewed)
179
+ yield* terminate
180
+ }),
181
+ transport: {
182
+ ...peer,
183
+ failure: peer.await,
184
+ report,
185
+ },
186
+ } satisfies Connected
187
+ })