@nimbus-sh/fabric 0.6.0 → 0.7.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/dist/turn-budget.d.ts +5 -1
- package/dist/turn-budget.d.ts.map +1 -1
- package/dist/turn-budget.js +45 -12
- package/package.json +3 -3
- package/src/turn-budget.ts +41 -12
package/dist/turn-budget.d.ts
CHANGED
|
@@ -114,7 +114,7 @@ export interface PacedWorkHost {
|
|
|
114
114
|
* reason. Without it the pump degrades to a same-context timer — see
|
|
115
115
|
* {@link PacedWork.nextTurn}.
|
|
116
116
|
*/
|
|
117
|
-
requestTurn?: (notBefore?: number) => void
|
|
117
|
+
requestTurn?: (notBefore?: number) => void | Promise<void>;
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
120
|
* The granting side of {@link TurnScheduler}: parks suspended launches
|
|
@@ -134,6 +134,7 @@ export declare class PacedWork implements TurnScheduler {
|
|
|
134
134
|
* from its inputs is the same idempotent work again.
|
|
135
135
|
*/
|
|
136
136
|
private waiters;
|
|
137
|
+
private closed;
|
|
137
138
|
/**
|
|
138
139
|
* `ctx` keys the cold-start queue the pump drains first on every turn it
|
|
139
140
|
* grants — see {@link pump}.
|
|
@@ -159,6 +160,9 @@ export declare class PacedWork implements TurnScheduler {
|
|
|
159
160
|
* the runtime may tear the context down mid-chunk.
|
|
160
161
|
*/
|
|
161
162
|
pump(): Promise<void>;
|
|
163
|
+
private requestTurn;
|
|
164
|
+
/** Reject parked work before the owner drains launch cleanup and cancels its alarm. */
|
|
165
|
+
close(reason?: Error): void;
|
|
162
166
|
/** Whether any launch is suspended waiting for a turn. */
|
|
163
167
|
get hasPending(): boolean;
|
|
164
168
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turn-budget.d.ts","sourceRoot":"","sources":["../src/turn-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,UAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,qBAAa,UAAU;IAkBnB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAnB/B,qEAAqE;IACrE,MAAM,SAAK;IACX,iDAAiD;IACjD,KAAK,SAAK;IAEV,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,UAAU,CAA8D;IAEhF;;;;;;;OAOG;gBAEgB,SAAS,EAAE,aAAa,EACxB,aAAa,GAAE,MAA6B,EAC5C,WAAW,CAAC,GAAE,MAAM,IAAI,aAAA;IAG3C;;;;;;OAMG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAczC;;;;OAIG;IACH,MAAM,IAAI,IAAI;CAIf;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,CAAC,GAAG,IAAI,KAAK;IAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;CAAE,CAI/G;AAED,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"turn-budget.d.ts","sourceRoot":"","sources":["../src/turn-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,UAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,qBAAa,UAAU;IAkBnB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAnB/B,qEAAqE;IACrE,MAAM,SAAK;IACX,iDAAiD;IACjD,KAAK,SAAK;IAEV,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,UAAU,CAA8D;IAEhF;;;;;;;OAOG;gBAEgB,SAAS,EAAE,aAAa,EACxB,aAAa,GAAE,MAA6B,EAC5C,WAAW,CAAC,GAAE,MAAM,IAAI,aAAA;IAG3C;;;;;;OAMG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAczC;;;;OAIG;IACH,MAAM,IAAI,IAAI;CAIf;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,CAAC,GAAG,IAAI,KAAK;IAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;CAAE,CAI/G;AAED,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED;;;GAGG;AACH,qBAAa,SAAU,YAAW,aAAa;IAmB3C,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAnBvB;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,CAAqH;IACpI,OAAO,CAAC,MAAM,CAAoB;IAElC;;;OAGG;gBAEgB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,aAAa;IAGtC;;;;;;;;;OASG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBjE;;;;;;;OAOG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAyBb,WAAW;IAKzB,uFAAuF;IACvF,KAAK,CAAC,MAAM,QAA0C,GAAG,IAAI;IAK7D,0DAA0D;IAC1D,IAAI,UAAU,IAAI,OAAO,CAExB;CACF;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAMtD"}
|
package/dist/turn-budget.js
CHANGED
|
@@ -130,6 +130,7 @@ export class PacedWork {
|
|
|
130
130
|
* from its inputs is the same idempotent work again.
|
|
131
131
|
*/
|
|
132
132
|
waiters = [];
|
|
133
|
+
closed;
|
|
133
134
|
/**
|
|
134
135
|
* `ctx` keys the cold-start queue the pump drains first on every turn it
|
|
135
136
|
* grants — see {@link pump}.
|
|
@@ -149,10 +150,19 @@ export class PacedWork {
|
|
|
149
150
|
* responsiveness but keeps the behaviour.
|
|
150
151
|
*/
|
|
151
152
|
nextTurn(chunkEnded, notBefore = 0) {
|
|
152
|
-
|
|
153
|
-
this.
|
|
153
|
+
if (this.closed)
|
|
154
|
+
return Promise.reject(this.closed);
|
|
155
|
+
return new Promise((resume, reject) => {
|
|
156
|
+
const waiter = { resume, reject, chunkEnded, notBefore };
|
|
157
|
+
this.waiters.push(waiter);
|
|
154
158
|
if (this.host.requestTurn) {
|
|
155
|
-
this.
|
|
159
|
+
void this.requestTurn(notBefore).catch((error) => {
|
|
160
|
+
const index = this.waiters.indexOf(waiter);
|
|
161
|
+
if (index < 0)
|
|
162
|
+
return;
|
|
163
|
+
this.waiters.splice(index, 1);
|
|
164
|
+
waiter.reject(error);
|
|
165
|
+
});
|
|
156
166
|
return;
|
|
157
167
|
}
|
|
158
168
|
setTimeout(() => { void this.pump(); }, Math.max(0, notBefore - Date.now()));
|
|
@@ -167,21 +177,44 @@ export class PacedWork {
|
|
|
167
177
|
* the runtime may tear the context down mid-chunk.
|
|
168
178
|
*/
|
|
169
179
|
async pump() {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// is what an alarm calls, and the first turn after a reset is the
|
|
173
|
-
// re-delivered alarm of a launch the reset interrupted.
|
|
180
|
+
if (this.closed)
|
|
181
|
+
return;
|
|
174
182
|
await runColdStart(this.ctx);
|
|
183
|
+
if (this.closed)
|
|
184
|
+
return;
|
|
175
185
|
const now = Date.now();
|
|
176
186
|
const waiting = this.waiters.filter((waiter) => waiter.notBefore <= now);
|
|
177
187
|
this.waiters = this.waiters.filter((waiter) => waiter.notBefore > now);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
188
|
+
const future = [...this.waiters];
|
|
189
|
+
const scheduled = future.length > 0
|
|
190
|
+
? this.requestTurn(Math.min(...future.map((waiter) => waiter.notBefore))).catch((error) => {
|
|
191
|
+
for (const waiter of future) {
|
|
192
|
+
const index = this.waiters.indexOf(waiter);
|
|
193
|
+
if (index < 0)
|
|
194
|
+
continue;
|
|
195
|
+
this.waiters.splice(index, 1);
|
|
196
|
+
waiter.reject(error);
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
199
|
+
})
|
|
200
|
+
: Promise.resolve();
|
|
182
201
|
for (const waiter of waiting)
|
|
183
202
|
waiter.resume();
|
|
184
|
-
await Promise.
|
|
203
|
+
const results = await Promise.allSettled([scheduled, ...waiting.map((waiter) => waiter.chunkEnded)]);
|
|
204
|
+
const failure = results.find((result) => result.status === 'rejected');
|
|
205
|
+
if (failure?.status === 'rejected')
|
|
206
|
+
throw failure.reason;
|
|
207
|
+
}
|
|
208
|
+
async requestTurn(notBefore) {
|
|
209
|
+
if (this.closed)
|
|
210
|
+
throw this.closed;
|
|
211
|
+
await this.host.requestTurn?.(notBefore);
|
|
212
|
+
}
|
|
213
|
+
/** Reject parked work before the owner drains launch cleanup and cancels its alarm. */
|
|
214
|
+
close(reason = new Error('Launch scheduler is closed')) {
|
|
215
|
+
this.closed ??= reason;
|
|
216
|
+
for (const waiter of this.waiters.splice(0))
|
|
217
|
+
waiter.reject(this.closed);
|
|
185
218
|
}
|
|
186
219
|
/** Whether any launch is suspended waiting for a turn. */
|
|
187
220
|
get hasPending() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbus-sh/fabric",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The Cloudflare half of Nimbus — Durable Object facet hosting, dynamic-worker loader pools, the resident-process fabric, and DO alarm/hibernation machinery.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"typecheck": "tsc --noEmit"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@nimbus-sh/core": "^0.
|
|
54
|
-
"@nimbus-sh/platform": "^0.
|
|
53
|
+
"@nimbus-sh/core": "^0.11.0",
|
|
54
|
+
"@nimbus-sh/platform": "^0.5.0",
|
|
55
55
|
"zod": "^4.4.3"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
package/src/turn-budget.ts
CHANGED
|
@@ -142,7 +142,7 @@ export interface PacedWorkHost {
|
|
|
142
142
|
* reason. Without it the pump degrades to a same-context timer — see
|
|
143
143
|
* {@link PacedWork.nextTurn}.
|
|
144
144
|
*/
|
|
145
|
-
requestTurn?: (notBefore?: number) => void
|
|
145
|
+
requestTurn?: (notBefore?: number) => void | Promise<void>;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/**
|
|
@@ -160,7 +160,8 @@ export class PacedWork implements TurnScheduler {
|
|
|
160
160
|
* half-built work for pids that no longer exist, where re-driving a launch
|
|
161
161
|
* from its inputs is the same idempotent work again.
|
|
162
162
|
*/
|
|
163
|
-
private waiters: Array<{ resume: () => void; chunkEnded: Promise<void>; notBefore: number }> = [];
|
|
163
|
+
private waiters: Array<{ resume: () => void; reject: (error: unknown) => void; chunkEnded: Promise<void>; notBefore: number }> = [];
|
|
164
|
+
private closed: Error | undefined;
|
|
164
165
|
|
|
165
166
|
/**
|
|
166
167
|
* `ctx` keys the cold-start queue the pump drains first on every turn it
|
|
@@ -182,10 +183,17 @@ export class PacedWork implements TurnScheduler {
|
|
|
182
183
|
* responsiveness but keeps the behaviour.
|
|
183
184
|
*/
|
|
184
185
|
nextTurn(chunkEnded: Promise<void>, notBefore = 0): Promise<void> {
|
|
185
|
-
return
|
|
186
|
-
|
|
186
|
+
if (this.closed) return Promise.reject(this.closed);
|
|
187
|
+
return new Promise<void>((resume, reject) => {
|
|
188
|
+
const waiter = { resume, reject, chunkEnded, notBefore };
|
|
189
|
+
this.waiters.push(waiter);
|
|
187
190
|
if (this.host.requestTurn) {
|
|
188
|
-
this.
|
|
191
|
+
void this.requestTurn(notBefore).catch((error: unknown) => {
|
|
192
|
+
const index = this.waiters.indexOf(waiter);
|
|
193
|
+
if (index < 0) return;
|
|
194
|
+
this.waiters.splice(index, 1);
|
|
195
|
+
waiter.reject(error);
|
|
196
|
+
});
|
|
189
197
|
return;
|
|
190
198
|
}
|
|
191
199
|
setTimeout(() => { void this.pump(); }, Math.max(0, notBefore - Date.now()));
|
|
@@ -201,18 +209,39 @@ export class PacedWork implements TurnScheduler {
|
|
|
201
209
|
* the runtime may tear the context down mid-chunk.
|
|
202
210
|
*/
|
|
203
211
|
async pump(): Promise<void> {
|
|
204
|
-
|
|
205
|
-
// the resident-launch journal's recovery sits (`onColdStart`): the pump
|
|
206
|
-
// is what an alarm calls, and the first turn after a reset is the
|
|
207
|
-
// re-delivered alarm of a launch the reset interrupted.
|
|
212
|
+
if (this.closed) return;
|
|
208
213
|
await runColdStart(this.ctx);
|
|
214
|
+
if (this.closed) return;
|
|
209
215
|
const now = Date.now();
|
|
210
216
|
const waiting = this.waiters.filter((waiter) => waiter.notBefore <= now);
|
|
211
217
|
this.waiters = this.waiters.filter((waiter) => waiter.notBefore > now);
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
const future = [...this.waiters];
|
|
219
|
+
const scheduled = future.length > 0
|
|
220
|
+
? this.requestTurn(Math.min(...future.map((waiter) => waiter.notBefore))).catch((error: unknown) => {
|
|
221
|
+
for (const waiter of future) {
|
|
222
|
+
const index = this.waiters.indexOf(waiter);
|
|
223
|
+
if (index < 0) continue;
|
|
224
|
+
this.waiters.splice(index, 1);
|
|
225
|
+
waiter.reject(error);
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
})
|
|
229
|
+
: Promise.resolve();
|
|
214
230
|
for (const waiter of waiting) waiter.resume();
|
|
215
|
-
await Promise.
|
|
231
|
+
const results = await Promise.allSettled([scheduled, ...waiting.map((waiter) => waiter.chunkEnded)]);
|
|
232
|
+
const failure = results.find((result) => result.status === 'rejected');
|
|
233
|
+
if (failure?.status === 'rejected') throw failure.reason;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private async requestTurn(notBefore: number): Promise<void> {
|
|
237
|
+
if (this.closed) throw this.closed;
|
|
238
|
+
await this.host.requestTurn?.(notBefore);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Reject parked work before the owner drains launch cleanup and cancels its alarm. */
|
|
242
|
+
close(reason = new Error('Launch scheduler is closed')): void {
|
|
243
|
+
this.closed ??= reason;
|
|
244
|
+
for (const waiter of this.waiters.splice(0)) waiter.reject(this.closed);
|
|
216
245
|
}
|
|
217
246
|
|
|
218
247
|
/** Whether any launch is suspended waiting for a turn. */
|