@nimbus-sh/platform 0.1.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/dist/diag-counters.d.ts +210 -0
  4. package/dist/diag-counters.d.ts.map +1 -0
  5. package/dist/diag-counters.js +170 -0
  6. package/dist/heap-estimate.d.ts +225 -0
  7. package/dist/heap-estimate.d.ts.map +1 -0
  8. package/dist/heap-estimate.js +185 -0
  9. package/dist/heavy-alloc-coord.d.ts +93 -0
  10. package/dist/heavy-alloc-coord.d.ts.map +1 -0
  11. package/dist/heavy-alloc-coord.js +181 -0
  12. package/dist/index.d.ts +19 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +18 -0
  15. package/dist/install-phase.d.ts +47 -0
  16. package/dist/install-phase.d.ts.map +1 -0
  17. package/dist/install-phase.js +1 -0
  18. package/dist/limits.d.ts +83 -0
  19. package/dist/limits.d.ts.map +1 -0
  20. package/dist/limits.js +146 -0
  21. package/dist/oom-classify.d.ts +172 -0
  22. package/dist/oom-classify.d.ts.map +1 -0
  23. package/dist/oom-classify.js +336 -0
  24. package/dist/oom-discriminator.d.ts +176 -0
  25. package/dist/oom-discriminator.d.ts.map +1 -0
  26. package/dist/oom-discriminator.js +272 -0
  27. package/dist/rpc-dispose.d.ts +4 -0
  28. package/dist/rpc-dispose.d.ts.map +1 -0
  29. package/dist/rpc-dispose.js +30 -0
  30. package/dist/w7-frame.d.ts +107 -0
  31. package/dist/w7-frame.d.ts.map +1 -0
  32. package/dist/w7-frame.js +842 -0
  33. package/dist/weighted-credit-pool.d.ts +61 -0
  34. package/dist/weighted-credit-pool.d.ts.map +1 -0
  35. package/dist/weighted-credit-pool.js +196 -0
  36. package/package.json +57 -0
  37. package/src/diag-counters.ts +310 -0
  38. package/src/heap-estimate.ts +331 -0
  39. package/src/heavy-alloc-coord.ts +241 -0
  40. package/src/index.ts +19 -0
  41. package/src/install-phase.ts +56 -0
  42. package/src/limits.ts +165 -0
  43. package/src/oom-classify.ts +350 -0
  44. package/src/oom-discriminator.ts +405 -0
  45. package/src/rpc-dispose.ts +31 -0
  46. package/src/w7-frame.ts +1046 -0
  47. package/src/weighted-credit-pool.ts +244 -0
