abxbus 2.5.1 → 2.5.4

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.
@@ -0,0 +1,86 @@
1
+ import { z } from 'zod';
2
+ import { BaseEvent } from './base_event.js';
3
+ import type { EventBus } from './event_bus.js';
4
+ import { EventHandler } from './event_handler.js';
5
+ import { type HandlerLock } from './lock_manager.js';
6
+ import type { Deferred } from './lock_manager.js';
7
+ import type { EventResultType } from './types.js';
8
+ export type EventResultStatus = 'pending' | 'started' | 'completed' | 'error';
9
+ export declare const EventResultJSONSchema: z.ZodObject<{
10
+ id: z.ZodString;
11
+ status: z.ZodEnum<{
12
+ pending: "pending";
13
+ started: "started";
14
+ completed: "completed";
15
+ error: "error";
16
+ }>;
17
+ event_id: z.ZodString;
18
+ handler_id: z.ZodString;
19
+ handler_name: z.ZodString;
20
+ handler_file_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
21
+ handler_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
22
+ handler_slow_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
23
+ handler_registered_at: z.ZodOptional<z.ZodString>;
24
+ handler_event_pattern: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"*">]>>;
25
+ eventbus_name: z.ZodString;
26
+ eventbus_id: z.ZodString;
27
+ started_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
28
+ completed_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
29
+ result: z.ZodOptional<z.ZodUnknown>;
30
+ error: z.ZodOptional<z.ZodUnknown>;
31
+ event_children: z.ZodArray<z.ZodString>;
32
+ }, z.core.$strict>;
33
+ export type EventResultJSON = z.infer<typeof EventResultJSONSchema>;
34
+ export declare class EventResult<TEvent extends BaseEvent = BaseEvent> {
35
+ id: string;
36
+ status: EventResultStatus;
37
+ event: TEvent;
38
+ handler: EventHandler;
39
+ started_at: string | null;
40
+ completed_at: string | null;
41
+ result?: EventResultType<TEvent>;
42
+ error?: unknown;
43
+ event_children: BaseEvent[];
44
+ _abort: Deferred<never> | null;
45
+ _lock: HandlerLock | null;
46
+ _queue_jump_pause_releases: Map<EventBus, () => void> | null;
47
+ constructor(params: {
48
+ event: TEvent;
49
+ handler: EventHandler;
50
+ });
51
+ toString(): string;
52
+ get event_id(): string;
53
+ get bus(): EventBus;
54
+ get handler_id(): string;
55
+ get handler_name(): string;
56
+ get handler_file_path(): string | null;
57
+ get eventbus_name(): string;
58
+ get eventbus_id(): string;
59
+ get eventbus_label(): string;
60
+ private getHookBus;
61
+ private _notifyStatusHook;
62
+ get value(): EventResultType<TEvent> | undefined;
63
+ get result_type(): TEvent['event_result_type'];
64
+ _linkEmittedChildEvent(child_event: BaseEvent): void;
65
+ get raw_value(): EventResultType<TEvent> | undefined;
66
+ get handler_timeout(): number | null;
67
+ get handler_slow_timeout(): number | null;
68
+ _createSlowHandlerWarningTimer(effective_timeout: number | null): ReturnType<typeof setTimeout> | null;
69
+ _ensureQueueJumpPause(bus: EventBus): void;
70
+ _releaseQueueJumpPauses(): void;
71
+ update(params: {
72
+ status?: EventResultStatus;
73
+ result?: EventResultType<TEvent> | BaseEvent | undefined;
74
+ error?: unknown;
75
+ }): this;
76
+ private _createHandlerTimeoutError;
77
+ private _handleHandlerError;
78
+ private _onHandlerExit;
79
+ runHandler(handler_lock: HandlerLock | null): Promise<void>;
80
+ _signalAbort(error: Error): void;
81
+ _markStarted(notify_hook?: boolean): Promise<never>;
82
+ _markCompleted(result: EventResultType<TEvent> | BaseEvent | undefined, notify_hook?: boolean): void;
83
+ _markError(error: unknown, notify_hook?: boolean): void;
84
+ toJSON(): EventResultJSON;
85
+ static fromJSON<TEvent extends BaseEvent>(event: TEvent, data: unknown): EventResult<TEvent>;
86
+ }
@@ -0,0 +1,70 @@
1
+ import type { BaseEvent } from './base_event.js';
2
+ import type { EventResult } from './event_result.js';
3
+ export type Deferred<T> = {
4
+ promise: Promise<T>;
5
+ resolve: (value: T | PromiseLike<T>) => void;
6
+ reject: (reason?: unknown) => void;
7
+ };
8
+ export declare const withResolvers: <T>() => Deferred<T>;
9
+ export declare const EVENT_CONCURRENCY_MODES: readonly ["global-serial", "bus-serial", "parallel"];
10
+ export type EventConcurrencyMode = (typeof EVENT_CONCURRENCY_MODES)[number];
11
+ export declare const EVENT_HANDLER_CONCURRENCY_MODES: readonly ["serial", "parallel"];
12
+ export type EventHandlerConcurrencyMode = (typeof EVENT_HANDLER_CONCURRENCY_MODES)[number];
13
+ export declare const EVENT_HANDLER_COMPLETION_MODES: readonly ["all", "first"];
14
+ export type EventHandlerCompletionMode = (typeof EVENT_HANDLER_COMPLETION_MODES)[number];
15
+ export declare class AsyncLock {
16
+ size: number;
17
+ in_use: number;
18
+ waiters: Array<() => void>;
19
+ constructor(size: number);
20
+ acquire(): Promise<void>;
21
+ release(): void;
22
+ }
23
+ export declare const runWithLock: <T>(lock: AsyncLock | null, fn: () => Promise<T>) => Promise<T>;
24
+ export type HandlerExecutionState = 'held' | 'yielded' | 'closed';
25
+ export declare class HandlerLock {
26
+ private lock;
27
+ private state;
28
+ constructor(lock: AsyncLock | null);
29
+ yieldHandlerLockForChildRun(): boolean;
30
+ reclaimHandlerLockIfRunning(): Promise<boolean>;
31
+ exitHandlerRun(): void;
32
+ runQueueJump<T>(fn: () => Promise<T>): Promise<T>;
33
+ }
34
+ export type EventBusInterfaceForLockManager = {
35
+ isIdleAndQueueEmpty: () => boolean;
36
+ event_concurrency: EventConcurrencyMode;
37
+ _lock_for_event_global_serial: AsyncLock;
38
+ };
39
+ export type LockManagerOptions = {
40
+ auto_schedule_idle_checks?: boolean;
41
+ };
42
+ export declare class LockManager {
43
+ private bus;
44
+ private auto_schedule_idle_checks;
45
+ readonly bus_event_lock: AsyncLock;
46
+ private pause_depth;
47
+ private pause_waiters;
48
+ private active_handler_results;
49
+ private idle_waiters;
50
+ private idle_check_pending;
51
+ private idle_check_streak;
52
+ constructor(bus: EventBusInterfaceForLockManager, options?: LockManagerOptions);
53
+ _requestRunloopPause(): () => void;
54
+ _waitUntilRunloopResumed(): Promise<void>;
55
+ _isPaused(): boolean;
56
+ _runWithHandlerDispatchContext<T>(result: EventResult, fn: () => Promise<T>): Promise<T>;
57
+ _getActiveHandlerResultForCurrentAsyncContext(): EventResult | undefined;
58
+ _getActiveHandlerResults(): EventResult[];
59
+ _isAnyHandlerActive(): boolean;
60
+ waitForIdle(timeout_seconds?: number | null): Promise<boolean>;
61
+ _notifyIdleListeners(): void;
62
+ getLockForEvent(event: BaseEvent): AsyncLock | null;
63
+ _runWithEventLock<T>(event: BaseEvent, fn: () => Promise<T>, options?: {
64
+ bypass_event_locks?: boolean;
65
+ pre_acquired_lock?: AsyncLock | null;
66
+ }): Promise<T>;
67
+ _runWithHandlerLock<T>(event: BaseEvent, default_handler_concurrency: EventHandlerConcurrencyMode | undefined, fn: (lock: HandlerLock | null) => Promise<T>): Promise<T>;
68
+ private scheduleIdleCheck;
69
+ clear(): void;
70
+ }
@@ -1,4 +1,11 @@
1
1
  type SemaphoreScope = 'multiprocess' | 'global' | 'class' | 'instance';
