bun-shared-memory 0.1.8 → 0.2.1

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/src/refcount.ts CHANGED
@@ -160,6 +160,8 @@ export class RefCountCoordinator {
160
160
  private nextWorkerId = 0;
161
161
  /** Free-space threshold (fraction of maxByteLength) that triggers defrag. */
162
162
  private readonly defragThreshold: number;
163
+ /** Minimum number of freed blocks before a table is considered fragmented. */
164
+ private readonly minFreeBlocks: number;
163
165
  private maintenanceTimer: ReturnType<typeof setInterval> | null = null;
164
166
  /** Called after a defragmentation pass with the new chunk lists. */
165
167
  private readonly onDefragment?: (buffers: {
@@ -174,6 +176,7 @@ export class RefCountCoordinator {
174
176
  buffer?: RefCountBuffer,
175
177
  opts: {
176
178
  defragThreshold?: number;
179
+ minFreeBlocks?: number;
177
180
  autoMaintenance?: boolean;
178
181
  maintenanceInterval?: number;
179
182
  onDefragment?: (buffers: {
@@ -198,7 +201,8 @@ export class RefCountCoordinator {
198
201
  mem.attachRefCountBuffer(b.sab);
199
202
  return b;
200
203
  })());
201
- this.defragThreshold = opts.defragThreshold ?? 0.1;
204
+ this.defragThreshold = opts.defragThreshold ?? 0.8;
205
+ this.minFreeBlocks = opts.minFreeBlocks ?? 64;
202
206
  // Start periodic maintenance automatically so refcount events are drained
203
207
  // and dead space is reclaimed without an explicit call.
204
208
  if (opts.autoMaintenance ?? true) {
@@ -298,15 +302,29 @@ export class RefCountCoordinator {
298
302
  }
299
303
  }
300
304
 
301
- /** Whether any table has less than `defragThreshold` free space. */
305
+ /**
306
+ * Whether any table is highly fragmented enough to warrant a defragmentation
307
+ * pass.
308
+ *
309
+ * Writes are lock-free bump allocations: overwriting a value allocates a new
310
+ * block and leaves the old one as dead space (there is no free list to reuse
311
+ * it). The used length therefore grows monotonically even when the live data
312
+ * set stays the same size. When the used length occupies a significant share
313
+ * of the buffer's capacity, a defragmentation pass is warranted to compact
314
+ * the live data and reclaim the dead space.
315
+ *
316
+ * A table triggers defrag when its used length exceeds `defragThreshold` of
317
+ * the buffer's capacity.
318
+ */
302
319
  needsDefrag(): boolean {
303
- const max = this.mem.dataTable.capacity;
304
- const free = (b: { capacity: number; byteLength: number }) =>
305
- (b.capacity - b.byteLength) / max;
320
+ const frag = (b: { capacity: number; byteLength: number }) => {
321
+ if (b.capacity === 0) return false;
322
+ return b.byteLength / b.capacity > this.defragThreshold;
323
+ };
306
324
  return (
307
- free(this.mem.dataTable) < this.defragThreshold ||
308
- free(this.mem.arrayTable) < this.defragThreshold ||
309
- free(this.mem.stringTable) < this.defragThreshold
325
+ frag(this.mem.dataTable) ||
326
+ frag(this.mem.arrayTable) ||
327
+ frag(this.mem.stringTable)
310
328
  );
311
329
  }
312
330
 
@@ -327,7 +345,12 @@ export class RefCountCoordinator {
327
345
  this.maintenanceTimer = setInterval(() => {
328
346
  this.drainEvents();
329
347
  if (this.needsDefrag()) {
348
+ // Set the defrag flag so lock-free writes fall back to the mutex, then
349
+ // wait for any in-flight lock-free writes to finish before compacting.
350
+ this.mem.setDefragmenting();
351
+ this.mem.waitForWritesToFinish();
330
352
  const buffers = this.mem.withLock(() => this.mem.defragment());
353
+ this.mem.clearDefragmenting();
331
354
  this.onDefragment?.(buffers);
332
355
  }
333
356
  }, intervalMs);