@ebowwa/bun-native-page 0.2.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/README.md +64 -0
- package/dist/allocator.d.ts +103 -0
- package/dist/allocator.d.ts.map +1 -0
- package/dist/allocator.js +272 -0
- package/dist/allocator.js.map +1 -0
- package/dist/allocator.ts +334 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +44 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.ts +51 -0
- package/dist/ffi.d.ts +104 -0
- package/dist/ffi.d.ts.map +1 -0
- package/dist/ffi.js +102 -0
- package/dist/ffi.js.map +1 -0
- package/dist/ffi.ts +119 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +294 -0
- package/dist/index.js.map +1 -0
- package/dist/index.ts +433 -0
- package/dist/oom.d.ts +36 -0
- package/dist/oom.d.ts.map +1 -0
- package/dist/oom.js +73 -0
- package/dist/oom.js.map +1 -0
- package/dist/oom.ts +96 -0
- package/native/darwin-arm64/libpage_native.dylib +0 -0
- package/package.json +69 -0
package/dist/index.ts
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ebowwa/bun-native-page
|
|
3
|
+
*
|
|
4
|
+
* Cross-platform page-size aware memory operations for Bun
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from './errors.js';
|
|
8
|
+
export * from './oom.js';
|
|
9
|
+
export * from './allocator.js';
|
|
10
|
+
|
|
11
|
+
import { ffi, ptr, toArrayBuffer } from './ffi.js';
|
|
12
|
+
import { PageSizeError, MapError, ProtectionError, LockError } from './errors.js';
|
|
13
|
+
import { handleOOM, OOMOptions } from './oom.js';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
export interface PageSizeInfo {
|
|
20
|
+
/** System page size in bytes */
|
|
21
|
+
readonly size: number;
|
|
22
|
+
/** true if page size is 4096 bytes */
|
|
23
|
+
readonly is4k: boolean;
|
|
24
|
+
/** true if page size is 16384 bytes */
|
|
25
|
+
readonly is16k: boolean;
|
|
26
|
+
/** true if page size is 65536 bytes */
|
|
27
|
+
readonly is64k: boolean;
|
|
28
|
+
/** Huge page size in bytes (Linux only, 0 if unavailable) */
|
|
29
|
+
readonly hugePageSize: number;
|
|
30
|
+
/** Platform identifier */
|
|
31
|
+
readonly platform: PlatformName;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type PlatformName =
|
|
35
|
+
| 'darwin-arm64'
|
|
36
|
+
| 'darwin-x64'
|
|
37
|
+
| 'linux-x64'
|
|
38
|
+
| 'linux-x86'
|
|
39
|
+
| 'linux-arm64'
|
|
40
|
+
| 'linux-arm'
|
|
41
|
+
| 'windows-x64'
|
|
42
|
+
| 'windows-x86'
|
|
43
|
+
| 'windows-arm64'
|
|
44
|
+
| 'unknown';
|
|
45
|
+
|
|
46
|
+
export type MemoryProtection =
|
|
47
|
+
| 'none'
|
|
48
|
+
| 'read'
|
|
49
|
+
| 'write'
|
|
50
|
+
| 'exec'
|
|
51
|
+
| 'read-write'
|
|
52
|
+
| 'read-exec'
|
|
53
|
+
| 'write-exec'
|
|
54
|
+
| 'all';
|
|
55
|
+
|
|
56
|
+
export type MemoryAdvice =
|
|
57
|
+
| 'normal'
|
|
58
|
+
| 'random'
|
|
59
|
+
| 'sequential'
|
|
60
|
+
| 'willneed'
|
|
61
|
+
| 'dontneed'
|
|
62
|
+
| 'free'
|
|
63
|
+
| 'hugepage'
|
|
64
|
+
| 'nohugepage';
|
|
65
|
+
|
|
66
|
+
export interface AllocatedBuffer {
|
|
67
|
+
/** Pointer to allocated memory */
|
|
68
|
+
readonly ptr: bigint;
|
|
69
|
+
/** Requested size */
|
|
70
|
+
readonly size: number;
|
|
71
|
+
/** Actual allocated size (may be larger due to alignment) */
|
|
72
|
+
readonly allocatedSize: number;
|
|
73
|
+
/** ArrayBuffer view of the memory */
|
|
74
|
+
readonly buffer: ArrayBuffer;
|
|
75
|
+
/** Free the allocation */
|
|
76
|
+
free(): void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface MappedRegion {
|
|
80
|
+
/** Pointer to mapped memory */
|
|
81
|
+
readonly ptr: bigint;
|
|
82
|
+
/** Mapped size */
|
|
83
|
+
readonly size: number;
|
|
84
|
+
/** ArrayBuffer view */
|
|
85
|
+
readonly buffer: ArrayBuffer;
|
|
86
|
+
/** Unmap the region */
|
|
87
|
+
unmap(): void;
|
|
88
|
+
/** Change memory protection */
|
|
89
|
+
protect(prot: MemoryProtection): void;
|
|
90
|
+
/** Advise kernel about usage pattern */
|
|
91
|
+
advise(advice: MemoryAdvice): void;
|
|
92
|
+
/** Lock pages in memory (prevent swap) */
|
|
93
|
+
lock(): void;
|
|
94
|
+
/** Unlock pages */
|
|
95
|
+
unlock(): void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Constants
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
const PROTECTION_MAP: Record<MemoryProtection, number> = {
|
|
103
|
+
'none': 0,
|
|
104
|
+
'read': 1,
|
|
105
|
+
'write': 2,
|
|
106
|
+
'exec': 4,
|
|
107
|
+
'read-write': 3,
|
|
108
|
+
'read-exec': 5,
|
|
109
|
+
'write-exec': 6,
|
|
110
|
+
'all': 7,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const ADVICE_MAP: Record<MemoryAdvice, number> = {
|
|
114
|
+
'normal': 0,
|
|
115
|
+
'random': 1,
|
|
116
|
+
'sequential': 2,
|
|
117
|
+
'willneed': 3,
|
|
118
|
+
'dontneed': 4,
|
|
119
|
+
'free': 5,
|
|
120
|
+
'hugepage': 6,
|
|
121
|
+
'nohugepage': 7,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const PLATFORM_NAMES: Record<number, PlatformName> = {
|
|
125
|
+
0: 'darwin-arm64',
|
|
126
|
+
1: 'darwin-x64',
|
|
127
|
+
2: 'linux-x64',
|
|
128
|
+
3: 'linux-x86',
|
|
129
|
+
4: 'linux-arm64',
|
|
130
|
+
5: 'linux-arm',
|
|
131
|
+
6: 'windows-x64',
|
|
132
|
+
7: 'windows-x86',
|
|
133
|
+
8: 'windows-arm64',
|
|
134
|
+
99: 'unknown',
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Cached page size info
|
|
138
|
+
let cachedPageSizeInfo: PageSizeInfo | null = null;
|
|
139
|
+
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Page Size API
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get system page size information
|
|
146
|
+
*/
|
|
147
|
+
export function getPageSize(): PageSizeInfo {
|
|
148
|
+
if (cachedPageSizeInfo) {
|
|
149
|
+
return cachedPageSizeInfo;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const size = Number(ffi.getSize());
|
|
153
|
+
const hugePageSize = Number(ffi.getHugePageSize());
|
|
154
|
+
const platformId = Number(ffi.getPlatform());
|
|
155
|
+
|
|
156
|
+
cachedPageSizeInfo = {
|
|
157
|
+
size,
|
|
158
|
+
is4k: size === 4096,
|
|
159
|
+
is16k: size === 16384,
|
|
160
|
+
is64k: size === 65536,
|
|
161
|
+
hugePageSize,
|
|
162
|
+
platform: PLATFORM_NAMES[platformId] ?? 'unknown',
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return cachedPageSizeInfo;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Round size up to nearest page boundary
|
|
170
|
+
*/
|
|
171
|
+
export function alignToPage(size: number): number {
|
|
172
|
+
return Number(ffi.alignSize(BigInt(size)));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Round size down to nearest page boundary
|
|
177
|
+
*/
|
|
178
|
+
export function truncateToPage(size: number): number {
|
|
179
|
+
return Number(ffi.truncateSize(BigInt(size)));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if a pointer is page-aligned
|
|
184
|
+
*/
|
|
185
|
+
export function isPageAligned(ptr: bigint | number): boolean {
|
|
186
|
+
const result = ffi.isAligned(BigInt(ptr));
|
|
187
|
+
return result !== 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// Allocation API
|
|
192
|
+
// ============================================================================
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Allocate page-aligned memory
|
|
196
|
+
*/
|
|
197
|
+
export function allocPageAligned(
|
|
198
|
+
size: number,
|
|
199
|
+
options?: OOMOptions
|
|
200
|
+
): AllocatedBuffer | null {
|
|
201
|
+
if (size <= 0) {
|
|
202
|
+
throw new PageSizeError('Size must be positive');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (size > Number.MAX_SAFE_INTEGER) {
|
|
206
|
+
throw new PageSizeError('Size exceeds safe integer range');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const allocHandle = ffi.alloc(BigInt(size));
|
|
210
|
+
|
|
211
|
+
if (allocHandle === null || allocHandle === 0n) {
|
|
212
|
+
return handleOOM(size, options) as never;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const actualPtr = ffi.allocPtr(allocHandle);
|
|
216
|
+
const actualSize = Number(ffi.allocSize(allocHandle));
|
|
217
|
+
|
|
218
|
+
if (actualPtr === null || actualPtr === 0n) {
|
|
219
|
+
ffi.free(allocHandle);
|
|
220
|
+
return handleOOM(size, options) as never;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let freed = false;
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
ptr: BigInt(actualPtr as unknown as number),
|
|
227
|
+
size,
|
|
228
|
+
allocatedSize: actualSize,
|
|
229
|
+
get buffer() {
|
|
230
|
+
if (freed) {
|
|
231
|
+
throw new PageSizeError('Cannot access freed buffer');
|
|
232
|
+
}
|
|
233
|
+
return toArrayBuffer(actualPtr as unknown as number, 0, actualSize);
|
|
234
|
+
},
|
|
235
|
+
free() {
|
|
236
|
+
if (!freed) {
|
|
237
|
+
freed = true;
|
|
238
|
+
ffi.free(allocHandle);
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ============================================================================
|
|
245
|
+
// mmap API
|
|
246
|
+
// ============================================================================
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Map anonymous memory (no file backing)
|
|
250
|
+
*/
|
|
251
|
+
export function mmapAnonymous(
|
|
252
|
+
size: number,
|
|
253
|
+
prot: MemoryProtection = 'read-write'
|
|
254
|
+
): MappedRegion {
|
|
255
|
+
if (size <= 0) {
|
|
256
|
+
throw new PageSizeError('Size must be positive');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const protValue = PROTECTION_MAP[prot];
|
|
260
|
+
const regionHandle = ffi.mmapAnonymous(BigInt(size), protValue);
|
|
261
|
+
|
|
262
|
+
if (regionHandle === null || regionHandle === 0n) {
|
|
263
|
+
throw new MapError(size, 'mmap failed');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const actualPtr = ffi.mmapPtr(regionHandle);
|
|
267
|
+
const actualSize = Number(ffi.mmapSize(regionHandle));
|
|
268
|
+
|
|
269
|
+
if (actualPtr === null || actualPtr === 0n) {
|
|
270
|
+
throw new MapError(size, 'mmap returned null pointer');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let unmapped = false;
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
ptr: BigInt(actualPtr as unknown as number),
|
|
277
|
+
size: actualSize,
|
|
278
|
+
get buffer() {
|
|
279
|
+
if (unmapped) {
|
|
280
|
+
throw new PageSizeError('Cannot access unmapped region');
|
|
281
|
+
}
|
|
282
|
+
return toArrayBuffer(actualPtr as unknown as number, 0, actualSize);
|
|
283
|
+
},
|
|
284
|
+
unmap() {
|
|
285
|
+
if (!unmapped) {
|
|
286
|
+
unmapped = true;
|
|
287
|
+
const result = ffi.munmap(regionHandle);
|
|
288
|
+
if (result !== 0) {
|
|
289
|
+
throw new PageSizeError('munmap failed');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
protect(prot: MemoryProtection) {
|
|
294
|
+
const protValue = PROTECTION_MAP[prot];
|
|
295
|
+
const result = ffi.mprotect(actualPtr as unknown as number, BigInt(actualSize), protValue);
|
|
296
|
+
if (result !== 0) {
|
|
297
|
+
throw new ProtectionError(`mprotect failed with code ${result}`);
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
advise(advice: MemoryAdvice) {
|
|
301
|
+
const adviceValue = ADVICE_MAP[advice];
|
|
302
|
+
ffi.madvise(actualPtr as unknown as number, BigInt(actualSize), adviceValue);
|
|
303
|
+
},
|
|
304
|
+
lock() {
|
|
305
|
+
const result = ffi.mlock(actualPtr as unknown as number, BigInt(actualSize));
|
|
306
|
+
if (result !== 0) {
|
|
307
|
+
throw new LockError(`mlock failed with code ${result}`);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
unlock() {
|
|
311
|
+
const result = ffi.munlock(actualPtr as unknown as number, BigInt(actualSize));
|
|
312
|
+
if (result !== 0) {
|
|
313
|
+
throw new LockError(`munlock failed with code ${result}`);
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Map a file into memory
|
|
321
|
+
*/
|
|
322
|
+
export function mmapFile(
|
|
323
|
+
fd: number,
|
|
324
|
+
size: number,
|
|
325
|
+
offset: number = 0,
|
|
326
|
+
prot: MemoryProtection = 'read-write'
|
|
327
|
+
): MappedRegion {
|
|
328
|
+
if (size <= 0) {
|
|
329
|
+
throw new PageSizeError('Size must be positive');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const protValue = PROTECTION_MAP[prot];
|
|
333
|
+
const regionHandle = ffi.mmapFile(fd, BigInt(size), BigInt(offset), protValue);
|
|
334
|
+
|
|
335
|
+
if (regionHandle === null || regionHandle === 0n) {
|
|
336
|
+
throw new MapError(size, 'mmap file failed');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const actualPtr = ffi.mmapPtr(regionHandle);
|
|
340
|
+
const actualSize = Number(ffi.mmapSize(regionHandle));
|
|
341
|
+
|
|
342
|
+
if (actualPtr === null || actualPtr === 0n) {
|
|
343
|
+
throw new MapError(size, 'mmap file returned null pointer');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let unmapped = false;
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
ptr: BigInt(actualPtr as unknown as number),
|
|
350
|
+
size: actualSize,
|
|
351
|
+
get buffer() {
|
|
352
|
+
if (unmapped) {
|
|
353
|
+
throw new PageSizeError('Cannot access unmapped region');
|
|
354
|
+
}
|
|
355
|
+
return toArrayBuffer(actualPtr as unknown as number, 0, actualSize);
|
|
356
|
+
},
|
|
357
|
+
unmap() {
|
|
358
|
+
if (!unmapped) {
|
|
359
|
+
unmapped = true;
|
|
360
|
+
ffi.munmap(regionHandle);
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
protect(prot: MemoryProtection) {
|
|
364
|
+
const protValue = PROTECTION_MAP[prot];
|
|
365
|
+
ffi.mprotect(actualPtr as unknown as number, BigInt(actualSize), protValue);
|
|
366
|
+
},
|
|
367
|
+
advise(advice: MemoryAdvice) {
|
|
368
|
+
const adviceValue = ADVICE_MAP[advice];
|
|
369
|
+
ffi.madvise(actualPtr as unknown as number, BigInt(actualSize), adviceValue);
|
|
370
|
+
},
|
|
371
|
+
lock() {
|
|
372
|
+
ffi.mlock(actualPtr as unknown as number, BigInt(actualSize));
|
|
373
|
+
},
|
|
374
|
+
unlock() {
|
|
375
|
+
ffi.munlock(actualPtr as unknown as number, BigInt(actualSize));
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Map huge pages (Linux only, falls back to regular pages on other platforms)
|
|
382
|
+
*/
|
|
383
|
+
export function mmapHuge(size: number): MappedRegion | null {
|
|
384
|
+
if (size <= 0) {
|
|
385
|
+
throw new PageSizeError('Size must be positive');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const regionHandle = ffi.mmapHuge(BigInt(size));
|
|
389
|
+
|
|
390
|
+
if (regionHandle === null || regionHandle === 0n) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const actualPtr = ffi.mmapPtr(regionHandle);
|
|
395
|
+
const actualSize = Number(ffi.mmapSize(regionHandle));
|
|
396
|
+
|
|
397
|
+
if (actualPtr === null || actualPtr === 0n) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let unmapped = false;
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
ptr: BigInt(actualPtr as unknown as number),
|
|
405
|
+
size: actualSize,
|
|
406
|
+
get buffer() {
|
|
407
|
+
if (unmapped) {
|
|
408
|
+
throw new PageSizeError('Cannot access unmapped region');
|
|
409
|
+
}
|
|
410
|
+
return toArrayBuffer(actualPtr as unknown as number, 0, actualSize);
|
|
411
|
+
},
|
|
412
|
+
unmap() {
|
|
413
|
+
if (!unmapped) {
|
|
414
|
+
unmapped = true;
|
|
415
|
+
ffi.munmap(regionHandle);
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
protect(prot: MemoryProtection) {
|
|
419
|
+
const protValue = PROTECTION_MAP[prot];
|
|
420
|
+
ffi.mprotect(actualPtr as unknown as number, BigInt(actualSize), protValue);
|
|
421
|
+
},
|
|
422
|
+
advise(advice: MemoryAdvice) {
|
|
423
|
+
const adviceValue = ADVICE_MAP[advice];
|
|
424
|
+
ffi.madvise(actualPtr as unknown as number, BigInt(actualSize), adviceValue);
|
|
425
|
+
},
|
|
426
|
+
lock() {
|
|
427
|
+
ffi.mlock(actualPtr as unknown as number, BigInt(actualSize));
|
|
428
|
+
},
|
|
429
|
+
unlock() {
|
|
430
|
+
ffi.munlock(actualPtr as unknown as number, BigInt(actualSize));
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
}
|
package/dist/oom.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OOM (Out of Memory) handling strategies
|
|
3
|
+
*/
|
|
4
|
+
export type OOMStrategy = 'throw' | 'null' | 'callback' | 'fallback';
|
|
5
|
+
export interface OOMOptions {
|
|
6
|
+
strategy?: OOMStrategy;
|
|
7
|
+
onOOM?: (requestedSize: number) => number | null;
|
|
8
|
+
fallbackSize?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Set global OOM strategy
|
|
12
|
+
*/
|
|
13
|
+
export declare function setOOMStrategy(strategy: OOMStrategy): void;
|
|
14
|
+
/**
|
|
15
|
+
* Get current global OOM strategy
|
|
16
|
+
*/
|
|
17
|
+
export declare function getOOMStrategy(): OOMStrategy;
|
|
18
|
+
/**
|
|
19
|
+
* Set global OOM callback (for 'callback' strategy)
|
|
20
|
+
*/
|
|
21
|
+
export declare function setOOMCallback(callback: (size: number) => number | null): void;
|
|
22
|
+
/**
|
|
23
|
+
* Set global fallback size (for 'fallback' strategy)
|
|
24
|
+
*/
|
|
25
|
+
export declare function setFallbackSize(size: number): void;
|
|
26
|
+
/**
|
|
27
|
+
* Handle OOM according to strategy
|
|
28
|
+
* Returns null if strategy allows, otherwise throws
|
|
29
|
+
*/
|
|
30
|
+
export declare function handleOOM(requestedSize: number, options?: OOMOptions): number | null;
|
|
31
|
+
/**
|
|
32
|
+
* Check if allocation would succeed (probe)
|
|
33
|
+
* Useful for 'fallback' strategy
|
|
34
|
+
*/
|
|
35
|
+
export declare function wouldAllocSucceed(size: number): boolean;
|
|
36
|
+
//# sourceMappingURL=oom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oom.d.ts","sourceRoot":"","sources":["oom.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;AAErE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,KAAK,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAOD;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAE1D;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAE5C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,IAAI,CAE9E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAElD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,UAAU,GACnB,MAAM,GAAG,IAAI,CAyBf;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYvD"}
|
package/dist/oom.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OOM (Out of Memory) handling strategies
|
|
3
|
+
*/
|
|
4
|
+
// Global OOM strategy
|
|
5
|
+
let globalOOMStrategy = 'throw';
|
|
6
|
+
let globalOOMCallback = null;
|
|
7
|
+
let globalFallbackSize = 4096;
|
|
8
|
+
/**
|
|
9
|
+
* Set global OOM strategy
|
|
10
|
+
*/
|
|
11
|
+
export function setOOMStrategy(strategy) {
|
|
12
|
+
globalOOMStrategy = strategy;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get current global OOM strategy
|
|
16
|
+
*/
|
|
17
|
+
export function getOOMStrategy() {
|
|
18
|
+
return globalOOMStrategy;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Set global OOM callback (for 'callback' strategy)
|
|
22
|
+
*/
|
|
23
|
+
export function setOOMCallback(callback) {
|
|
24
|
+
globalOOMCallback = callback;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Set global fallback size (for 'fallback' strategy)
|
|
28
|
+
*/
|
|
29
|
+
export function setFallbackSize(size) {
|
|
30
|
+
globalFallbackSize = size;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Handle OOM according to strategy
|
|
34
|
+
* Returns null if strategy allows, otherwise throws
|
|
35
|
+
*/
|
|
36
|
+
export function handleOOM(requestedSize, options) {
|
|
37
|
+
const strategy = options?.strategy ?? globalOOMStrategy;
|
|
38
|
+
switch (strategy) {
|
|
39
|
+
case 'null':
|
|
40
|
+
return null;
|
|
41
|
+
case 'callback': {
|
|
42
|
+
const callback = options?.onOOM ?? globalOOMCallback;
|
|
43
|
+
if (callback) {
|
|
44
|
+
return callback(requestedSize);
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
case 'fallback': {
|
|
49
|
+
return options?.fallbackSize ?? globalFallbackSize;
|
|
50
|
+
}
|
|
51
|
+
case 'throw':
|
|
52
|
+
default:
|
|
53
|
+
throw new Error(`Out of memory: failed to allocate ${requestedSize} bytes`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if allocation would succeed (probe)
|
|
58
|
+
* Useful for 'fallback' strategy
|
|
59
|
+
*/
|
|
60
|
+
export function wouldAllocSucceed(size) {
|
|
61
|
+
try {
|
|
62
|
+
// This is a heuristic - actual allocation may still fail
|
|
63
|
+
const totalMem = process.memoryUsage().heapTotal;
|
|
64
|
+
const usedMem = process.memoryUsage().heapUsed;
|
|
65
|
+
const available = totalMem - usedMem;
|
|
66
|
+
// Be conservative - require 2x the requested size
|
|
67
|
+
return available >= size * 2;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=oom.js.map
|
package/dist/oom.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oom.js","sourceRoot":"","sources":["oom.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,sBAAsB;AACtB,IAAI,iBAAiB,GAAgB,OAAO,CAAC;AAC7C,IAAI,iBAAiB,GAA6C,IAAI,CAAC;AACvE,IAAI,kBAAkB,GAAW,IAAI,CAAC;AAEtC;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,QAAqB;IAClD,iBAAiB,GAAG,QAAQ,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,QAAyC;IACtE,iBAAiB,GAAG,QAAQ,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,kBAAkB,GAAG,IAAI,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CACvB,aAAqB,EACrB,OAAoB;IAEpB,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,iBAAiB,CAAC;IAExD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,MAAM;YACT,OAAO,IAAI,CAAC;QAEd,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,QAAQ,GAAG,OAAO,EAAE,KAAK,IAAI,iBAAiB,CAAC;YACrD,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,QAAQ,CAAC,aAAa,CAAC,CAAC;YACjC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,OAAO,OAAO,EAAE,YAAY,IAAI,kBAAkB,CAAC;QACrD,CAAC;QAED,KAAK,OAAO,CAAC;QACb;YACE,MAAM,IAAI,KAAK,CACb,qCAAqC,aAAa,QAAQ,CAC3D,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,yDAAyD;QACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC;QACjD,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC;QAC/C,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;QAErC,kDAAkD;QAClD,OAAO,SAAS,IAAI,IAAI,GAAG,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/dist/oom.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OOM (Out of Memory) handling strategies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type OOMStrategy = 'throw' | 'null' | 'callback' | 'fallback';
|
|
6
|
+
|
|
7
|
+
export interface OOMOptions {
|
|
8
|
+
strategy?: OOMStrategy;
|
|
9
|
+
onOOM?: (requestedSize: number) => number | null;
|
|
10
|
+
fallbackSize?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Global OOM strategy
|
|
14
|
+
let globalOOMStrategy: OOMStrategy = 'throw';
|
|
15
|
+
let globalOOMCallback: ((size: number) => number | null) | null = null;
|
|
16
|
+
let globalFallbackSize: number = 4096;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Set global OOM strategy
|
|
20
|
+
*/
|
|
21
|
+
export function setOOMStrategy(strategy: OOMStrategy): void {
|
|
22
|
+
globalOOMStrategy = strategy;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get current global OOM strategy
|
|
27
|
+
*/
|
|
28
|
+
export function getOOMStrategy(): OOMStrategy {
|
|
29
|
+
return globalOOMStrategy;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Set global OOM callback (for 'callback' strategy)
|
|
34
|
+
*/
|
|
35
|
+
export function setOOMCallback(callback: (size: number) => number | null): void {
|
|
36
|
+
globalOOMCallback = callback;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set global fallback size (for 'fallback' strategy)
|
|
41
|
+
*/
|
|
42
|
+
export function setFallbackSize(size: number): void {
|
|
43
|
+
globalFallbackSize = size;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Handle OOM according to strategy
|
|
48
|
+
* Returns null if strategy allows, otherwise throws
|
|
49
|
+
*/
|
|
50
|
+
export function handleOOM(
|
|
51
|
+
requestedSize: number,
|
|
52
|
+
options?: OOMOptions
|
|
53
|
+
): number | null {
|
|
54
|
+
const strategy = options?.strategy ?? globalOOMStrategy;
|
|
55
|
+
|
|
56
|
+
switch (strategy) {
|
|
57
|
+
case 'null':
|
|
58
|
+
return null;
|
|
59
|
+
|
|
60
|
+
case 'callback': {
|
|
61
|
+
const callback = options?.onOOM ?? globalOOMCallback;
|
|
62
|
+
if (callback) {
|
|
63
|
+
return callback(requestedSize);
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case 'fallback': {
|
|
69
|
+
return options?.fallbackSize ?? globalFallbackSize;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
case 'throw':
|
|
73
|
+
default:
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Out of memory: failed to allocate ${requestedSize} bytes`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if allocation would succeed (probe)
|
|
82
|
+
* Useful for 'fallback' strategy
|
|
83
|
+
*/
|
|
84
|
+
export function wouldAllocSucceed(size: number): boolean {
|
|
85
|
+
try {
|
|
86
|
+
// This is a heuristic - actual allocation may still fail
|
|
87
|
+
const totalMem = process.memoryUsage().heapTotal;
|
|
88
|
+
const usedMem = process.memoryUsage().heapUsed;
|
|
89
|
+
const available = totalMem - usedMem;
|
|
90
|
+
|
|
91
|
+
// Be conservative - require 2x the requested size
|
|
92
|
+
return available >= size * 2;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
Binary file
|