2
+ type AnyFunction = (this: any, ...args: any[]) => any;
3
+ type LegacyMethodDescriptor = TypedPropertyDescriptor<AnyFunction>;
4
+ type RetryDecorator = {
5
+ <T extends AnyFunction>(target: T): T;
6
+ <T extends AnyFunction>(target: T, context: ClassMethodDecoratorContext): T;
7
+ (target: object, property_key: string | symbol, descriptor: LegacyMethodDescriptor): LegacyMethodDescriptor;
8
+ };
2
9
  export interface RetryOptions {
3
10
  /** Total number of attempts including the initial call (1 = no retry, 3 = up to 2 retries). Default: 1 */
4
11
  max_attempts?: number;
@@ -51,5 +58,5 @@ export declare class SemaphoreTimeoutError extends Error {
51
58
  }
52
59
  /** Reset the global semaphore registry. Useful in tests. */
53
60
  export declare function clearSemaphoreRegistry(): void;
54
- export declare function retry(options?: RetryOptions): <T extends (...args: any[]) => any>(target: T, _context?: ClassMethodDecoratorContext) => T;
61
+ export declare function retry(options?: RetryOptions): RetryDecorator;
55
62
  export {};
package/dist/cjs/retry.js CHANGED
@@ -84,12 +84,18 @@ class RetrySemaphore {
84
84
  this.inUse = 0;
85
85
  this.waiters = [];
86
86
  }
87
- async acquire() {
87
+ tryAcquire() {
88
88
  if (this.size === Infinity) {
89
- return;
89
+ return true;
90
90
  }
91
91
  if (this.inUse < this.size) {
92
92
  this.inUse += 1;
93
+ return true;
94
+ }
95
+ return false;
96
+ }
97
+ async acquire() {
98
+ if (this.tryAcquire()) {
93
99
  return;
94
100
  }
95
101
  await new Promise((resolve) => {
@@ -133,11 +139,51 @@ function retry(options = {}) {
133
139
  semaphore_scope = "global",
134
140
  semaphore_timeout
135
141
  } = options;
136
- return function decorator(target, _context) {
142
+ const decorateFunction = (target, _context) => {
137
143
  const fn_name = target.name || _context?.name || "anonymous";
138
144
  const effective_max_attempts = Math.max(1, max_attempts);
139
145
  const effective_retry_after = Math.max(0, retry_after);
140
- async function retryWrapper(...args) {
146
+ const shouldRetry = (error) => {
147
+ if (!retry_on_errors || retry_on_errors.length === 0) return true;
148
+ return retry_on_errors.some(
149
+ (matcher) => typeof matcher === "string" ? error?.name === matcher : matcher instanceof RegExp ? matcher.test(String(error)) : error instanceof matcher
150
+ );
151
+ };
152
+ const asyncRetryDelay = async (attempt) => {
153
+ const delay_seconds = effective_retry_after * Math.pow(retry_backoff_factor, attempt - 1);
154
+ if (delay_seconds > 0) {
155
+ await sleep(delay_seconds * 1e3);
156
+ }
157
+ };
158
+ const syncRetryDelay = (attempt) => {
159
+ const delay_seconds = effective_retry_after * Math.pow(retry_backoff_factor, attempt - 1);
160
+ if (delay_seconds > 0) {
161
+ sleepSync(delay_seconds * 1e3);
162
+ }
163
+ };
164
+ const runRetryLoopFromThenable = async (this_arg, args, first_thenable, first_attempt) => {
165
+ let current_result = first_thenable;
166
+ for (let attempt = first_attempt; attempt <= effective_max_attempts; attempt++) {
167
+ try {
168
+ if (attempt !== first_attempt) {
169
+ current_result = target.apply(this_arg, args);
170
+ }
171
+ if (isThenable(current_result)) {
172
+ if (timeout != null && timeout > 0) {
173
+ return await _runWithTimeout(() => Promise.resolve(current_result), timeout * 1e3, attempt);
174
+ }
175
+ return await current_result;
176
+ }
177
+ return current_result;
178
+ } catch (error) {
179
+ if (!shouldRetry(error)) throw error;
180
+ if (attempt >= effective_max_attempts) throw error;
181
+ await asyncRetryDelay(attempt);
182
+ }
183
+ }
184
+ throw new Error(`retry(${fn_name}): unexpected end of retry loop`);
185
+ };
186
+ async function asyncRetryWrapper(...args) {
141
187
  const base_name = typeof semaphore_name_option === "function" ? semaphore_name_option(...args) : semaphore_name_option ?? fn_name;
142
188
  const sem_name = typeof base_name === "string" ? base_name : String(base_name);
143
189
  const scoped_key = scopedSemaphoreKey(sem_name, semaphore_scope, this);
@@ -202,17 +248,9 @@ function retry(options = {}) {
202
248
  return await Promise.resolve(target.apply(this, args));
203
249
  }
204
250
  } catch (error) {
205
- if (retry_on_errors && retry_on_errors.length > 0) {
206
- const is_retryable = retry_on_errors.some(
207
- (matcher) => typeof matcher === "string" ? error?.name === matcher : matcher instanceof RegExp ? matcher.test(String(error)) : error instanceof matcher
208
- );
209
- if (!is_retryable) throw error;
210
- }
251
+ if (!shouldRetry(error)) throw error;
211
252
  if (attempt >= effective_max_attempts) throw error;
212
- const delay_seconds = effective_retry_after * Math.pow(retry_backoff_factor, attempt - 1);
213
- if (delay_seconds > 0) {
214
- await sleep(delay_seconds * 1e3);
215
- }
253
+ await asyncRetryDelay(attempt);
216
254
  }
217
255
  }
218
256
  throw new Error(`retry(${fn_name}): unexpected end of retry loop`);
@@ -227,6 +265,79 @@ function retry(options = {}) {
227
265
  }
228
266
  }
229
267
  }
268
+ function syncRetryWrapper(...args) {
269
+ const base_name = typeof semaphore_name_option === "function" ? semaphore_name_option(...args) : semaphore_name_option ?? fn_name;
270
+ const sem_name = typeof base_name === "string" ? base_name : String(base_name);
271
+ const scoped_key = scopedSemaphoreKey(sem_name, semaphore_scope, this);
272
+ const held = getHeldSemaphores();
273
+ const needs_semaphore = semaphore_limit != null && semaphore_limit > 0;
274
+ const is_reentrant = needs_semaphore && held.has(scoped_key);
275
+ let semaphore = null;
276
+ let multiprocess_lock = null;
277
+ let semaphore_acquired = false;
278
+ if (needs_semaphore && !is_reentrant) {
279
+ const effective_sem_timeout = semaphore_timeout != null ? semaphore_timeout : timeout != null ? timeout * Math.max(1, semaphore_limit - 1) : null;
280
+ if (semaphore_scope === "multiprocess") {
281
+ if ((0, import_optional_deps.isNodeRuntime)()) {
282
+ multiprocess_lock = acquireMultiprocessSemaphoreSync(scoped_key, semaphore_limit, effective_sem_timeout, semaphore_lax);
283
+ semaphore_acquired = multiprocess_lock !== null;
284
+ } else {
285
+ logMultiprocessFallbackOnce("multiprocess semaphores require a Node.js runtime; falling back to in-process global scope");
286
+ semaphore = getOrCreateSemaphore(scoped_key, semaphore_limit);
287
+ semaphore_acquired = acquireSemaphoreSyncOrThrow(semaphore, scoped_key, semaphore_limit, effective_sem_timeout, semaphore_lax);
288
+ }
289
+ } else {
290
+ semaphore = getOrCreateSemaphore(scoped_key, semaphore_limit);
291
+ semaphore_acquired = acquireSemaphoreSyncOrThrow(semaphore, scoped_key, semaphore_limit, effective_sem_timeout, semaphore_lax);
292
+ }
293
+ }
294
+ const new_held = new Set(held);
295
+ if (semaphore_acquired) {
296
+ new_held.add(scoped_key);
297
+ }
298
+ const release = () => {
299
+ if (semaphore_acquired && multiprocess_lock) {
300
+ multiprocess_lock.release();
301
+ } else if (semaphore_acquired && semaphore) {
302
+ semaphore.release();
303
+ }
304
+ };
305
+ const runRetryLoop = () => {
306
+ for (let attempt = 1; attempt <= effective_max_attempts; attempt++) {
307
+ const attempt_started_at = Date.now();
308
+ try {
309
+ const result = target.apply(this, args);
310
+ if (isThenable(result)) {
311
+ return runRetryLoopFromThenable(this, args, result, attempt);
312
+ }
313
+ if (timeout != null && timeout > 0 && Date.now() - attempt_started_at > timeout * 1e3) {
314
+ throw new RetryTimeoutError(`Timed out after ${timeout}s (attempt ${attempt})`, {
315
+ timeout_seconds: timeout,
316
+ attempt
317
+ });
318
+ }
319
+ return result;
320
+ } catch (error) {
321
+ if (!shouldRetry(error)) throw error;
322
+ if (attempt >= effective_max_attempts) throw error;
323
+ syncRetryDelay(attempt);
324
+ }
325
+ }
326
+ throw new Error(`retry(${fn_name}): unexpected end of retry loop`);
327
+ };
328
+ try {
329
+ const result = runWithHeldSemaphores(new_held, runRetryLoop);
330
+ if (isThenable(result)) {
331
+ return Promise.resolve(result).finally(release);
332
+ }
333
+ release();
334
+ return result;
335
+ } catch (error) {
336
+ release();
337
+ throw error;
338
+ }
339
+ }
340
+ const retryWrapper = isAsyncFunction(target) ? asyncRetryWrapper : syncRetryWrapper;
230
341
  Object.defineProperty(retryWrapper, "name", { value: fn_name, configurable: true });
231
342
  if (_context?.kind === "method" && typeof _context.addInitializer === "function") {
232
343
  _context.addInitializer(function() {
@@ -238,6 +349,17 @@ function retry(options = {}) {
238
349
  }
239
350
  return retryWrapper;
240
351
  };
352
+ function decorator(target, context_or_property_key, descriptor) {
353
+ if (descriptor?.value && typeof descriptor.value === "function") {
354
+ descriptor.value = decorateFunction(descriptor.value);
355
+ return descriptor;
356
+ }
357
+ if (typeof target === "function") {
358
+ return decorateFunction(target, typeof context_or_property_key === "object" ? context_or_property_key : void 0);
359
+ }
360
+ throw new TypeError("retry() can only decorate functions and class methods");
361
+ }
362
+ return decorator;
241
363
  }
242
364
  function findDecoratedMethodOwnerName(context_this, context, replacement) {
243
365
  const method_name = context.name;
@@ -270,6 +392,12 @@ function findDecoratedMethodOwnerName(context_this, context, replacement) {
270
392
  }
271
393
  return null;
272
394
  }
395
+ function isAsyncFunction(fn) {
396
+ return Object.prototype.toString.call(fn) === "[object AsyncFunction]";
397
+ }
398
+ function isThenable(value) {
399
+ return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
400
+ }
273
401
  async function acquireWithTimeout(semaphore, timeout_ms) {
274
402
  return new Promise((resolve) => {
275
403
  let settled = false;
@@ -290,11 +418,51 @@ async function acquireWithTimeout(semaphore, timeout_ms) {
290
418
  });
291
419
  });
292
420
  }
421
+ function acquireSemaphoreSyncOrThrow(semaphore, scoped_key, semaphore_limit, timeout_seconds, semaphore_lax) {
422
+ const acquired = acquireSemaphoreSync(semaphore, timeout_seconds == null ? null : timeout_seconds * 1e3);
423
+ if (acquired) return true;
424
+ if (!semaphore_lax) {
425
+ throw new SemaphoreTimeoutError(`Failed to acquire semaphore "${scoped_key}" within ${timeout_seconds}s (limit=${semaphore_limit})`, {
426
+ semaphore_name: scoped_key,
427
+ semaphore_limit,
428
+ timeout_seconds: timeout_seconds ?? 0
429
+ });
430
+ }
431
+ return false;
432
+ }
433
+ function acquireSemaphoreSync(semaphore, timeout_ms) {
434
+ if (semaphore.tryAcquire()) return true;
435
+ const start = Date.now();
436
+ while (true) {
437
+ if (timeout_ms != null && timeout_ms > 0 && Date.now() - start >= timeout_ms) {
438
+ return false;
439
+ }
440
+ sleepSync(10);
441
+ if (semaphore.tryAcquire()) return true;
442
+ }
443
+ }
293
444
  function logMultiprocessFallbackOnce(reason) {
294
445
  if (multiprocess_fallback_reason_logged === reason) return;
295
446
  multiprocess_fallback_reason_logged = reason;
296
447
  console.warn(`[abxbus.retry] ${reason}`);
297
448
  }
449
+ function importNodeModuleSync(specifier) {
450
+ const maybe_process = globalThis.process;
451
+ const get_builtin_module = maybe_process?.getBuiltinModule;
452
+ const bare_specifier = specifier.startsWith("node:") ? specifier.slice("node:".length) : specifier;
453
+ if (typeof get_builtin_module === "function") {
454
+ const builtin = get_builtin_module(bare_specifier) ?? get_builtin_module(specifier);
455
+ if (builtin) return builtin;
456
+ }
457
+ let require_fn;
458
+ try {
459
+ require_fn = Function('return typeof require === "function" ? require : undefined')();
460
+ } catch {
461
+ require_fn = void 0;
462
+ }
463
+ if (require_fn) return require_fn(specifier);
464
+ throw new Error("[abxbus.retry] synchronous Node.js module loading is unavailable; cannot use sync multiprocess semaphores");
465
+ }
298
466
  async function importNodeModule(specifier) {
299
467
  const dynamic_import = Function("module_name", "return import(module_name)");
300
468
  return dynamic_import(specifier);
@@ -385,6 +553,90 @@ async function acquireMultiprocessSemaphore(scoped_key, semaphore_limit, semapho
385
553
  }
386
554
  return null;
387
555
  }
556
+ function acquireMultiprocessSemaphoreSync(scoped_key, semaphore_limit, semaphore_timeout_seconds, semaphore_lax) {
557
+ const crypto = importNodeModuleSync("node:crypto");
558
+ const fs = importNodeModuleSync("node:fs");
559
+ const os = importNodeModuleSync("node:os");
560
+ const path = importNodeModuleSync("node:path");
561
+ const semaphore_directory = path.join(os.tmpdir(), MULTIPROCESS_SEMAPHORE_DIRNAME);
562
+ const lock_prefix = crypto.createHash("sha256").update(scoped_key).digest("hex").slice(0, 40);
563
+ fs.mkdirSync(semaphore_directory, { recursive: true });
564
+ const start = Date.now();
565
+ let retry_delay_ms = 100;
566
+ while (true) {
567
+ const elapsed_ms = Date.now() - start;
568
+ const remaining_ms = semaphore_timeout_seconds != null && semaphore_timeout_seconds > 0 ? semaphore_timeout_seconds * 1e3 - elapsed_ms : null;
569
+ if (remaining_ms != null && remaining_ms <= 0) {
570
+ break;
571
+ }
572
+ for (let slot = 0; slot < semaphore_limit; slot++) {
573
+ const slot_file = path.join(semaphore_directory, `${lock_prefix}.${String(slot).padStart(2, "0")}.lock`);
574
+ const token = `${process.pid}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
575
+ try {
576
+ const fd = fs.openSync(slot_file, "wx", 384);
577
+ try {
578
+ fs.writeFileSync(
579
+ fd,
580
+ JSON.stringify({
581
+ token,
582
+ pid: process.pid,
583
+ semaphore_name: scoped_key,
584
+ created_at_ms: Date.now()
585
+ }),
586
+ "utf8"
587
+ );
588
+ } finally {
589
+ fs.closeSync(fd);
590
+ }
591
+ return {
592
+ release: () => {
593
+ try {
594
+ const raw = String(fs.readFileSync(slot_file, "utf8") || "").trim();
595
+ const current_owner = raw ? JSON.parse(raw) : null;
596
+ if (current_owner?.token === token) {
597
+ fs.unlinkSync(slot_file);
598
+ }
599
+ } catch {
600
+ }
601
+ }
602
+ };
603
+ } catch (error) {
604
+ if (!(error instanceof Error) || error.code !== "EEXIST") {
605
+ throw error;
606
+ }
607
+ try {
608
+ const raw = String(fs.readFileSync(slot_file, "utf8") || "").trim();
609
+ const current_owner = raw ? JSON.parse(raw) : null;
610
+ const current_pid = typeof current_owner?.pid === "number" ? current_owner.pid : null;
611
+ if (current_pid != null) {
612
+ try {
613
+ process.kill(current_pid, 0);
614
+ continue;
615
+ } catch {
616
+ }
617
+ }
618
+ const slot_age_ms = Date.now() - fs.statSync(slot_file).mtimeMs;
619
+ if (current_pid != null || slot_age_ms >= MULTIPROCESS_STALE_LOCK_MS) {
620
+ fs.unlinkSync(slot_file);
621
+ }
622
+ } catch {
623
+ }
624
+ }
625
+ }
626
+ const sleep_ms = Math.min(retry_delay_ms, remaining_ms ?? retry_delay_ms);
627
+ if (sleep_ms > 0) {
628
+ sleepSync(sleep_ms);
629
+ }
630
+ retry_delay_ms = Math.min(retry_delay_ms * 2, 1e3);
631
+ }
632
+ if (!semaphore_lax) {
633
+ throw new SemaphoreTimeoutError(
634
+ `Failed to acquire semaphore "${scoped_key}" within ${semaphore_timeout_seconds}s (limit=${semaphore_limit})`,
635
+ { semaphore_name: scoped_key, semaphore_limit, timeout_seconds: semaphore_timeout_seconds ?? 0 }
636
+ );
637
+ }
638
+ return null;
639
+ }
388
640
  async function _runWithTimeout(fn, timeout_ms, attempt) {
389
641
  return new Promise((resolve, reject) => {
390
642
  let settled = false;
@@ -420,4 +672,21 @@ async function _runWithTimeout(fn, timeout_ms, attempt) {
420
672
  function sleep(ms) {
421
673
  return new Promise((resolve) => setTimeout(resolve, ms));
422
674
  }
675
+ function sleepSync(ms) {
676
+ if (ms <= 0) return;
677
+ const shared_array_buffer = globalThis.SharedArrayBuffer;
678
+ const atomics = globalThis.Atomics;
679
+ if (typeof shared_array_buffer === "function" && typeof atomics?.wait === "function") {
680
+ try {
681
+ const buffer = new shared_array_buffer(4);
682
+ const view = new Int32Array(buffer);
683
+ atomics.wait(view, 0, 0, ms);
684
+ return;
685
+ } catch {
686
+ }
687
+ }
688
+ const deadline = Date.now() + ms;
689
+ while (Date.now() < deadline) {
690
+ }
691
+ }
423
692
  //# sourceMappingURL=retry.js.map