@namzu/sandbox 14.0.0 → 16.0.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/CHANGELOG.md +924 -0
- package/README.md +369 -14
- package/dist/backends/aci-standby-pool/index.d.ts.map +1 -1
- package/dist/backends/aci-standby-pool/index.js +13 -1
- package/dist/backends/aci-standby-pool/index.js.map +1 -1
- package/dist/backends/docker/index.d.ts +169 -6
- package/dist/backends/docker/index.d.ts.map +1 -1
- package/dist/backends/docker/index.js +499 -85
- package/dist/backends/docker/index.js.map +1 -1
- package/dist/backends/firecracker/index.d.ts.map +1 -1
- package/dist/backends/firecracker/index.js +12 -2
- package/dist/backends/firecracker/index.js.map +1 -1
- package/dist/backends/firecracker/protocol.d.ts +459 -8
- package/dist/backends/firecracker/protocol.d.ts.map +1 -1
- package/dist/backends/firecracker/protocol.js +136 -0
- package/dist/backends/firecracker/protocol.js.map +1 -1
- package/dist/backends/firecracker/transport.d.ts +539 -6
- package/dist/backends/firecracker/transport.d.ts.map +1 -1
- package/dist/backends/firecracker/transport.js +1171 -24
- package/dist/backends/firecracker/transport.js.map +1 -1
- package/dist/backends/kubernetes/egress-policy.d.ts +1181 -13
- package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -1
- package/dist/backends/kubernetes/egress-policy.js +2350 -31
- package/dist/backends/kubernetes/egress-policy.js.map +1 -1
- package/dist/backends/kubernetes/identity.d.ts +193 -0
- package/dist/backends/kubernetes/identity.d.ts.map +1 -0
- package/dist/backends/kubernetes/identity.js +147 -0
- package/dist/backends/kubernetes/identity.js.map +1 -0
- package/dist/backends/kubernetes/index.d.ts +678 -33
- package/dist/backends/kubernetes/index.d.ts.map +1 -1
- package/dist/backends/kubernetes/index.js +1180 -95
- package/dist/backends/kubernetes/index.js.map +1 -1
- package/dist/backends/kubernetes/ingress-policy.d.ts +375 -0
- package/dist/backends/kubernetes/ingress-policy.d.ts.map +1 -0
- package/dist/backends/kubernetes/ingress-policy.js +1050 -0
- package/dist/backends/kubernetes/ingress-policy.js.map +1 -0
- package/dist/backends/kubernetes/k8s-client.d.ts +213 -4
- package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -1
- package/dist/backends/kubernetes/k8s-client.js +359 -52
- package/dist/backends/kubernetes/k8s-client.js.map +1 -1
- package/dist/backends/kubernetes/lease.d.ts +40 -14
- package/dist/backends/kubernetes/lease.d.ts.map +1 -1
- package/dist/backends/kubernetes/lease.js +68 -18
- package/dist/backends/kubernetes/lease.js.map +1 -1
- package/dist/backends/kubernetes/objects.d.ts +423 -3
- package/dist/backends/kubernetes/objects.d.ts.map +1 -1
- package/dist/backends/kubernetes/objects.js +364 -2
- package/dist/backends/kubernetes/objects.js.map +1 -1
- package/dist/backends/kubernetes/per-sandbox-policy.d.ts +219 -0
- package/dist/backends/kubernetes/per-sandbox-policy.d.ts.map +1 -0
- package/dist/backends/kubernetes/per-sandbox-policy.js +375 -0
- package/dist/backends/kubernetes/per-sandbox-policy.js.map +1 -0
- package/dist/backends/kubernetes/rbac.d.ts +153 -0
- package/dist/backends/kubernetes/rbac.d.ts.map +1 -0
- package/dist/backends/kubernetes/rbac.js +177 -0
- package/dist/backends/kubernetes/rbac.js.map +1 -0
- package/dist/backends/kubernetes/sandbox.d.ts +81 -14
- package/dist/backends/kubernetes/sandbox.d.ts.map +1 -1
- package/dist/backends/kubernetes/sandbox.js +149 -15
- package/dist/backends/kubernetes/sandbox.js.map +1 -1
- package/dist/backends/kubernetes/transport.d.ts +935 -9
- package/dist/backends/kubernetes/transport.d.ts.map +1 -1
- package/dist/backends/kubernetes/transport.js +1958 -62
- package/dist/backends/kubernetes/transport.js.map +1 -1
- package/dist/backends/kubernetes/workspace.d.ts +1149 -18
- package/dist/backends/kubernetes/workspace.d.ts.map +1 -1
- package/dist/backends/kubernetes/workspace.js +2825 -186
- package/dist/backends/kubernetes/workspace.js.map +1 -1
- package/dist/backends/remote-execution-controller.d.ts +14 -0
- package/dist/backends/remote-execution-controller.d.ts.map +1 -1
- package/dist/backends/remote-execution-controller.js.map +1 -1
- package/dist/index.d.ts +294 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +280 -10
- package/dist/index.js.map +1 -1
- package/dist/testing/sandbox-conformance.d.ts +39 -5
- package/dist/testing/sandbox-conformance.d.ts.map +1 -1
- package/dist/testing/sandbox-conformance.js +436 -5
- package/dist/testing/sandbox-conformance.js.map +1 -1
- package/package.json +3 -3
- package/src/backends/aci-standby-pool/index.ts +16 -1
- package/src/backends/docker/index.ts +617 -100
- package/src/backends/firecracker/index.ts +14 -2
- package/src/backends/firecracker/protocol.ts +514 -6
- package/src/backends/firecracker/transport.ts +1492 -40
- package/src/backends/kubernetes/egress-policy.ts +3334 -55
- package/src/backends/kubernetes/identity.ts +261 -0
- package/src/backends/kubernetes/index.ts +1785 -127
- package/src/backends/kubernetes/ingress-policy.ts +1344 -0
- package/src/backends/kubernetes/k8s-client.ts +444 -54
- package/src/backends/kubernetes/lease.ts +75 -19
- package/src/backends/kubernetes/objects.ts +626 -6
- package/src/backends/kubernetes/per-sandbox-policy.ts +497 -0
- package/src/backends/kubernetes/rbac.ts +192 -0
- package/src/backends/kubernetes/sandbox.ts +218 -20
- package/src/backends/kubernetes/transport.ts +2733 -124
- package/src/backends/kubernetes/workspace.ts +4476 -222
- package/src/backends/remote-execution-controller.ts +14 -0
- package/src/index.ts +668 -19
- package/src/testing/sandbox-conformance.ts +540 -5
|
@@ -16,36 +16,48 @@
|
|
|
16
16
|
*
|
|
17
17
|
* One `setTimeout` chained per tick, never `setInterval`: a renewal PATCH
|
|
18
18
|
* that takes longer than the interval must not queue a second one behind
|
|
19
|
-
* it.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* PATCH the API server in the same millisecond forever after.
|
|
19
|
+
* it. On SUCCESS the interval is HALF the TTL, jittered ±10% so a hundred
|
|
20
|
+
* handles acquired in the same second do not PATCH the API server in the
|
|
21
|
+
* same millisecond forever after.
|
|
23
22
|
*
|
|
24
23
|
* The timer is `unref`'d: a host process that has finished its work should
|
|
25
24
|
* exit, not linger because a sandbox handle is still counting. A handle
|
|
26
25
|
* nobody destroyed then expires on the cluster's clock exactly as an
|
|
27
26
|
* abandoned one does, which is the behaviour the TTL exists for.
|
|
28
27
|
*
|
|
28
|
+
* ## A failed tick does not wait for the next half-TTL
|
|
29
|
+
*
|
|
30
|
+
* Waiting a full half-TTL before retrying a FAILED renewal means one blip at
|
|
31
|
+
* exactly the wrong moment is a coin flip against the object's own
|
|
32
|
+
* `shutdownTime`: the retry and the expiry are both roughly a TTL after the
|
|
33
|
+
* last success, so a single failure can lose that race. A failed tick
|
|
34
|
+
* instead retries on capped exponential backoff — starting at one second,
|
|
35
|
+
* doubling, capped at whichever is smaller of thirty seconds or a
|
|
36
|
+
* twentieth of the TTL — so an outage around a scheduled renewal gets many
|
|
37
|
+
* attempts inside the window that actually matters, not one. Every success
|
|
38
|
+
* resets the backoff and returns the loop to the normal half-TTL cadence.
|
|
39
|
+
*
|
|
29
40
|
* ## Every tick is bounded
|
|
30
41
|
*
|
|
31
|
-
* A renewal that FAILS is survivable — it is reported and retried
|
|
32
|
-
*
|
|
42
|
+
* A renewal that FAILS is survivable — it is reported and retried on a
|
|
43
|
+
* short backoff. A renewal that HANGS is not: the next tick is scheduled
|
|
33
44
|
* only after the current one settles, so a PATCH that never answers parks
|
|
34
45
|
* the loop forever, reports nothing, and lets the lease expire in silence —
|
|
35
46
|
* precisely the defect this file exists to close, moved onto the failure
|
|
36
47
|
* path. An API server that accepts a connection and then never responds is
|
|
37
48
|
* an ordinary cluster event, so each PATCH runs under its own deadline: it
|
|
38
49
|
* aborts the request through the signal the client already takes, and an
|
|
39
|
-
* expiry is then just another reported failure that retries on
|
|
40
|
-
* tick.
|
|
50
|
+
* expiry is then just another reported failure that retries on backoff.
|
|
41
51
|
*
|
|
42
52
|
* ## What each outcome means
|
|
43
53
|
*
|
|
44
|
-
* - Success → the object's expiry moves a full TTL into the future
|
|
54
|
+
* - Success → the object's expiry moves a full TTL into the future, the
|
|
55
|
+
* backoff resets, and the next tick is a half-TTL away again.
|
|
45
56
|
* - Any error, a tick that ran out of time included → reported to
|
|
46
|
-
* `onRenewalError` and RETRIED on
|
|
47
|
-
*
|
|
48
|
-
*
|
|
57
|
+
* `onRenewalError` and RETRIED on a backoff far shorter than the
|
|
58
|
+
* half-TTL interval. A transient API blip must not tear down a working
|
|
59
|
+
* sandbox, and the loop keeps trying rather than spend the object's
|
|
60
|
+
* remaining headroom waiting.
|
|
49
61
|
* - Already gone (404/410) → the object this handle owns no longer exists.
|
|
50
62
|
* Nothing will bring it back, so the loop stops and the handle is marked
|
|
51
63
|
* gone; every later call fails with a named error instead of dialing an
|
|
@@ -86,7 +98,7 @@ export interface LeaseRenewalOptions {
|
|
|
86
98
|
* Defaults to a quarter of the interval, capped at
|
|
87
99
|
* {@link MAX_RENEWAL_TIMEOUT_MS} — a fraction rather than the whole
|
|
88
100
|
* interval so that a stalled API server still leaves the loop several
|
|
89
|
-
* attempts
|
|
101
|
+
* backoff-paced attempts before the next regular half-TTL tick.
|
|
90
102
|
*/
|
|
91
103
|
readonly patchTimeoutMs?: number
|
|
92
104
|
/** Deterministic jitter for tests. Defaults to `Math.random`. */
|
|
@@ -100,6 +112,23 @@ export interface LeaseRenewalOptions {
|
|
|
100
112
|
*/
|
|
101
113
|
const MAX_RENEWAL_TIMEOUT_MS = 30_000
|
|
102
114
|
|
|
115
|
+
/**
|
|
116
|
+
* The floor of the retry backoff after a failed renewal: one second. Far
|
|
117
|
+
* short of the half-TTL interval, on purpose — a failure needs another
|
|
118
|
+
* chance long before the object's `shutdownTime` is at risk, not after
|
|
119
|
+
* waiting as long as a successful tick would have.
|
|
120
|
+
*/
|
|
121
|
+
const RETRY_BACKOFF_FLOOR_MS = 1_000
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The ceiling of the retry backoff, whichever is smaller: thirty seconds, or
|
|
125
|
+
* a twentieth of the TTL. The TTL fraction keeps a short-TTL sandbox (tests,
|
|
126
|
+
* mainly) from retrying so slowly that the backoff alone could still lose
|
|
127
|
+
* the race against expiry; thirty seconds keeps an hour-plus TTL from
|
|
128
|
+
* retrying needlessly often once the ceiling is reached.
|
|
129
|
+
*/
|
|
130
|
+
const MAX_RETRY_BACKOFF_MS = 30_000
|
|
131
|
+
|
|
103
132
|
/** ±10%: enough to spread a synchronised fleet, far too little to matter
|
|
104
133
|
* against a half-TTL of headroom. */
|
|
105
134
|
const JITTER_FRACTION = 0.1
|
|
@@ -120,13 +149,23 @@ export class KubernetesLeaseRenewal {
|
|
|
120
149
|
private started = false
|
|
121
150
|
private readonly baseIntervalMs: number
|
|
122
151
|
private readonly patchTimeoutMs: number
|
|
152
|
+
private readonly retryBackoffCapMs: number
|
|
123
153
|
private readonly random: () => number
|
|
154
|
+
/**
|
|
155
|
+
* Non-gone failures since the last success (or since the loop started).
|
|
156
|
+
* Reset to 0 by every success; drives how far the next retry backs off.
|
|
157
|
+
*/
|
|
158
|
+
private consecutiveFailures = 0
|
|
124
159
|
|
|
125
160
|
constructor(private readonly options: LeaseRenewalOptions) {
|
|
126
161
|
this.baseIntervalMs = options.intervalMs ?? Math.max(1, (options.ttlSeconds * 1_000) / 2)
|
|
127
162
|
this.patchTimeoutMs =
|
|
128
163
|
options.patchTimeoutMs ??
|
|
129
164
|
Math.max(1, Math.min(MAX_RENEWAL_TIMEOUT_MS, Math.round(this.baseIntervalMs / 4)))
|
|
165
|
+
this.retryBackoffCapMs = Math.max(
|
|
166
|
+
1,
|
|
167
|
+
Math.min(MAX_RETRY_BACKOFF_MS, (options.ttlSeconds * 1_000) / 20),
|
|
168
|
+
)
|
|
130
169
|
this.random = options.random ?? Math.random
|
|
131
170
|
}
|
|
132
171
|
|
|
@@ -144,7 +183,7 @@ export class KubernetesLeaseRenewal {
|
|
|
144
183
|
start(): void {
|
|
145
184
|
if (this.stopped || this.timer !== undefined) return
|
|
146
185
|
this.started = true
|
|
147
|
-
this.
|
|
186
|
+
this.scheduleNext(this.baseIntervalMs)
|
|
148
187
|
}
|
|
149
188
|
|
|
150
189
|
stop(): void {
|
|
@@ -155,19 +194,31 @@ export class KubernetesLeaseRenewal {
|
|
|
155
194
|
}
|
|
156
195
|
}
|
|
157
196
|
|
|
158
|
-
private
|
|
197
|
+
private scheduleNext(baseMs: number): void {
|
|
159
198
|
if (this.stopped) return
|
|
160
199
|
const timer = setTimeout(
|
|
161
200
|
() => {
|
|
162
201
|
void this.tick()
|
|
163
202
|
},
|
|
164
|
-
jitteredInterval(
|
|
203
|
+
jitteredInterval(baseMs, this.random),
|
|
165
204
|
)
|
|
166
205
|
// A pending renewal must never be the reason a host process stays up.
|
|
167
206
|
timer.unref?.()
|
|
168
207
|
this.timer = timer
|
|
169
208
|
}
|
|
170
209
|
|
|
210
|
+
/**
|
|
211
|
+
* The delay before the NEXT retry after a non-gone failure: capped
|
|
212
|
+
* exponential backoff from {@link RETRY_BACKOFF_FLOOR_MS}, doubling on
|
|
213
|
+
* every consecutive failure, ceilinged at {@link retryBackoffCapMs}.
|
|
214
|
+
* Called only once `consecutiveFailures` has already been incremented for
|
|
215
|
+
* the failure that just happened, so the first retry uses the floor.
|
|
216
|
+
*/
|
|
217
|
+
private retryDelayMs(): number {
|
|
218
|
+
const doubled = RETRY_BACKOFF_FLOOR_MS * 2 ** (this.consecutiveFailures - 1)
|
|
219
|
+
return Math.min(this.retryBackoffCapMs, doubled)
|
|
220
|
+
}
|
|
221
|
+
|
|
171
222
|
/** Exposed for tests: one renewal attempt plus its scheduling decision. */
|
|
172
223
|
async tick(): Promise<void> {
|
|
173
224
|
this.timer = undefined
|
|
@@ -189,10 +240,15 @@ export class KubernetesLeaseRenewal {
|
|
|
189
240
|
return
|
|
190
241
|
}
|
|
191
242
|
// Everything else is transient until proven otherwise: report it
|
|
192
|
-
// and
|
|
193
|
-
//
|
|
243
|
+
// and retry on a backoff far shorter than the half-TTL interval —
|
|
244
|
+
// a coin-flip race against the object's own expiry is exactly what
|
|
245
|
+
// this file exists to avoid.
|
|
246
|
+
this.consecutiveFailures += 1
|
|
194
247
|
this.options.onRenewalError?.(error)
|
|
248
|
+
this.scheduleNext(this.retryDelayMs())
|
|
249
|
+
return
|
|
195
250
|
}
|
|
196
|
-
this.
|
|
251
|
+
this.consecutiveFailures = 0
|
|
252
|
+
this.scheduleNext(this.baseIntervalMs)
|
|
197
253
|
}
|
|
198
254
|
}
|