@@ -0,0 +1,244 @@
1
+ export interface CreditLease {
2
+ readonly bytes: number;
3
+ release(): void;
4
+ }
5
+
6
+ export interface ResizableCreditLease extends CreditLease {
7
+ shrinkTo(bytes: number): void;
8
+ }
9
+
10
+ export interface WeightedCreditPoolStats {
11
+ readonly current: number;
12
+ readonly peak: number;
13
+ readonly queued: number;
14
+ }
15
+
16
+ export interface WeightedCreditPoolOptions {
17
+ /**
18
+ * Requests at or below this size draw from `reserve` instead of the shared
19
+ * capacity. Omit to keep a single lane.
20
+ */
21
+ readonly smallRequestBytes?: number;
22
+ /**
23
+ * Bytes set aside for small requests. Held apart from `capacity` so a
24
+ * full-capacity owner stays grantable and small requests stay servable
25
+ * while it works.
26
+ */
27
+ readonly reserve?: number;
28
+ }
29
+
30
+ interface CreditWaiter {
31
+ readonly bytes: number;
32
+ readonly resolve: (lease: ResizableCreditLease) => void;
33
+ readonly reject: (error: Error) => void;
34
+ readonly signal?: AbortSignal;
35
+ readonly onAbort?: () => void;
36
+ }
37
+
38
+ /** One FIFO queue over one byte capacity. */
39
+ class CreditLane {
40
+ private current = 0;
41
+ private peak = 0;
42
+ private readonly waiters: CreditWaiter[] = [];
43
+
44
+ constructor(readonly capacity: number) {}
45
+
46
+ get stats(): WeightedCreditPoolStats {
47
+ return { current: this.current, peak: this.peak, queued: this.waiters.length };
48
+ }
49
+
50
+ tryAcquire(bytes: number): ResizableCreditLease | null {
51
+ if (this.waiters.length > 0 || this.current + bytes > this.capacity) return null;
52
+ return this.grant(bytes);
53
+ }
54
+
55
+ acquire(bytes: number, signal?: AbortSignal): Promise<ResizableCreditLease> {
56
+ if (signal?.aborted) return Promise.reject(abortError(signal));
57
+ const immediate = this.tryAcquire(bytes);
58
+ if (immediate) return Promise.resolve(immediate);
59
+
60
+ return new Promise<ResizableCreditLease>((resolve, reject) => {
61
+ const waiter: CreditWaiter = {
62
+ bytes,
63
+ resolve,
64
+ reject,
65
+ signal,
66
+ onAbort: signal
67
+ ? () => {
68
+ const index = this.waiters.indexOf(waiter);
69
+ if (index < 0) return;
70
+ this.waiters.splice(index, 1);
71
+ signal.removeEventListener('abort', waiter.onAbort!);
72
+ reject(abortError(signal));
73
+ this.drain();
74
+ }
75
+ : undefined,
76
+ };
77
+ this.waiters.push(waiter);
78
+ signal?.addEventListener('abort', waiter.onAbort!, { once: true });
79
+ if (signal?.aborted) {
80
+ waiter.onAbort!();
81
+ return;
82
+ }
83
+ this.drain();
84
+ });
85
+ }
86
+
87
+ private grant(bytes: number): ResizableCreditLease {
88
+ this.current += bytes;
89
+ this.peak = Math.max(this.peak, this.current);
90
+ let leasedBytes = bytes;
91
+ let released = false;
92
+ return {
93
+ get bytes() {
94
+ return leasedBytes;
95
+ },
96
+ shrinkTo: (nextBytes: number) => {
97
+ if (released) {
98
+ throw new Error('cannot shrink a released weighted credit lease');
99
+ }
100
+ if (
101
+ !Number.isSafeInteger(nextBytes)
102
+ || nextBytes <= 0
103
+ || nextBytes > leasedBytes
104
+ ) {
105
+ throw new RangeError(
106
+ `weighted credit lease can only shrink to a positive safe integer no larger than ${leasedBytes}: ${nextBytes}`,
107
+ );
108
+ }
109
+ this.current -= leasedBytes - nextBytes;
110
+ leasedBytes = nextBytes;
111
+ this.drain();
112
+ },
113
+ release: () => {
114
+ if (released) return;
115
+ released = true;
116
+ this.current -= leasedBytes;
117
+ if (this.current < 0) {
118
+ throw new Error('weighted credit accounting underflow');
119
+ }
120
+ this.drain();
121
+ },
122
+ };
123
+ }
124
+
125
+ private drain(): void {
126
+ while (this.waiters.length > 0) {
127
+ const waiter = this.waiters[0];
128
+ if (waiter.signal?.aborted) {
129
+ this.waiters.shift();
130
+ waiter.signal.removeEventListener('abort', waiter.onAbort!);
131
+ waiter.reject(abortError(waiter.signal));
132
+ continue;
133
+ }
134
+ if (this.current + waiter.bytes > this.capacity) return;
135
+ this.waiters.shift();
136
+ waiter.signal?.removeEventListener('abort', waiter.onAbort!);
137
+ waiter.resolve(this.grant(waiter.bytes));
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * FIFO byte-credit pool shared by concurrent allocation owners.
144
+ * Capacity is measured in retained bytes, not operation count.
145
+ *
146
+ * One FIFO queue makes every request wait behind the one ahead of it even
147
+ * when the pool has room to spare — `tryAcquire` refuses outright while
148
+ * anyone is queued — so a single multi-megabyte owner parks every request
149
+ * behind it for as long as it holds. Between owners of comparable size that
150
+ * is the point. For a chunk-sized filesystem read it means a wait with
151
+ * nothing to do with its own cost.
152
+ *
153
+ * So a request at or below `smallRequestBytes` takes shared capacity when
154
+ * that can be granted outright, and otherwise falls back to a separate
155
+ * `reserve` where it queues only among its own kind. It never joins the
156
+ * shared queue, so it is never parked behind a larger owner.
157
+ *
158
+ * Neither side can starve the other. Shared capacity stops admitting small
159
+ * requests the moment anything is queued for it, so a large waiter is never
160
+ * overtaken; and the reserve is held apart from `capacity`, so a
161
+ * full-capacity owner stays grantable while small requests stay servable.
162
+ * Peak occupancy only reaches `capacity + reserve` while the shared lane is
163
+ * contended.
164
+ */
165
+ export class WeightedCreditPool {
166
+ private readonly general: CreditLane;
167
+ private readonly small: CreditLane | null;
168
+ private readonly smallRequestBytes: number;
169
+
170
+ constructor(readonly capacity: number, options: WeightedCreditPoolOptions = {}) {
171
+ if (!Number.isSafeInteger(capacity) || capacity <= 0) {
172
+ throw new RangeError(`weighted credit capacity must be a positive safe integer: ${capacity}`);
173
+ }
174
+ const smallRequestBytes = options.smallRequestBytes ?? 0;
175
+ const reserve = options.reserve ?? 0;
176
+ if (!Number.isSafeInteger(smallRequestBytes) || smallRequestBytes < 0
177
+ || !Number.isSafeInteger(reserve) || reserve < 0) {
178
+ throw new RangeError('weighted credit reserve and small-request size must be non-negative safe integers');
179
+ }
180
+ if (smallRequestBytes > reserve) {
181
+ throw new RangeError(
182
+ `a small request (${smallRequestBytes}) must fit in the reserve (${reserve}) or it could never be granted`,
183
+ );
184
+ }
185
+ this.general = new CreditLane(capacity);
186
+ this.smallRequestBytes = smallRequestBytes;
187
+ this.small = smallRequestBytes > 0 ? new CreditLane(reserve) : null;
188
+ }
189
+
190
+ get stats(): WeightedCreditPoolStats {
191
+ const general = this.general.stats;
192
+ if (!this.small) return general;
193
+ const small = this.small.stats;
194
+ return {
195
+ current: general.current + small.current,
196
+ peak: general.peak + small.peak,
197
+ queued: general.queued + small.queued,
198
+ };
199
+ }
200
+
201
+ private isSmall(bytes: number): boolean {
202
+ return this.small !== null && bytes <= this.smallRequestBytes;
203
+ }
204
+
205
+ tryAcquire(bytes: number): ResizableCreditLease | null {
206
+ this.validateRequest(bytes);
207
+ const shared = this.general.tryAcquire(bytes);
208
+ if (shared || !this.isSmall(bytes)) return shared;
209
+ return this.small!.tryAcquire(bytes);
210
+ }
211
+
212
+ acquire(bytes: number, signal?: AbortSignal): Promise<ResizableCreditLease> {
213
+ try {
214
+ this.validateRequest(bytes);
215
+ } catch (error) {
216
+ return Promise.reject(error as Error);
217
+ }
218
+ if (this.isSmall(bytes)) {
219
+ // Shared capacity while it is free — a small request should get the
220
+ // same concurrency as any other when nothing is contending. It never
221
+ // joins the shared queue, so it cannot be parked behind a large owner.
222
+ const shared = this.general.tryAcquire(bytes);
223
+ if (shared) return Promise.resolve(shared);
224
+ return this.small!.acquire(bytes, signal);
225
+ }
226
+ return this.general.acquire(bytes, signal);
227
+ }
228
+
229
+ private validateRequest(bytes: number): void {
230
+ if (!Number.isSafeInteger(bytes) || bytes <= 0 || bytes > this.capacity) {
231
+ throw new RangeError(
232
+ `weighted credit request must be a positive safe integer no larger than ${this.capacity}: ${bytes}`,
233
+ );
234
+ }
235
+ }
236
+ }
237
+
238
+ function abortError(signal: AbortSignal): DOMException {
239
+ const reason = signal.reason;
240
+ return new DOMException(
241
+ reason instanceof Error ? reason.message : String(reason ?? 'The operation was aborted'),
242
+ 'AbortError',
243
+ );
244
+ }