@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.
@@ -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;CAC5C;AAED;;;GAGG;AACH,qBAAa,SAAU,YAAW,aAAa;IAkB3C,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAlBvB;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,CAAmF;IAElG;;;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;IAWjE;;;;;;;OAOG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAe3B,0DAA0D;IAC1D,IAAI,UAAU,IAAI,OAAO,CAExB;CACF;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAMtD"}
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"}
@@ -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
- return new Promise((resume) => {
153
- this.waiters.push({ resume, chunkEnded, notBefore });
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.host.requestTurn(notBefore);
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
- // Deferred reconciliation runs first, before any waiter resumes. Where
171
- // the resident-launch journal's recovery sits (`onColdStart`): the pump
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
- if (this.waiters.length > 0)
179
- this.host.requestTurn?.(Math.min(...this.waiters.map((waiter) => waiter.notBefore)));
180
- if (waiting.length === 0)
181
- return;
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.all(waiting.map((waiter) => waiter.chunkEnded));
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.6.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.10.0",
54
- "@nimbus-sh/platform": "^0.4.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": {
@@ -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 new Promise<void>((resume) => {
186
- this.waiters.push({ resume, chunkEnded, notBefore });
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.host.requestTurn(notBefore);
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
- // Deferred reconciliation runs first, before any waiter resumes. Where
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
- if (this.waiters.length > 0) this.host.requestTurn?.(Math.min(...this.waiters.map((waiter) => waiter.notBefore)));
213
- if (waiting.length === 0) return;
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.all(waiting.map((waiter) => waiter.chunkEnded));
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. */