abxbus 2.4.1 → 2.4.15
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 +19 -7
- package/dist/cjs/base_event.d.ts +8 -4
- package/dist/cjs/base_event.js +98 -38
- package/dist/cjs/base_event.js.map +2 -2
- package/dist/cjs/event_bus.d.ts +0 -2
- package/dist/cjs/event_bus.js +13 -46
- package/dist/cjs/event_bus.js.map +2 -2
- package/dist/cjs/event_result.js +2 -2
- package/dist/cjs/event_result.js.map +1 -1
- package/dist/cjs/lock_manager.d.ts +1 -1
- package/dist/cjs/lock_manager.js +9 -6
- package/dist/cjs/lock_manager.js.map +2 -2
- package/dist/cjs/logging.js +5 -2
- package/dist/cjs/logging.js.map +2 -2
- package/dist/cjs/retry.d.ts +4 -1
- package/dist/cjs/retry.js +139 -12
- package/dist/cjs/retry.js.map +2 -2
- package/dist/esm/base_event.js +98 -38
- package/dist/esm/base_event.js.map +2 -2
- package/dist/esm/event_bus.js +13 -46
- package/dist/esm/event_bus.js.map +2 -2
- package/dist/esm/event_result.js +2 -2
- package/dist/esm/event_result.js.map +1 -1
- package/dist/esm/lock_manager.js +9 -6
- package/dist/esm/lock_manager.js.map +2 -2
- package/dist/esm/logging.js +6 -3
- package/dist/esm/logging.js.map +2 -2
- package/dist/esm/retry.js +139 -12
- package/dist/esm/retry.js.map +2 -2
- package/dist/types/base_event.d.ts +8 -4
- package/dist/types/event_bus.d.ts +0 -2
- package/dist/types/lock_manager.d.ts +1 -1
- package/dist/types/retry.d.ts +4 -1
- package/package.json +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lock_manager.ts"],
|
|
4
|
-
"sourcesContent": ["import type { BaseEvent } from './base_event.js'\nimport type { EventResult } from './event_result.js'\n\n// \u2500\u2500\u2500 Deferred / withResolvers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type Deferred<T> = {\n promise: Promise<T>\n resolve: (value: T | PromiseLike<T>) => void\n reject: (reason?: unknown) => void\n}\n\nexport const withResolvers = <T>(): Deferred<T> => {\n if (typeof Promise.withResolvers === 'function') {\n return Promise.withResolvers<T>()\n }\n let resolve!: (value: T | PromiseLike<T>) => void\n let reject!: (reason?: unknown) => void\n const promise = new Promise<T>((resolve_fn, reject_fn) => {\n resolve = resolve_fn\n reject = reject_fn\n })\n return { promise, resolve, reject }\n}\n\n// \u2500\u2500\u2500 Concurrency modes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const EVENT_CONCURRENCY_MODES = ['global-serial', 'bus-serial', 'parallel'] as const\nexport type EventConcurrencyMode = (typeof EVENT_CONCURRENCY_MODES)[number]\n\nexport const EVENT_HANDLER_CONCURRENCY_MODES = ['serial', 'parallel'] as const\nexport type EventHandlerConcurrencyMode = (typeof EVENT_HANDLER_CONCURRENCY_MODES)[number]\n\nexport const EVENT_HANDLER_COMPLETION_MODES = ['all', 'first'] as const\nexport type EventHandlerCompletionMode = (typeof EVENT_HANDLER_COMPLETION_MODES)[number]\n\n// \u2500\u2500\u2500 AsyncLock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class AsyncLock {\n size: number\n in_use: number\n waiters: Array<() => void>\n\n constructor(size: number) {\n this.size = size\n this.in_use = 0\n this.waiters = []\n }\n\n async acquire(): Promise<void> {\n if (this.size === Infinity) {\n return\n }\n if (this.in_use < this.size) {\n this.in_use += 1\n return\n }\n await new Promise<void>((resolve) => {\n this.waiters.push(resolve)\n })\n }\n\n release(): void {\n if (this.size === Infinity) {\n return\n }\n const next = this.waiters.shift()\n if (next) {\n // Handoff: keep permit accounted for and transfer directly to next waiter.\n next()\n return\n }\n this.in_use = Math.max(0, this.in_use - 1)\n }\n}\n\nexport const runWithLock = async <T>(lock: AsyncLock | null, fn: () => Promise<T>): Promise<T> => {\n if (!lock) {\n return await fn()\n }\n await lock.acquire()\n try {\n return await fn()\n } finally {\n lock.release()\n }\n}\n\n// \u2500\u2500\u2500 HandlerLock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type HandlerExecutionState = 'held' | 'yielded' | 'closed'\n\n// Tracks a single handler execution's ownership of a handler lock.\n// Reacquire is race-safe: if the handler exits while waiting to reclaim,\n// the reclaimed lock is immediately released to avoid leaks.\nexport class HandlerLock {\n private lock: AsyncLock | null\n private state: HandlerExecutionState\n\n constructor(lock: AsyncLock | null) {\n this.lock = lock\n this.state = 'held'\n }\n\n // used by EventBus._processEventImmediately to yield the parent handler's lock to the child event so it can be processed immediately\n yieldHandlerLockForChildRun(): boolean {\n if (!this.lock || this.state !== 'held') {\n return false\n }\n this.state = 'yielded'\n this.lock.release()\n return true\n }\n\n // used by EventBus._processEventImmediately to reacquire the handler lock after the child event has been processed\n async reclaimHandlerLockIfRunning(): Promise<boolean> {\n if (!this.lock || this.state !== 'yielded') {\n return false\n }\n await this.lock.acquire()\n if (this.state !== 'yielded') {\n // Handler exited while this reacquire was pending.\n this.lock.release()\n return false\n }\n this.state = 'held'\n return true\n }\n\n // used by EventResult.runHandler to exit the handler lock after the handler has finished executing\n exitHandlerRun(): void {\n if (this.state === 'closed') {\n return\n }\n const should_release = !!this.lock && this.state === 'held'\n this.state = 'closed'\n if (should_release) {\n this.lock!.release()\n }\n }\n\n // used by EventBus._processEventImmediately to yield the handler lock and reacquire it after the child event has been processed\n async runQueueJump<T>(fn: () => Promise<T>): Promise<T> {\n const yielded = this.yieldHandlerLockForChildRun()\n try {\n return await fn()\n } finally {\n if (yielded) {\n await this.reclaimHandlerLockIfRunning()\n }\n }\n }\n}\n\n// \u2500\u2500\u2500 LockManager \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Interface that must be implemented by the EventBus class to be used by the LockManager\nexport type EventBusInterfaceForLockManager = {\n isIdleAndQueueEmpty: () => boolean\n event_concurrency: EventConcurrencyMode\n _lock_for_event_global_serial: AsyncLock\n}\n\nexport type LockManagerOptions = {\n auto_schedule_idle_checks?: boolean\n}\n\n// The LockManager is responsible for managing the concurrency of events and handlers\nexport class LockManager {\n private bus: EventBusInterfaceForLockManager // Live bus reference; used to read defaults and idle state.\n private auto_schedule_idle_checks: boolean\n\n readonly bus_event_lock: AsyncLock // Per-bus event lock; created with LockManager and never swapped.\n private pause_depth: number // Re-entrant pause counter; increments on _requestRunloopPause, decrements on release.\n private pause_waiters: Array<() => void> // Resolvers for _waitUntilRunloopResumed; drained when pause_depth hits 0.\n private active_handler_results: EventResult[] // Stack of active handler results for \"inside handler\" detection.\n\n private idle_waiters: Array<(became_idle: boolean) => void> // Resolvers waiting for stable idle; cleared when idle confirmed.\n private idle_check_pending: boolean // Debounce flag to avoid scheduling redundant idle checks.\n private idle_check_streak: number // Counts consecutive idle checks; used to require two ticks of idle.\n\n constructor(bus: EventBusInterfaceForLockManager, options: LockManagerOptions = {}) {\n this.bus = bus\n this.auto_schedule_idle_checks = options.auto_schedule_idle_checks ?? true\n this.bus_event_lock = new AsyncLock(1) // used for the bus-serial concurrency mode\n\n this.pause_depth = 0\n this.pause_waiters = []\n this.active_handler_results = []\n\n this.idle_waiters = []\n this.idle_check_pending = false\n this.idle_check_streak = 0\n }\n\n // Low-level runloop pause: increments a re-entrant counter and returns a release\n // function. Used for broad, bus-scoped pauses during queue-jump across buses.\n _requestRunloopPause(): () => void {\n this.pause_depth += 1\n let released = false\n return () => {\n if (released) {\n return\n }\n released = true\n this.pause_depth = Math.max(0, this.pause_depth - 1)\n if (this.pause_depth !== 0) {\n return\n }\n const waiters = this.pause_waiters\n this.pause_waiters = []\n for (const resolve of waiters) {\n resolve()\n }\n }\n }\n\n _waitUntilRunloopResumed(): Promise<void> {\n if (this.pause_depth === 0) {\n return Promise.resolve()\n }\n return new Promise((resolve) => {\n this.pause_waiters.push(resolve)\n })\n }\n\n _isPaused(): boolean {\n return this.pause_depth > 0\n }\n\n async _runWithHandlerDispatchContext<T>(result: EventResult, fn: () => Promise<T>): Promise<T> {\n this.active_handler_results.push(result)\n try {\n return await fn()\n } finally {\n const idx = this.active_handler_results.indexOf(result)\n if (idx >= 0) {\n this.active_handler_results.splice(idx, 1)\n }\n }\n }\n\n _getActiveHandlerResult(): EventResult | undefined {\n return this.active_handler_results[this.active_handler_results.length - 1]\n }\n\n _getActiveHandlerResults(): EventResult[] {\n return [...this.active_handler_results]\n }\n\n // Per-bus check: true only if this specific bus has a handler on its stack.\n // For cross-bus queue-jumping, EventBus._processEventImmediately uses getParentEventResultAcrossAllBuses()\n // to walk up the parent event tree, and the bus proxy passes handler_result\n // to _processEventImmediately so it can yield/reacquire the correct lock.\n _isAnyHandlerActive(): boolean {\n return this.active_handler_results.length > 0\n }\n\n waitForIdle(timeout_seconds: number | null = null): Promise<boolean> {\n return new Promise((resolve) => {\n let done = false\n let timeout_id: ReturnType<typeof setTimeout> | null = null\n\n const finish = (became_idle: boolean): void => {\n if (done) {\n return\n }\n done = true\n if (timeout_id !== null) {\n clearTimeout(timeout_id)\n timeout_id = null\n }\n resolve(became_idle)\n }\n\n this.idle_waiters.push(finish)\n this.scheduleIdleCheck()\n\n if (timeout_seconds === null || timeout_seconds === undefined) {\n return\n }\n\n const timeout_ms = Math.max(0, Number(timeout_seconds)) * 1000\n if (!Number.isFinite(timeout_ms)) {\n return\n }\n\n timeout_id = setTimeout(() => {\n const index = this.idle_waiters.indexOf(finish)\n if (index >= 0) {\n this.idle_waiters.splice(index, 1)\n }\n finish(false)\n }, timeout_ms)\n })\n }\n\n // Called by EventBus.markEventCompleted and EventBus.markHandlerCompleted to notify\n // waitUntilIdle() callers that the bus may now be idle.\n _notifyIdleListeners(): void {\n // Fast-path: most completions have no waitUntilIdle() callers waiting,\n // so skip expensive idle snapshot scans in that common case.\n if (this.idle_waiters.length === 0) {\n this.idle_check_streak = 0\n return\n }\n\n if (!this.bus.isIdleAndQueueEmpty()) {\n this.idle_check_streak = 0\n if (this.idle_waiters.length > 0) {\n this.scheduleIdleCheck()\n }\n return\n }\n\n this.idle_check_streak += 1\n if (this.idle_check_streak < 2) {\n if (this.idle_waiters.length > 0) {\n this.scheduleIdleCheck()\n }\n return\n }\n\n this.idle_check_streak = 0\n const waiters = this.idle_waiters\n this.idle_waiters = []\n for (const resolve of waiters) {\n resolve(true)\n }\n }\n\n // get the bus-level lock that prevents/allows multiple events to be processed concurrently on the same bus\n getLockForEvent(event: BaseEvent): AsyncLock | null {\n const resolved = event.event_concurrency ?? this.bus.event_concurrency\n if (resolved === 'parallel') {\n return null\n }\n if (resolved === 'global-serial') {\n return this.bus._lock_for_event_global_serial\n }\n return this.bus_event_lock\n }\n\n async _runWithEventLock<T>(\n event: BaseEvent,\n fn: () => Promise<T>,\n options: { bypass_event_locks?: boolean; pre_acquired_lock?: AsyncLock | null } = {}\n ): Promise<T> {\n const pre_acquired = options.pre_acquired_lock ?? null\n if (options.bypass_event_locks || pre_acquired) {\n return await fn()\n }\n return await runWithLock(this.getLockForEvent(event), fn)\n }\n\n async _runWithHandlerLock<T>(\n event: BaseEvent,\n default_handler_concurrency: EventHandlerConcurrencyMode | undefined,\n fn: (lock: HandlerLock | null) => Promise<T>\n ): Promise<T> {\n const lock = event._getHandlerLock(default_handler_concurrency)\n if (lock) {\n await lock.acquire()\n }\n const handler_lock = lock ? new HandlerLock(lock) : null\n try {\n return await fn(handler_lock)\n } finally {\n handler_lock?.exitHandlerRun()\n }\n }\n\n // Schedules a debounced idle check to run after a short delay. Used to gate\n // waitUntilIdle() calls during handler execution and after event completion.\n private scheduleIdleCheck(): void {\n if (!this.auto_schedule_idle_checks) {\n return\n }\n if (this.idle_check_pending) {\n return\n }\n this.idle_check_pending = true\n setTimeout(() => {\n this.idle_check_pending = false\n this._notifyIdleListeners()\n }, 0)\n }\n\n // Reset all state to initial values\n clear(): void {\n this.pause_depth = 0\n this.pause_waiters = []\n this.active_handler_results = []\n this.idle_waiters = []\n this.idle_check_pending = false\n this.idle_check_streak = 0\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { BaseEvent } from './base_event.js'\nimport type { EventResult } from './event_result.js'\nimport { createAsyncLocalStorage, type AsyncLocalStorageLike } from './async_context.js'\n\n// \u2500\u2500\u2500 Deferred / withResolvers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type Deferred<T> = {\n promise: Promise<T>\n resolve: (value: T | PromiseLike<T>) => void\n reject: (reason?: unknown) => void\n}\n\nexport const withResolvers = <T>(): Deferred<T> => {\n if (typeof Promise.withResolvers === 'function') {\n return Promise.withResolvers<T>()\n }\n let resolve!: (value: T | PromiseLike<T>) => void\n let reject!: (reason?: unknown) => void\n const promise = new Promise<T>((resolve_fn, reject_fn) => {\n resolve = resolve_fn\n reject = reject_fn\n })\n return { promise, resolve, reject }\n}\n\n// \u2500\u2500\u2500 Concurrency modes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const EVENT_CONCURRENCY_MODES = ['global-serial', 'bus-serial', 'parallel'] as const\nexport type EventConcurrencyMode = (typeof EVENT_CONCURRENCY_MODES)[number]\n\nexport const EVENT_HANDLER_CONCURRENCY_MODES = ['serial', 'parallel'] as const\nexport type EventHandlerConcurrencyMode = (typeof EVENT_HANDLER_CONCURRENCY_MODES)[number]\n\nexport const EVENT_HANDLER_COMPLETION_MODES = ['all', 'first'] as const\nexport type EventHandlerCompletionMode = (typeof EVENT_HANDLER_COMPLETION_MODES)[number]\n\n// \u2500\u2500\u2500 AsyncLock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class AsyncLock {\n size: number\n in_use: number\n waiters: Array<() => void>\n\n constructor(size: number) {\n this.size = size\n this.in_use = 0\n this.waiters = []\n }\n\n async acquire(): Promise<void> {\n if (this.size === Infinity) {\n return\n }\n if (this.in_use < this.size) {\n this.in_use += 1\n return\n }\n await new Promise<void>((resolve) => {\n this.waiters.push(resolve)\n })\n }\n\n release(): void {\n if (this.size === Infinity) {\n return\n }\n const next = this.waiters.shift()\n if (next) {\n // Handoff: keep permit accounted for and transfer directly to next waiter.\n next()\n return\n }\n this.in_use = Math.max(0, this.in_use - 1)\n }\n}\n\nexport const runWithLock = async <T>(lock: AsyncLock | null, fn: () => Promise<T>): Promise<T> => {\n if (!lock) {\n return await fn()\n }\n await lock.acquire()\n try {\n return await fn()\n } finally {\n lock.release()\n }\n}\n\nconst handler_context_storage: AsyncLocalStorageLike | null = createAsyncLocalStorage()\n\n// \u2500\u2500\u2500 HandlerLock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type HandlerExecutionState = 'held' | 'yielded' | 'closed'\n\n// Tracks a single handler execution's ownership of a handler lock.\n// Reacquire is race-safe: if the handler exits while waiting to reclaim,\n// the reclaimed lock is immediately released to avoid leaks.\nexport class HandlerLock {\n private lock: AsyncLock | null\n private state: HandlerExecutionState\n\n constructor(lock: AsyncLock | null) {\n this.lock = lock\n this.state = 'held'\n }\n\n // used by EventBus._processEventImmediately to yield the parent handler's lock to the child event so it can be processed immediately\n yieldHandlerLockForChildRun(): boolean {\n if (!this.lock || this.state !== 'held') {\n return false\n }\n this.state = 'yielded'\n this.lock.release()\n return true\n }\n\n // used by EventBus._processEventImmediately to reacquire the handler lock after the child event has been processed\n async reclaimHandlerLockIfRunning(): Promise<boolean> {\n if (!this.lock || this.state !== 'yielded') {\n return false\n }\n await this.lock.acquire()\n if (this.state !== 'yielded') {\n // Handler exited while this reacquire was pending.\n this.lock.release()\n return false\n }\n this.state = 'held'\n return true\n }\n\n // used by EventResult.runHandler to exit the handler lock after the handler has finished executing\n exitHandlerRun(): void {\n if (this.state === 'closed') {\n return\n }\n const should_release = !!this.lock && this.state === 'held'\n this.state = 'closed'\n if (should_release) {\n this.lock!.release()\n }\n }\n\n // used by EventBus._processEventImmediately to yield the handler lock and reacquire it after the child event has been processed\n async runQueueJump<T>(fn: () => Promise<T>): Promise<T> {\n const yielded = this.yieldHandlerLockForChildRun()\n try {\n return await fn()\n } finally {\n if (yielded) {\n await this.reclaimHandlerLockIfRunning()\n }\n }\n }\n}\n\n// \u2500\u2500\u2500 LockManager \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Interface that must be implemented by the EventBus class to be used by the LockManager\nexport type EventBusInterfaceForLockManager = {\n isIdleAndQueueEmpty: () => boolean\n event_concurrency: EventConcurrencyMode\n _lock_for_event_global_serial: AsyncLock\n}\n\nexport type LockManagerOptions = {\n auto_schedule_idle_checks?: boolean\n}\n\n// The LockManager is responsible for managing the concurrency of events and handlers\nexport class LockManager {\n private bus: EventBusInterfaceForLockManager // Live bus reference; used to read defaults and idle state.\n private auto_schedule_idle_checks: boolean\n\n readonly bus_event_lock: AsyncLock // Per-bus event lock; created with LockManager and never swapped.\n private pause_depth: number // Re-entrant pause counter; increments on _requestRunloopPause, decrements on release.\n private pause_waiters: Array<() => void> // Resolvers for _waitUntilRunloopResumed; drained when pause_depth hits 0.\n private active_handler_results: EventResult[] // Stack of active handler results for \"inside handler\" detection.\n\n private idle_waiters: Array<(became_idle: boolean) => void> // Resolvers waiting for stable idle; cleared when idle confirmed.\n private idle_check_pending: boolean // Debounce flag to avoid scheduling redundant idle checks.\n private idle_check_streak: number // Counts consecutive idle checks; used to require two ticks of idle.\n\n constructor(bus: EventBusInterfaceForLockManager, options: LockManagerOptions = {}) {\n this.bus = bus\n this.auto_schedule_idle_checks = options.auto_schedule_idle_checks ?? true\n this.bus_event_lock = new AsyncLock(1) // used for the bus-serial concurrency mode\n\n this.pause_depth = 0\n this.pause_waiters = []\n this.active_handler_results = []\n\n this.idle_waiters = []\n this.idle_check_pending = false\n this.idle_check_streak = 0\n }\n\n // Low-level runloop pause: increments a re-entrant counter and returns a release\n // function. Used for broad, bus-scoped pauses during queue-jump across buses.\n _requestRunloopPause(): () => void {\n this.pause_depth += 1\n let released = false\n return () => {\n if (released) {\n return\n }\n released = true\n this.pause_depth = Math.max(0, this.pause_depth - 1)\n if (this.pause_depth !== 0) {\n return\n }\n const waiters = this.pause_waiters\n this.pause_waiters = []\n for (const resolve of waiters) {\n resolve()\n }\n }\n }\n\n _waitUntilRunloopResumed(): Promise<void> {\n if (this.pause_depth === 0) {\n return Promise.resolve()\n }\n return new Promise((resolve) => {\n this.pause_waiters.push(resolve)\n })\n }\n\n _isPaused(): boolean {\n return this.pause_depth > 0\n }\n\n async _runWithHandlerDispatchContext<T>(result: EventResult, fn: () => Promise<T>): Promise<T> {\n this.active_handler_results.push(result)\n try {\n if (!handler_context_storage) {\n return await fn()\n }\n return await handler_context_storage.run(result, fn)\n } finally {\n const idx = this.active_handler_results.indexOf(result)\n if (idx >= 0) {\n this.active_handler_results.splice(idx, 1)\n }\n }\n }\n\n _getActiveHandlerResultForCurrentAsyncContext(): EventResult | undefined {\n const result = handler_context_storage?.getStore() as EventResult | undefined\n return result?.status === 'started' ? result : undefined\n }\n\n _getActiveHandlerResults(): EventResult[] {\n return [...this.active_handler_results]\n }\n\n // Per-bus check: true only if this specific bus has a handler on its stack.\n _isAnyHandlerActive(): boolean {\n return this.active_handler_results.length > 0\n }\n\n waitForIdle(timeout_seconds: number | null = null): Promise<boolean> {\n return new Promise((resolve) => {\n let done = false\n let timeout_id: ReturnType<typeof setTimeout> | null = null\n\n const finish = (became_idle: boolean): void => {\n if (done) {\n return\n }\n done = true\n if (timeout_id !== null) {\n clearTimeout(timeout_id)\n timeout_id = null\n }\n resolve(became_idle)\n }\n\n this.idle_waiters.push(finish)\n this.scheduleIdleCheck()\n\n if (timeout_seconds === null || timeout_seconds === undefined) {\n return\n }\n\n const timeout_ms = Math.max(0, Number(timeout_seconds)) * 1000\n if (!Number.isFinite(timeout_ms)) {\n return\n }\n\n timeout_id = setTimeout(() => {\n const index = this.idle_waiters.indexOf(finish)\n if (index >= 0) {\n this.idle_waiters.splice(index, 1)\n }\n finish(false)\n }, timeout_ms)\n })\n }\n\n // Called by EventBus.markEventCompleted and EventBus.markHandlerCompleted to notify\n // waitUntilIdle() callers that the bus may now be idle.\n _notifyIdleListeners(): void {\n // Fast-path: most completions have no waitUntilIdle() callers waiting,\n // so skip expensive idle snapshot scans in that common case.\n if (this.idle_waiters.length === 0) {\n this.idle_check_streak = 0\n return\n }\n\n if (!this.bus.isIdleAndQueueEmpty()) {\n this.idle_check_streak = 0\n if (this.idle_waiters.length > 0) {\n this.scheduleIdleCheck()\n }\n return\n }\n\n this.idle_check_streak += 1\n if (this.idle_check_streak < 2) {\n if (this.idle_waiters.length > 0) {\n this.scheduleIdleCheck()\n }\n return\n }\n\n this.idle_check_streak = 0\n const waiters = this.idle_waiters\n this.idle_waiters = []\n for (const resolve of waiters) {\n resolve(true)\n }\n }\n\n // get the bus-level lock that prevents/allows multiple events to be processed concurrently on the same bus\n getLockForEvent(event: BaseEvent): AsyncLock | null {\n const resolved = event.event_concurrency ?? this.bus.event_concurrency\n if (resolved === 'parallel') {\n return null\n }\n if (resolved === 'global-serial') {\n return this.bus._lock_for_event_global_serial\n }\n return this.bus_event_lock\n }\n\n async _runWithEventLock<T>(\n event: BaseEvent,\n fn: () => Promise<T>,\n options: { bypass_event_locks?: boolean; pre_acquired_lock?: AsyncLock | null } = {}\n ): Promise<T> {\n const pre_acquired = options.pre_acquired_lock ?? null\n if (options.bypass_event_locks || pre_acquired) {\n return await fn()\n }\n return await runWithLock(this.getLockForEvent(event), fn)\n }\n\n async _runWithHandlerLock<T>(\n event: BaseEvent,\n default_handler_concurrency: EventHandlerConcurrencyMode | undefined,\n fn: (lock: HandlerLock | null) => Promise<T>\n ): Promise<T> {\n const lock = event._getHandlerLock(default_handler_concurrency)\n if (lock) {\n await lock.acquire()\n }\n const handler_lock = lock ? new HandlerLock(lock) : null\n try {\n return await fn(handler_lock)\n } finally {\n handler_lock?.exitHandlerRun()\n }\n }\n\n // Schedules a debounced idle check to run after a short delay. Used to gate\n // waitUntilIdle() calls during handler execution and after event completion.\n private scheduleIdleCheck(): void {\n if (!this.auto_schedule_idle_checks) {\n return\n }\n if (this.idle_check_pending) {\n return\n }\n this.idle_check_pending = true\n setTimeout(() => {\n this.idle_check_pending = false\n this._notifyIdleListeners()\n }, 0)\n }\n\n // Reset all state to initial values\n clear(): void {\n this.pause_depth = 0\n this.pause_waiters = []\n this.active_handler_results = []\n this.idle_waiters = []\n this.idle_check_pending = false\n this.idle_check_streak = 0\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/esm/logging.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseEvent } from "./base_event.js";
|
|
2
|
-
import { EventHandlerCancelledError, EventHandlerTimeoutError } from "./event_handler.js";
|
|
2
|
+
import { EventHandlerAbortedError, EventHandlerCancelledError, EventHandlerTimeoutError } from "./event_handler.js";
|
|
3
3
|
const logTree = (bus) => {
|
|
4
4
|
const parent_to_children = /* @__PURE__ */ new Map();
|
|
5
5
|
const addChild = (parent_id, child) => {
|
|
@@ -102,7 +102,7 @@ const buildTreeLine = (event, indent, is_last, parent_to_children, visited) => {
|
|
|
102
102
|
};
|
|
103
103
|
const buildResultLine = (result, indent, is_last, parent_to_children, visited) => {
|
|
104
104
|
const connector = is_last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
105
|
-
const status_icon = result.status === "completed" ? "\u2705" : result.status === "error" ? "\u274C" : result.status === "started" ? "\u{1F3C3}" : "\u23F3";
|
|
105
|
+
const status_icon = result.status === "completed" ? "\u2705" : result.status === "error" && isCancellationControlError(result.error) ? "\u{1F6AB}" : result.status === "error" ? "\u274C" : result.status === "started" ? "\u{1F3C3}" : "\u23F3";
|
|
106
106
|
const handler_label = result.handler_name && result.handler_name !== "anonymous" ? result.handler_name : result.handler_file_path ? result.handler_file_path : "anonymous";
|
|
107
107
|
const handler_display = `${result.eventbus_label}.${handler_label}#${result.handler_id.slice(-4)}`;
|
|
108
108
|
let line = `${indent}${connector}${status_icon} ${handler_display}`;
|
|
@@ -122,7 +122,9 @@ const buildResultLine = (result, indent, is_last, parent_to_children, visited) =
|
|
|
122
122
|
if (result.error instanceof EventHandlerTimeoutError) {
|
|
123
123
|
line += ` \u23F1\uFE0F Timeout: ${result.error.message}`;
|
|
124
124
|
} else if (result.error instanceof EventHandlerCancelledError) {
|
|
125
|
-
line += `
|
|
125
|
+
line += ` Cancelled: ${result.error.message}`;
|
|
126
|
+
} else if (result.error instanceof EventHandlerAbortedError) {
|
|
127
|
+
line += ` Aborted: ${result.error.message}`;
|
|
126
128
|
} else {
|
|
127
129
|
const error_name = result.error instanceof Error ? result.error.name : "Error";
|
|
128
130
|
const error_message = result.error instanceof Error ? result.error.message : String(result.error);
|
|
@@ -155,6 +157,7 @@ const buildResultLine = (result, indent, is_last, parent_to_children, visited) =
|
|
|
155
157
|
});
|
|
156
158
|
return [line, ...child_lines].join("\n");
|
|
157
159
|
};
|
|
160
|
+
const isCancellationControlError = (error) => error instanceof EventHandlerCancelledError || error instanceof EventHandlerAbortedError;
|
|
158
161
|
const formatTimestamp = (value) => {
|
|
159
162
|
if (!value) {
|
|
160
163
|
return "N/A";
|
package/dist/esm/logging.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/logging.ts"],
|
|
4
|
-
"sourcesContent": ["import { BaseEvent } from './base_event.js'\nimport { EventResult } from './event_result.js'\nimport { EventHandlerCancelledError, EventHandlerTimeoutError } from './event_handler.js'\n\ntype LogTreeBus = {\n name: string\n event_history: {\n values(): IterableIterator<BaseEvent>\n has(event_id: string): boolean\n }\n toString?: () => string\n}\n\nexport const logTree = (bus: LogTreeBus): string => {\n const parent_to_children = new Map<string, BaseEvent[]>()\n\n const addChild = (parent_id: string, child: BaseEvent): void => {\n const existing = parent_to_children.get(parent_id) ?? []\n existing.push(child)\n parent_to_children.set(parent_id, existing)\n }\n\n const root_events: BaseEvent[] = []\n const seen = new Set<string>()\n\n for (const event of bus.event_history.values()) {\n const parent_id = event.event_parent_id\n if (!parent_id || parent_id === event.event_id || !bus.event_history.has(parent_id)) {\n if (!seen.has(event.event_id)) {\n root_events.push(event)\n seen.add(event.event_id)\n }\n }\n }\n\n if (root_events.length === 0) {\n return '(No events in history)'\n }\n\n const nodes_by_id = new Map<string, BaseEvent>()\n for (const root of root_events) {\n nodes_by_id.set(root.event_id, root)\n for (const descendant of root.event_descendants) {\n nodes_by_id.set(descendant.event_id, descendant)\n }\n }\n\n for (const node of nodes_by_id.values()) {\n const parent_id = node.event_parent_id\n if (!parent_id || parent_id === node.event_id) {\n continue\n }\n if (!nodes_by_id.has(parent_id)) {\n continue\n }\n addChild(parent_id, node)\n }\n\n for (const children of parent_to_children.values()) {\n children.sort((a, b) => (a.event_created_at < b.event_created_at ? -1 : a.event_created_at > b.event_created_at ? 1 : 0))\n }\n\n const lines: string[] = []\n const bus_label = typeof bus.toString === 'function' ? bus.toString() : bus.name\n lines.push(`\uD83D\uDCCA Event History Tree for ${bus_label}`)\n lines.push('='.repeat(80))\n\n root_events.sort((a, b) => (a.event_created_at < b.event_created_at ? -1 : a.event_created_at > b.event_created_at ? 1 : 0))\n const visited = new Set<string>()\n root_events.forEach((event, index) => {\n lines.push(buildTreeLine(event, '', index === root_events.length - 1, parent_to_children, visited))\n })\n\n lines.push('='.repeat(80))\n\n return lines.join('\\n')\n}\n\nexport const buildTreeLine = (\n event: BaseEvent,\n indent: string,\n is_last: boolean,\n parent_to_children: Map<string, BaseEvent[]>,\n visited: Set<string>\n): string => {\n const connector = is_last ? '\u2514\u2500\u2500 ' : '\u251C\u2500\u2500 '\n const status_icon = event.event_status === 'completed' ? '\u2705' : event.event_status === 'started' ? '\uD83C\uDFC3' : '\u23F3'\n\n const created_at = formatTimestamp(event.event_created_at)\n let timing = `[${created_at}`\n if (event.event_completed_at) {\n const created_ms = Date.parse(event.event_created_at)\n const completed_ms = Date.parse(event.event_completed_at)\n if (!Number.isNaN(created_ms) && !Number.isNaN(completed_ms)) {\n const duration = (completed_ms - created_ms) / 1000\n timing += ` (${duration.toFixed(3)}s)`\n }\n }\n timing += ']'\n\n const line = `${indent}${connector}${status_icon} ${event.event_type}#${event.event_id.slice(-4)} ${timing}`\n\n if (visited.has(event.event_id)) {\n return line\n }\n visited.add(event.event_id)\n\n const extension = is_last ? ' ' : '\u2502 '\n const new_indent = indent + extension\n\n const result_items: Array<{ type: 'result'; result: EventResult } | { type: 'child'; child: BaseEvent }> = []\n for (const result of event.event_results.values()) {\n result_items.push({ type: 'result', result })\n }\n const children = parent_to_children.get(event.event_id) ?? []\n const printed_child_ids = new Set<string>(event.event_results.size > 0 ? event.event_results.keys() : [])\n for (const child of children) {\n if (!printed_child_ids.has(child.event_id) && !child.event_emitted_by_handler_id) {\n result_items.push({ type: 'child', child })\n printed_child_ids.add(child.event_id)\n }\n }\n\n if (result_items.length === 0) {\n return line\n }\n\n const child_lines: string[] = []\n result_items.forEach((item, index) => {\n const is_last_item = index === result_items.length - 1\n if (item.type === 'result') {\n child_lines.push(buildResultLine(item.result, new_indent, is_last_item, parent_to_children, visited))\n } else {\n child_lines.push(buildTreeLine(item.child, new_indent, is_last_item, parent_to_children, visited))\n }\n })\n\n return [line, ...child_lines].join('\\n')\n}\n\nexport const buildResultLine = (\n result: EventResult,\n indent: string,\n is_last: boolean,\n parent_to_children: Map<string, BaseEvent[]>,\n visited: Set<string>\n): string => {\n const connector = is_last ? '\u2514\u2500\u2500 ' : '\u251C\u2500\u2500 '\n const status_icon
|
|
5
|
-
"mappings": "AAAA,SAAS,iBAAiB;AAE1B,SAAS,4BAA4B,gCAAgC;
|
|
4
|
+
"sourcesContent": ["import { BaseEvent } from './base_event.js'\nimport { EventResult } from './event_result.js'\nimport { EventHandlerAbortedError, EventHandlerCancelledError, EventHandlerTimeoutError } from './event_handler.js'\n\ntype LogTreeBus = {\n name: string\n event_history: {\n values(): IterableIterator<BaseEvent>\n has(event_id: string): boolean\n }\n toString?: () => string\n}\n\nexport const logTree = (bus: LogTreeBus): string => {\n const parent_to_children = new Map<string, BaseEvent[]>()\n\n const addChild = (parent_id: string, child: BaseEvent): void => {\n const existing = parent_to_children.get(parent_id) ?? []\n existing.push(child)\n parent_to_children.set(parent_id, existing)\n }\n\n const root_events: BaseEvent[] = []\n const seen = new Set<string>()\n\n for (const event of bus.event_history.values()) {\n const parent_id = event.event_parent_id\n if (!parent_id || parent_id === event.event_id || !bus.event_history.has(parent_id)) {\n if (!seen.has(event.event_id)) {\n root_events.push(event)\n seen.add(event.event_id)\n }\n }\n }\n\n if (root_events.length === 0) {\n return '(No events in history)'\n }\n\n const nodes_by_id = new Map<string, BaseEvent>()\n for (const root of root_events) {\n nodes_by_id.set(root.event_id, root)\n for (const descendant of root.event_descendants) {\n nodes_by_id.set(descendant.event_id, descendant)\n }\n }\n\n for (const node of nodes_by_id.values()) {\n const parent_id = node.event_parent_id\n if (!parent_id || parent_id === node.event_id) {\n continue\n }\n if (!nodes_by_id.has(parent_id)) {\n continue\n }\n addChild(parent_id, node)\n }\n\n for (const children of parent_to_children.values()) {\n children.sort((a, b) => (a.event_created_at < b.event_created_at ? -1 : a.event_created_at > b.event_created_at ? 1 : 0))\n }\n\n const lines: string[] = []\n const bus_label = typeof bus.toString === 'function' ? bus.toString() : bus.name\n lines.push(`\uD83D\uDCCA Event History Tree for ${bus_label}`)\n lines.push('='.repeat(80))\n\n root_events.sort((a, b) => (a.event_created_at < b.event_created_at ? -1 : a.event_created_at > b.event_created_at ? 1 : 0))\n const visited = new Set<string>()\n root_events.forEach((event, index) => {\n lines.push(buildTreeLine(event, '', index === root_events.length - 1, parent_to_children, visited))\n })\n\n lines.push('='.repeat(80))\n\n return lines.join('\\n')\n}\n\nexport const buildTreeLine = (\n event: BaseEvent,\n indent: string,\n is_last: boolean,\n parent_to_children: Map<string, BaseEvent[]>,\n visited: Set<string>\n): string => {\n const connector = is_last ? '\u2514\u2500\u2500 ' : '\u251C\u2500\u2500 '\n const status_icon = event.event_status === 'completed' ? '\u2705' : event.event_status === 'started' ? '\uD83C\uDFC3' : '\u23F3'\n\n const created_at = formatTimestamp(event.event_created_at)\n let timing = `[${created_at}`\n if (event.event_completed_at) {\n const created_ms = Date.parse(event.event_created_at)\n const completed_ms = Date.parse(event.event_completed_at)\n if (!Number.isNaN(created_ms) && !Number.isNaN(completed_ms)) {\n const duration = (completed_ms - created_ms) / 1000\n timing += ` (${duration.toFixed(3)}s)`\n }\n }\n timing += ']'\n\n const line = `${indent}${connector}${status_icon} ${event.event_type}#${event.event_id.slice(-4)} ${timing}`\n\n if (visited.has(event.event_id)) {\n return line\n }\n visited.add(event.event_id)\n\n const extension = is_last ? ' ' : '\u2502 '\n const new_indent = indent + extension\n\n const result_items: Array<{ type: 'result'; result: EventResult } | { type: 'child'; child: BaseEvent }> = []\n for (const result of event.event_results.values()) {\n result_items.push({ type: 'result', result })\n }\n const children = parent_to_children.get(event.event_id) ?? []\n const printed_child_ids = new Set<string>(event.event_results.size > 0 ? event.event_results.keys() : [])\n for (const child of children) {\n if (!printed_child_ids.has(child.event_id) && !child.event_emitted_by_handler_id) {\n result_items.push({ type: 'child', child })\n printed_child_ids.add(child.event_id)\n }\n }\n\n if (result_items.length === 0) {\n return line\n }\n\n const child_lines: string[] = []\n result_items.forEach((item, index) => {\n const is_last_item = index === result_items.length - 1\n if (item.type === 'result') {\n child_lines.push(buildResultLine(item.result, new_indent, is_last_item, parent_to_children, visited))\n } else {\n child_lines.push(buildTreeLine(item.child, new_indent, is_last_item, parent_to_children, visited))\n }\n })\n\n return [line, ...child_lines].join('\\n')\n}\n\nexport const buildResultLine = (\n result: EventResult,\n indent: string,\n is_last: boolean,\n parent_to_children: Map<string, BaseEvent[]>,\n visited: Set<string>\n): string => {\n const connector = is_last ? '\u2514\u2500\u2500 ' : '\u251C\u2500\u2500 '\n const status_icon =\n result.status === 'completed'\n ? '\u2705'\n : result.status === 'error' && isCancellationControlError(result.error)\n ? '\uD83D\uDEAB'\n : result.status === 'error'\n ? '\u274C'\n : result.status === 'started'\n ? '\uD83C\uDFC3'\n : '\u23F3'\n\n const handler_label =\n result.handler_name && result.handler_name !== 'anonymous'\n ? result.handler_name\n : result.handler_file_path\n ? result.handler_file_path\n : 'anonymous'\n const handler_display = `${result.eventbus_label}.${handler_label}#${result.handler_id.slice(-4)}`\n let line = `${indent}${connector}${status_icon} ${handler_display}`\n\n if (result.started_at) {\n line += ` [${formatTimestamp(result.started_at)}`\n if (result.completed_at) {\n const started_ms = Date.parse(result.started_at)\n const completed_ms = Date.parse(result.completed_at)\n if (!Number.isNaN(started_ms) && !Number.isNaN(completed_ms)) {\n const duration = (completed_ms - started_ms) / 1000\n line += ` (${duration.toFixed(3)}s)`\n }\n }\n line += ']'\n }\n\n if (result.status === 'error' && result.error) {\n if (result.error instanceof EventHandlerTimeoutError) {\n line += ` \u23F1\uFE0F Timeout: ${result.error.message}`\n } else if (result.error instanceof EventHandlerCancelledError) {\n line += ` Cancelled: ${result.error.message}`\n } else if (result.error instanceof EventHandlerAbortedError) {\n line += ` Aborted: ${result.error.message}`\n } else {\n const error_name = result.error instanceof Error ? result.error.name : 'Error'\n const error_message = result.error instanceof Error ? result.error.message : String(result.error)\n line += ` \u2620\uFE0F ${error_name}: ${error_message}`\n }\n } else if (result.status === 'completed') {\n line += ` \u2192 ${formatResultValue(result.result)}`\n }\n\n const extension = is_last ? ' ' : '\u2502 '\n const new_indent = indent + extension\n\n const direct_children = result.event_children\n if (direct_children.length === 0) {\n return line\n }\n\n const child_lines: string[] = []\n const parent_children = parent_to_children.get(result.event_id) ?? []\n const emitted_children = parent_children.filter((child) => child.event_emitted_by_handler_id === result.handler_id)\n const children_by_id = new Map<string, BaseEvent>()\n direct_children.forEach((child) => {\n children_by_id.set(child.event_id, child)\n })\n emitted_children.forEach((child) => {\n if (!children_by_id.has(child.event_id)) {\n children_by_id.set(child.event_id, child)\n }\n })\n const children_to_print = Array.from(children_by_id.values()).filter((child) => !visited.has(child.event_id))\n\n children_to_print.forEach((child, index) => {\n child_lines.push(buildTreeLine(child, new_indent, index === children_to_print.length - 1, parent_to_children, visited))\n })\n\n return [line, ...child_lines].join('\\n')\n}\n\nconst isCancellationControlError = (error: unknown): boolean =>\n error instanceof EventHandlerCancelledError || error instanceof EventHandlerAbortedError\n\nexport const formatTimestamp = (value?: string): string => {\n if (!value) {\n return 'N/A'\n }\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) {\n return 'N/A'\n }\n return date.toISOString().slice(11, 23)\n}\n\nexport const formatResultValue = (value: unknown): string => {\n if (value === null || value === undefined) {\n return 'None'\n }\n if (value instanceof BaseEvent) {\n return `Event(${value.event_type}#${value.event_id.slice(-4)})`\n }\n if (typeof value === 'string') {\n return JSON.stringify(value)\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value)\n }\n if (Array.isArray(value)) {\n return `list(${value.length} items)`\n }\n if (typeof value === 'object') {\n return `dict(${Object.keys(value as Record<string, unknown>).length} items)`\n }\n return `${typeof value}(...)`\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAE1B,SAAS,0BAA0B,4BAA4B,gCAAgC;AAWxF,MAAM,UAAU,CAAC,QAA4B;AAClD,QAAM,qBAAqB,oBAAI,IAAyB;AAExD,QAAM,WAAW,CAAC,WAAmB,UAA2B;AAC9D,UAAM,WAAW,mBAAmB,IAAI,SAAS,KAAK,CAAC;AACvD,aAAS,KAAK,KAAK;AACnB,uBAAmB,IAAI,WAAW,QAAQ;AAAA,EAC5C;AAEA,QAAM,cAA2B,CAAC;AAClC,QAAM,OAAO,oBAAI,IAAY;AAE7B,aAAW,SAAS,IAAI,cAAc,OAAO,GAAG;AAC9C,UAAM,YAAY,MAAM;AACxB,QAAI,CAAC,aAAa,cAAc,MAAM,YAAY,CAAC,IAAI,cAAc,IAAI,SAAS,GAAG;AACnF,UAAI,CAAC,KAAK,IAAI,MAAM,QAAQ,GAAG;AAC7B,oBAAY,KAAK,KAAK;AACtB,aAAK,IAAI,MAAM,QAAQ;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,WAAW,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,oBAAI,IAAuB;AAC/C,aAAW,QAAQ,aAAa;AAC9B,gBAAY,IAAI,KAAK,UAAU,IAAI;AACnC,eAAW,cAAc,KAAK,mBAAmB;AAC/C,kBAAY,IAAI,WAAW,UAAU,UAAU;AAAA,IACjD;AAAA,EACF;AAEA,aAAW,QAAQ,YAAY,OAAO,GAAG;AACvC,UAAM,YAAY,KAAK;AACvB,QAAI,CAAC,aAAa,cAAc,KAAK,UAAU;AAC7C;AAAA,IACF;AACA,QAAI,CAAC,YAAY,IAAI,SAAS,GAAG;AAC/B;AAAA,IACF;AACA,aAAS,WAAW,IAAI;AAAA,EAC1B;AAEA,aAAW,YAAY,mBAAmB,OAAO,GAAG;AAClD,aAAS,KAAK,CAAC,GAAG,MAAO,EAAE,mBAAmB,EAAE,mBAAmB,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,IAAI,CAAE;AAAA,EAC1H;AAEA,QAAM,QAAkB,CAAC;AACzB,QAAM,YAAY,OAAO,IAAI,aAAa,aAAa,IAAI,SAAS,IAAI,IAAI;AAC5E,QAAM,KAAK,oCAA6B,SAAS,EAAE;AACnD,QAAM,KAAK,IAAI,OAAO,EAAE,CAAC;AAEzB,cAAY,KAAK,CAAC,GAAG,MAAO,EAAE,mBAAmB,EAAE,mBAAmB,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,IAAI,CAAE;AAC3H,QAAM,UAAU,oBAAI,IAAY;AAChC,cAAY,QAAQ,CAAC,OAAO,UAAU;AACpC,UAAM,KAAK,cAAc,OAAO,IAAI,UAAU,YAAY,SAAS,GAAG,oBAAoB,OAAO,CAAC;AAAA,EACpG,CAAC;AAED,QAAM,KAAK,IAAI,OAAO,EAAE,CAAC;AAEzB,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,MAAM,gBAAgB,CAC3B,OACA,QACA,SACA,oBACA,YACW;AACX,QAAM,YAAY,UAAU,wBAAS;AACrC,QAAM,cAAc,MAAM,iBAAiB,cAAc,WAAM,MAAM,iBAAiB,YAAY,cAAO;AAEzG,QAAM,aAAa,gBAAgB,MAAM,gBAAgB;AACzD,MAAI,SAAS,IAAI,UAAU;AAC3B,MAAI,MAAM,oBAAoB;AAC5B,UAAM,aAAa,KAAK,MAAM,MAAM,gBAAgB;AACpD,UAAM,eAAe,KAAK,MAAM,MAAM,kBAAkB;AACxD,QAAI,CAAC,OAAO,MAAM,UAAU,KAAK,CAAC,OAAO,MAAM,YAAY,GAAG;AAC5D,YAAM,YAAY,eAAe,cAAc;AAC/C,gBAAU,KAAK,SAAS,QAAQ,CAAC,CAAC;AAAA,IACpC;AAAA,EACF;AACA,YAAU;AAEV,QAAM,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,IAAI,MAAM,UAAU,IAAI,MAAM,SAAS,MAAM,EAAE,CAAC,IAAI,MAAM;AAE1G,MAAI,QAAQ,IAAI,MAAM,QAAQ,GAAG;AAC/B,WAAO;AAAA,EACT;AACA,UAAQ,IAAI,MAAM,QAAQ;AAE1B,QAAM,YAAY,UAAU,SAAS;AACrC,QAAM,aAAa,SAAS;AAE5B,QAAM,eAAqG,CAAC;AAC5G,aAAW,UAAU,MAAM,cAAc,OAAO,GAAG;AACjD,iBAAa,KAAK,EAAE,MAAM,UAAU,OAAO,CAAC;AAAA,EAC9C;AACA,QAAM,WAAW,mBAAmB,IAAI,MAAM,QAAQ,KAAK,CAAC;AAC5D,QAAM,oBAAoB,IAAI,IAAY,MAAM,cAAc,OAAO,IAAI,MAAM,cAAc,KAAK,IAAI,CAAC,CAAC;AACxG,aAAW,SAAS,UAAU;AAC5B,QAAI,CAAC,kBAAkB,IAAI,MAAM,QAAQ,KAAK,CAAC,MAAM,6BAA6B;AAChF,mBAAa,KAAK,EAAE,MAAM,SAAS,MAAM,CAAC;AAC1C,wBAAkB,IAAI,MAAM,QAAQ;AAAA,IACtC;AAAA,EACF;AAEA,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,cAAwB,CAAC;AAC/B,eAAa,QAAQ,CAAC,MAAM,UAAU;AACpC,UAAM,eAAe,UAAU,aAAa,SAAS;AACrD,QAAI,KAAK,SAAS,UAAU;AAC1B,kBAAY,KAAK,gBAAgB,KAAK,QAAQ,YAAY,cAAc,oBAAoB,OAAO,CAAC;AAAA,IACtG,OAAO;AACL,kBAAY,KAAK,cAAc,KAAK,OAAO,YAAY,cAAc,oBAAoB,OAAO,CAAC;AAAA,IACnG;AAAA,EACF,CAAC;AAED,SAAO,CAAC,MAAM,GAAG,WAAW,EAAE,KAAK,IAAI;AACzC;AAEO,MAAM,kBAAkB,CAC7B,QACA,QACA,SACA,oBACA,YACW;AACX,QAAM,YAAY,UAAU,wBAAS;AACrC,QAAM,cACJ,OAAO,WAAW,cACd,WACA,OAAO,WAAW,WAAW,2BAA2B,OAAO,KAAK,IAClE,cACA,OAAO,WAAW,UAChB,WACA,OAAO,WAAW,YAChB,cACA;AAEZ,QAAM,gBACJ,OAAO,gBAAgB,OAAO,iBAAiB,cAC3C,OAAO,eACP,OAAO,oBACL,OAAO,oBACP;AACR,QAAM,kBAAkB,GAAG,OAAO,cAAc,IAAI,aAAa,IAAI,OAAO,WAAW,MAAM,EAAE,CAAC;AAChG,MAAI,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,IAAI,eAAe;AAEjE,MAAI,OAAO,YAAY;AACrB,YAAQ,KAAK,gBAAgB,OAAO,UAAU,CAAC;AAC/C,QAAI,OAAO,cAAc;AACvB,YAAM,aAAa,KAAK,MAAM,OAAO,UAAU;AAC/C,YAAM,eAAe,KAAK,MAAM,OAAO,YAAY;AACnD,UAAI,CAAC,OAAO,MAAM,UAAU,KAAK,CAAC,OAAO,MAAM,YAAY,GAAG;AAC5D,cAAM,YAAY,eAAe,cAAc;AAC/C,gBAAQ,KAAK,SAAS,QAAQ,CAAC,CAAC;AAAA,MAClC;AAAA,IACF;AACA,YAAQ;AAAA,EACV;AAEA,MAAI,OAAO,WAAW,WAAW,OAAO,OAAO;AAC7C,QAAI,OAAO,iBAAiB,0BAA0B;AACpD,cAAQ,0BAAgB,OAAO,MAAM,OAAO;AAAA,IAC9C,WAAW,OAAO,iBAAiB,4BAA4B;AAC7D,cAAQ,eAAe,OAAO,MAAM,OAAO;AAAA,IAC7C,WAAW,OAAO,iBAAiB,0BAA0B;AAC3D,cAAQ,aAAa,OAAO,MAAM,OAAO;AAAA,IAC3C,OAAO;AACL,YAAM,aAAa,OAAO,iBAAiB,QAAQ,OAAO,MAAM,OAAO;AACvE,YAAM,gBAAgB,OAAO,iBAAiB,QAAQ,OAAO,MAAM,UAAU,OAAO,OAAO,KAAK;AAChG,cAAQ,iBAAO,UAAU,KAAK,aAAa;AAAA,IAC7C;AAAA,EACF,WAAW,OAAO,WAAW,aAAa;AACxC,YAAQ,WAAM,kBAAkB,OAAO,MAAM,CAAC;AAAA,EAChD;AAEA,QAAM,YAAY,UAAU,SAAS;AACrC,QAAM,aAAa,SAAS;AAE5B,QAAM,kBAAkB,OAAO;AAC/B,MAAI,gBAAgB,WAAW,GAAG;AAChC,WAAO;AAAA,EACT;AAEA,QAAM,cAAwB,CAAC;AAC/B,QAAM,kBAAkB,mBAAmB,IAAI,OAAO,QAAQ,KAAK,CAAC;AACpE,QAAM,mBAAmB,gBAAgB,OAAO,CAAC,UAAU,MAAM,gCAAgC,OAAO,UAAU;AAClH,QAAM,iBAAiB,oBAAI,IAAuB;AAClD,kBAAgB,QAAQ,CAAC,UAAU;AACjC,mBAAe,IAAI,MAAM,UAAU,KAAK;AAAA,EAC1C,CAAC;AACD,mBAAiB,QAAQ,CAAC,UAAU;AAClC,QAAI,CAAC,eAAe,IAAI,MAAM,QAAQ,GAAG;AACvC,qBAAe,IAAI,MAAM,UAAU,KAAK;AAAA,IAC1C;AAAA,EACF,CAAC;AACD,QAAM,oBAAoB,MAAM,KAAK,eAAe,OAAO,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,QAAQ,IAAI,MAAM,QAAQ,CAAC;AAE5G,oBAAkB,QAAQ,CAAC,OAAO,UAAU;AAC1C,gBAAY,KAAK,cAAc,OAAO,YAAY,UAAU,kBAAkB,SAAS,GAAG,oBAAoB,OAAO,CAAC;AAAA,EACxH,CAAC;AAED,SAAO,CAAC,MAAM,GAAG,WAAW,EAAE,KAAK,IAAI;AACzC;AAEA,MAAM,6BAA6B,CAAC,UAClC,iBAAiB,8BAA8B,iBAAiB;AAE3D,MAAM,kBAAkB,CAAC,UAA2B;AACzD,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AACA,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG;AAChC,WAAO;AAAA,EACT;AACA,SAAO,KAAK,YAAY,EAAE,MAAM,IAAI,EAAE;AACxC;AAEO,MAAM,oBAAoB,CAAC,UAA2B;AAC3D,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,WAAO;AAAA,EACT;AACA,MAAI,iBAAiB,WAAW;AAC9B,WAAO,SAAS,MAAM,UAAU,IAAI,MAAM,SAAS,MAAM,EAAE,CAAC;AAAA,EAC9D;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW;AAC3D,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,QAAQ,MAAM,MAAM;AAAA,EAC7B;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,QAAQ,OAAO,KAAK,KAAgC,EAAE,MAAM;AAAA,EACrE;AACA,SAAO,GAAG,OAAO,KAAK;AACxB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/esm/retry.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { createAsyncLocalStorage } from "./async_context.js";
|
|
2
|
+
import { isNodeRuntime } from "./optional_deps.js";
|
|
3
|
+
const MULTIPROCESS_SEMAPHORE_DIRNAME = "browser_use_semaphores";
|
|
4
|
+
const MULTIPROCESS_STALE_LOCK_MS = 5 * 60 * 1e3;
|
|
5
|
+
let multiprocess_fallback_reason_logged = null;
|
|
2
6
|
class RetryTimeoutError extends Error {
|
|
3
7
|
timeout_seconds;
|
|
4
8
|
attempt;
|
|
@@ -88,6 +92,7 @@ function getOrCreateSemaphore(name, limit) {
|
|
|
88
92
|
}
|
|
89
93
|
function clearSemaphoreRegistry() {
|
|
90
94
|
SEMAPHORE_REGISTRY.clear();
|
|
95
|
+
multiprocess_fallback_reason_logged = null;
|
|
91
96
|
}
|
|
92
97
|
function retry(options = {}) {
|
|
93
98
|
const {
|
|
@@ -114,23 +119,48 @@ function retry(options = {}) {
|
|
|
114
119
|
const needs_semaphore = semaphore_limit != null && semaphore_limit > 0;
|
|
115
120
|
const is_reentrant = needs_semaphore && held.has(scoped_key);
|
|
116
121
|
let semaphore = null;
|
|
122
|
+
let multiprocess_lock = null;
|
|
117
123
|
let semaphore_acquired = false;
|
|
118
124
|
if (needs_semaphore && !is_reentrant) {
|
|
119
|
-
semaphore = getOrCreateSemaphore(scoped_key, semaphore_limit);
|
|
120
125
|
const effective_sem_timeout = semaphore_timeout != null ? semaphore_timeout : timeout != null ? timeout * Math.max(1, semaphore_limit - 1) : null;
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
if (semaphore_scope === "multiprocess") {
|
|
127
|
+
if (isNodeRuntime()) {
|
|
128
|
+
multiprocess_lock = await acquireMultiprocessSemaphore(scoped_key, semaphore_limit, effective_sem_timeout, semaphore_lax);
|
|
129
|
+
semaphore_acquired = multiprocess_lock !== null;
|
|
130
|
+
} else {
|
|
131
|
+
logMultiprocessFallbackOnce("multiprocess semaphores require a Node.js runtime; falling back to in-process global scope");
|
|
132
|
+
semaphore = getOrCreateSemaphore(scoped_key, semaphore_limit);
|
|
133
|
+
if (effective_sem_timeout != null && effective_sem_timeout > 0) {
|
|
134
|
+
semaphore_acquired = await acquireWithTimeout(semaphore, effective_sem_timeout * 1e3);
|
|
135
|
+
if (!semaphore_acquired) {
|
|
136
|
+
if (!semaphore_lax) {
|
|
137
|
+
throw new SemaphoreTimeoutError(
|
|
138
|
+
`Failed to acquire semaphore "${scoped_key}" within ${effective_sem_timeout}s (limit=${semaphore_limit})`,
|
|
139
|
+
{ semaphore_name: scoped_key, semaphore_limit, timeout_seconds: effective_sem_timeout }
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
await semaphore.acquire();
|
|
145
|
+
semaphore_acquired = true;
|
|
129
146
|
}
|
|
130
147
|
}
|
|
131
148
|
} else {
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
semaphore = getOrCreateSemaphore(scoped_key, semaphore_limit);
|
|
150
|
+
if (effective_sem_timeout != null && effective_sem_timeout > 0) {
|
|
151
|
+
semaphore_acquired = await acquireWithTimeout(semaphore, effective_sem_timeout * 1e3);
|
|
152
|
+
if (!semaphore_acquired) {
|
|
153
|
+
if (!semaphore_lax) {
|
|
154
|
+
throw new SemaphoreTimeoutError(
|
|
155
|
+
`Failed to acquire semaphore "${scoped_key}" within ${effective_sem_timeout}s (limit=${semaphore_limit})`,
|
|
156
|
+
{ semaphore_name: scoped_key, semaphore_limit, timeout_seconds: effective_sem_timeout }
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
await semaphore.acquire();
|
|
162
|
+
semaphore_acquired = true;
|
|
163
|
+
}
|
|
134
164
|
}
|
|
135
165
|
}
|
|
136
166
|
const new_held = new Set(held);
|
|
@@ -164,7 +194,9 @@ function retry(options = {}) {
|
|
|
164
194
|
try {
|
|
165
195
|
return await runWithHeldSemaphores(new_held, runRetryLoop);
|
|
166
196
|
} finally {
|
|
167
|
-
if (semaphore_acquired &&
|
|
197
|
+
if (semaphore_acquired && multiprocess_lock) {
|
|
198
|
+
await multiprocess_lock.release();
|
|
199
|
+
} else if (semaphore_acquired && semaphore) {
|
|
168
200
|
semaphore.release();
|
|
169
201
|
}
|
|
170
202
|
}
|
|
@@ -193,6 +225,101 @@ async function acquireWithTimeout(semaphore, timeout_ms) {
|
|
|
193
225
|
});
|
|
194
226
|
});
|
|
195
227
|
}
|
|
228
|
+
function logMultiprocessFallbackOnce(reason) {
|
|
229
|
+
if (multiprocess_fallback_reason_logged === reason) return;
|
|
230
|
+
multiprocess_fallback_reason_logged = reason;
|
|
231
|
+
console.warn(`[abxbus.retry] ${reason}`);
|
|
232
|
+
}
|
|
233
|
+
async function importNodeModule(specifier) {
|
|
234
|
+
const dynamic_import = Function("module_name", "return import(module_name)");
|
|
235
|
+
return dynamic_import(specifier);
|
|
236
|
+
}
|
|
237
|
+
async function acquireMultiprocessSemaphore(scoped_key, semaphore_limit, semaphore_timeout_seconds, semaphore_lax) {
|
|
238
|
+
const [crypto, fs, os, path] = await Promise.all([
|
|
239
|
+
importNodeModule("node:crypto"),
|
|
240
|
+
importNodeModule("node:fs"),
|
|
241
|
+
importNodeModule("node:os"),
|
|
242
|
+
importNodeModule("node:path")
|
|
243
|
+
]);
|
|
244
|
+
const semaphore_directory = path.join(os.tmpdir(), MULTIPROCESS_SEMAPHORE_DIRNAME);
|
|
245
|
+
const lock_prefix = crypto.createHash("sha256").update(scoped_key).digest("hex").slice(0, 40);
|
|
246
|
+
fs.mkdirSync(semaphore_directory, { recursive: true });
|
|
247
|
+
const start = Date.now();
|
|
248
|
+
let retry_delay_ms = 100;
|
|
249
|
+
while (true) {
|
|
250
|
+
const elapsed_ms = Date.now() - start;
|
|
251
|
+
const remaining_ms = semaphore_timeout_seconds != null && semaphore_timeout_seconds > 0 ? semaphore_timeout_seconds * 1e3 - elapsed_ms : null;
|
|
252
|
+
if (remaining_ms != null && remaining_ms <= 0) {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
for (let slot = 0; slot < semaphore_limit; slot++) {
|
|
256
|
+
const slot_file = path.join(semaphore_directory, `${lock_prefix}.${String(slot).padStart(2, "0")}.lock`);
|
|
257
|
+
const token = `${process.pid}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
|
|
258
|
+
try {
|
|
259
|
+
const fd = fs.openSync(slot_file, "wx", 384);
|
|
260
|
+
try {
|
|
261
|
+
fs.writeFileSync(
|
|
262
|
+
fd,
|
|
263
|
+
JSON.stringify({
|
|
264
|
+
token,
|
|
265
|
+
pid: process.pid,
|
|
266
|
+
semaphore_name: scoped_key,
|
|
267
|
+
created_at_ms: Date.now()
|
|
268
|
+
}),
|
|
269
|
+
"utf8"
|
|
270
|
+
);
|
|
271
|
+
} finally {
|
|
272
|
+
fs.closeSync(fd);
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
release: async () => {
|
|
276
|
+
try {
|
|
277
|
+
const raw = String(fs.readFileSync(slot_file, "utf8") || "").trim();
|
|
278
|
+
const current_owner = raw ? JSON.parse(raw) : null;
|
|
279
|
+
if (current_owner?.token === token) {
|
|
280
|
+
fs.unlinkSync(slot_file);
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (!(error instanceof Error) || error.code !== "EEXIST") {
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const raw = String(fs.readFileSync(slot_file, "utf8") || "").trim();
|
|
292
|
+
const current_owner = raw ? JSON.parse(raw) : null;
|
|
293
|
+
const current_pid = typeof current_owner?.pid === "number" ? current_owner.pid : null;
|
|
294
|
+
if (current_pid != null) {
|
|
295
|
+
try {
|
|
296
|
+
process.kill(current_pid, 0);
|
|
297
|
+
continue;
|
|
298
|
+
} catch {
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const slot_age_ms = Date.now() - fs.statSync(slot_file).mtimeMs;
|
|
302
|
+
if (current_pid != null || slot_age_ms >= MULTIPROCESS_STALE_LOCK_MS) {
|
|
303
|
+
fs.unlinkSync(slot_file);
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const sleep_ms = Math.min(retry_delay_ms, remaining_ms ?? retry_delay_ms);
|
|
310
|
+
if (sleep_ms > 0) {
|
|
311
|
+
await sleep(sleep_ms);
|
|
312
|
+
}
|
|
313
|
+
retry_delay_ms = Math.min(retry_delay_ms * 2, 1e3);
|
|
314
|
+
}
|
|
315
|
+
if (!semaphore_lax) {
|
|
316
|
+
throw new SemaphoreTimeoutError(
|
|
317
|
+
`Failed to acquire semaphore "${scoped_key}" within ${semaphore_timeout_seconds}s (limit=${semaphore_limit})`,
|
|
318
|
+
{ semaphore_name: scoped_key, semaphore_limit, timeout_seconds: semaphore_timeout_seconds ?? 0 }
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
196
323
|
async function _runWithTimeout(fn, timeout_ms, attempt) {
|
|
197
324
|
return new Promise((resolve, reject) => {
|
|
198
325
|
let settled = false;
|
package/dist/esm/retry.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/retry.ts"],
|
|
4
|
-
"sourcesContent": ["import { createAsyncLocalStorage, type AsyncLocalStorageLike } from './async_context.js'\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface RetryOptions {\n /** Total number of attempts including the initial call (1 = no retry, 3 = up to 2 retries). Default: 1 */\n max_attempts?: number\n\n /** Seconds to wait between retries. Default: 0 */\n retry_after?: number\n\n /** Multiplier applied to retry_after after each attempt for exponential backoff. Default: 1.0 (constant delay) */\n retry_backoff_factor?: number\n\n /** Only retry when the thrown error matches one of these matchers. Accepts error class constructors,\n * string error names (matched against error.name), or RegExp patterns (tested against String(error)).\n * Default: undefined (retry on any error) */\n retry_on_errors?: Array<(new (...args: any[]) => Error) | string | RegExp>\n\n /** Per-attempt timeout in seconds. Default: undefined (no per-attempt timeout) */\n timeout?: number | null\n\n /** Maximum concurrent executions sharing this semaphore. Default: undefined (no concurrency limit) */\n semaphore_limit?: number | null\n\n /** Semaphore identifier. Functions with the same name share the same concurrency slot pool. Default: function name.\n * If a function is provided, it receives the same arguments as the wrapped function. */\n semaphore_name?: string | ((...args: any[]) => string) | null\n\n /** If true, proceed without concurrency limit when semaphore acquisition times out. Default: true */\n semaphore_lax?: boolean\n\n /** Semaphore scoping strategy. Default: 'global'\n * - 'global': all calls share one semaphore (keyed by semaphore_name)\n * - 'class': all instances of the same class share one semaphore (keyed by className.semaphore_name)\n * - 'instance': each object instance gets its own semaphore (keyed by instanceId.semaphore_name)\n * 'class' and 'instance' require `this` to be an object; they fall back to 'global' for standalone calls. */\n semaphore_scope?: 'global' | 'class' | 'instance'\n\n /** Maximum seconds to wait for semaphore acquisition. Default: undefined \u2192 timeout * max(1, limit - 1) */\n semaphore_timeout?: number | null\n}\n\n// \u2500\u2500\u2500 Errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Thrown when a single attempt exceeds the per-attempt timeout. */\nexport class RetryTimeoutError extends Error {\n timeout_seconds: number\n attempt: number\n\n constructor(message: string, params: { timeout_seconds: number; attempt: number }) {\n super(message)\n this.name = 'RetryTimeoutError'\n this.timeout_seconds = params.timeout_seconds\n this.attempt = params.attempt\n }\n}\n\n/** Thrown (when semaphore_lax=false) if the semaphore cannot be acquired within the timeout. */\nexport class SemaphoreTimeoutError extends Error {\n semaphore_name: string\n semaphore_limit: number\n timeout_seconds: number\n\n constructor(message: string, params: { semaphore_name: string; semaphore_limit: number; timeout_seconds: number }) {\n super(message)\n this.name = 'SemaphoreTimeoutError'\n this.semaphore_name = params.semaphore_name\n this.semaphore_limit = params.semaphore_limit\n this.timeout_seconds = params.timeout_seconds\n }\n}\n\n// \u2500\u2500\u2500 Re-entrancy tracking via AsyncLocalStorage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Prevents deadlocks when a retry()-wrapped function calls another retry()-wrapped\n// function that shares the same semaphore (or calls itself recursively).\n//\n// Each async call stack tracks which semaphore names it currently holds. When a\n// nested call encounters a semaphore it already holds, it skips acquisition and\n// runs directly within the parent's slot.\n//\n// Uses the same AsyncLocalStorage polyfill as the rest of abxbus (see async_context.ts)\n// so it works in Node.js and gracefully degrades to a no-op in browsers.\n\ntype ReentrantStore = Set<string>\n\n// Separate AsyncLocalStorage instance for retry re-entrancy tracking.\n// Created via the shared factory in async_context.ts (returns null in browsers).\nconst retry_context_storage: AsyncLocalStorageLike | null = createAsyncLocalStorage()\n\nfunction getHeldSemaphores(): ReentrantStore {\n return (retry_context_storage?.getStore() as ReentrantStore | undefined) ?? new Set()\n}\n\nfunction runWithHeldSemaphores<T>(held: ReentrantStore, fn: () => T): T {\n if (!retry_context_storage) return fn()\n return retry_context_storage.run(held, fn)\n}\n\n// \u2500\u2500\u2500 Semaphore scope helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nlet _next_instance_id = 1\nconst _instance_ids = new WeakMap<object, number>()\n\nfunction scopedSemaphoreKey(base_name: string, scope: 'global' | 'class' | 'instance', context: unknown): string {\n if (scope === 'class' && context && typeof context === 'object') {\n return `${(context as object).constructor?.name ?? 'Object'}.${base_name}`\n }\n if (scope === 'instance' && context && typeof context === 'object') {\n let id = _instance_ids.get(context as object)\n if (id === undefined) {\n id = _next_instance_id++\n _instance_ids.set(context as object, id)\n }\n return `${id}.${base_name}`\n }\n return base_name\n}\n\n// \u2500\u2500\u2500 Global semaphore registry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nclass RetrySemaphore {\n readonly size: number\n private inUse: number\n private waiters: Array<() => void>\n\n constructor(size: number) {\n this.size = size\n this.inUse = 0\n this.waiters = []\n }\n\n async acquire(): Promise<void> {\n if (this.size === Infinity) {\n return\n }\n if (this.inUse < this.size) {\n this.inUse += 1\n return\n }\n await new Promise<void>((resolve) => {\n this.waiters.push(resolve)\n })\n }\n\n release(): void {\n if (this.size === Infinity) {\n return\n }\n const next = this.waiters.shift()\n if (next) {\n // Handoff: keep the permit accounted for and transfer it directly to the waiter.\n next()\n return\n }\n this.inUse = Math.max(0, this.inUse - 1)\n }\n}\n\nconst SEMAPHORE_REGISTRY = new Map<string, RetrySemaphore>()\n\nfunction getOrCreateSemaphore(name: string, limit: number): RetrySemaphore {\n const existing = SEMAPHORE_REGISTRY.get(name)\n if (existing && existing.size === limit) return existing\n const sem = new RetrySemaphore(limit)\n SEMAPHORE_REGISTRY.set(name, sem)\n return sem\n}\n\n/** Reset the global semaphore registry. Useful in tests. */\nexport function clearSemaphoreRegistry(): void {\n SEMAPHORE_REGISTRY.clear()\n}\n\n// \u2500\u2500\u2500 retry() decorator / higher-order wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Usage as a higher-order function (works on any async function):\n//\n// const fetchWithRetry = retry({ max_attempts: 3, retry_after: 1 })(async (url: string) => {\n// return await fetch(url)\n// })\n//\n// Usage as a TC39 Stage 3 decorator on class methods (TS 5.0+):\n//\n// class ApiClient {\n// @retry({ max_attempts: 3, retry_after: 1 })\n// async fetchData(): Promise<Data> { ... }\n// }\n//\n// Usage on event bus handlers:\n//\n// bus.on(MyEvent, retry({ max_attempts: 3 })(async (event) => {\n// await riskyOperation(event.data)\n// }))\n\nexport function retry(options: RetryOptions = {}) {\n const {\n max_attempts = 1,\n retry_after = 0,\n retry_backoff_factor = 1.0,\n retry_on_errors,\n timeout,\n semaphore_limit,\n semaphore_name: semaphore_name_option,\n semaphore_lax = true,\n semaphore_scope = 'global',\n semaphore_timeout,\n } = options\n\n return function decorator<T extends (...args: any[]) => any>(target: T, _context?: ClassMethodDecoratorContext): T {\n const fn_name = target.name || (_context?.name as string) || 'anonymous'\n const effective_max_attempts = Math.max(1, max_attempts)\n const effective_retry_after = Math.max(0, retry_after)\n\n async function retryWrapper(this: any, ...args: any[]): Promise<any> {\n const base_name = typeof semaphore_name_option === 'function' ? semaphore_name_option(...args) : (semaphore_name_option ?? fn_name)\n const sem_name = typeof base_name === 'string' ? base_name : String(base_name)\n // \u2500\u2500 Resolve scoped semaphore key at call time (uses `this` for class/instance scopes) \u2500\u2500\n const scoped_key = scopedSemaphoreKey(sem_name, semaphore_scope, this)\n\n // \u2500\u2500 Check re-entrancy: skip semaphore if we already hold it in this async context \u2500\u2500\n const held = getHeldSemaphores()\n const needs_semaphore = semaphore_limit != null && semaphore_limit > 0\n const is_reentrant = needs_semaphore && held.has(scoped_key)\n\n // \u2500\u2500 Semaphore acquisition (held across all retry attempts, skipped if re-entrant) \u2500\u2500\n let semaphore: RetrySemaphore | null = null\n let semaphore_acquired = false\n\n if (needs_semaphore && !is_reentrant) {\n semaphore = getOrCreateSemaphore(scoped_key, semaphore_limit!)\n\n const effective_sem_timeout =\n semaphore_timeout != null ? semaphore_timeout : timeout != null ? timeout * Math.max(1, semaphore_limit! - 1) : null\n\n if (effective_sem_timeout != null && effective_sem_timeout > 0) {\n semaphore_acquired = await acquireWithTimeout(semaphore, effective_sem_timeout * 1000)\n if (!semaphore_acquired) {\n if (!semaphore_lax) {\n throw new SemaphoreTimeoutError(\n `Failed to acquire semaphore \"${scoped_key}\" within ${effective_sem_timeout}s (limit=${semaphore_limit})`,\n { semaphore_name: scoped_key, semaphore_limit: semaphore_limit!, timeout_seconds: effective_sem_timeout }\n )\n }\n // lax mode: proceed without concurrency limit\n }\n } else {\n // No timeout configured: wait indefinitely for a slot\n await semaphore.acquire()\n semaphore_acquired = true\n }\n }\n\n // \u2500\u2500 Build the set of held semaphores for nested calls \u2500\u2500\n const new_held = new Set(held)\n if (semaphore_acquired) {\n new_held.add(scoped_key)\n }\n\n // \u2500\u2500 Retry loop (runs inside the semaphore and re-entrancy context) \u2500\u2500\n const runRetryLoop = async (): Promise<any> => {\n for (let attempt = 1; attempt <= effective_max_attempts; attempt++) {\n try {\n if (timeout != null && timeout > 0) {\n return await _runWithTimeout(() => Promise.resolve(target.apply(this, args)), timeout * 1000, attempt)\n } else {\n return await Promise.resolve(target.apply(this, args))\n }\n } catch (error) {\n // Check if this error type should trigger a retry\n if (retry_on_errors && retry_on_errors.length > 0) {\n const is_retryable = retry_on_errors.some((matcher) =>\n typeof matcher === 'string'\n ? (error as Error)?.name === matcher\n : matcher instanceof RegExp\n ? matcher.test(String(error))\n : error instanceof matcher\n )\n if (!is_retryable) throw error\n }\n\n // Last attempt: rethrow\n if (attempt >= effective_max_attempts) throw error\n\n // Wait before next attempt with exponential backoff\n const delay_seconds = effective_retry_after * Math.pow(retry_backoff_factor, attempt - 1)\n if (delay_seconds > 0) {\n await sleep(delay_seconds * 1000)\n }\n }\n }\n\n // Unreachable, but satisfies the type checker\n throw new Error(`retry(${fn_name}): unexpected end of retry loop`)\n }\n\n try {\n return await runWithHeldSemaphores(new_held, runRetryLoop)\n } finally {\n if (semaphore_acquired && semaphore) {\n semaphore.release()\n }\n }\n }\n\n Object.defineProperty(retryWrapper, 'name', { value: fn_name, configurable: true })\n return retryWrapper as unknown as T\n }\n}\n\n// \u2500\u2500\u2500 Internal helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Try to acquire a semaphore within a timeout. Returns true if acquired, false if timed out.\n * If the semaphore is acquired after the timeout (due to the waiter remaining queued),\n * it is immediately released to avoid leaking slots.\n */\nasync function acquireWithTimeout(semaphore: RetrySemaphore, timeout_ms: number): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n let settled = false\n\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true\n resolve(false)\n }\n }, timeout_ms)\n\n semaphore.acquire().then(() => {\n if (!settled) {\n settled = true\n clearTimeout(timer)\n resolve(true)\n } else {\n // Acquired after timeout fired \u2014 release immediately to avoid slot leak\n semaphore.release()\n }\n })\n })\n}\n\n/** Run fn() with a timeout. Rejects with RetryTimeoutError if the timeout fires first. */\nasync function _runWithTimeout<T>(fn: () => Promise<T>, timeout_ms: number, attempt: number): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n let settled = false\n\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true\n reject(\n new RetryTimeoutError(`Timed out after ${timeout_ms / 1000}s (attempt ${attempt})`, {\n timeout_seconds: timeout_ms / 1000,\n attempt,\n })\n )\n }\n }, timeout_ms)\n\n fn().then(\n (value) => {\n if (!settled) {\n settled = true\n clearTimeout(timer)\n resolve(value)\n }\n },\n (error) => {\n if (!settled) {\n settled = true\n clearTimeout(timer)\n reject(error)\n }\n }\n )\n })\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,+BAA2D;
|
|
4
|
+
"sourcesContent": ["import { createAsyncLocalStorage, type AsyncLocalStorageLike } from './async_context.js'\nimport { isNodeRuntime } from './optional_deps.js'\n\ntype SemaphoreScope = 'multiprocess' | 'global' | 'class' | 'instance'\n\ntype MultiprocessLockHandle = {\n release: () => Promise<void>\n}\n\nconst MULTIPROCESS_SEMAPHORE_DIRNAME = 'browser_use_semaphores'\nconst MULTIPROCESS_STALE_LOCK_MS = 5 * 60 * 1000\n\nlet multiprocess_fallback_reason_logged: string | null = null\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface RetryOptions {\n /** Total number of attempts including the initial call (1 = no retry, 3 = up to 2 retries). Default: 1 */\n max_attempts?: number\n\n /** Seconds to wait between retries. Default: 0 */\n retry_after?: number\n\n /** Multiplier applied to retry_after after each attempt for exponential backoff. Default: 1.0 (constant delay) */\n retry_backoff_factor?: number\n\n /** Only retry when the thrown error matches one of these matchers. Accepts error class constructors,\n * string error names (matched against error.name), or RegExp patterns (tested against String(error)).\n * Default: undefined (retry on any error) */\n retry_on_errors?: Array<(new (...args: any[]) => Error) | string | RegExp>\n\n /** Per-attempt timeout in seconds. Default: undefined (no per-attempt timeout) */\n timeout?: number | null\n\n /** Maximum concurrent executions sharing this semaphore. Default: undefined (no concurrency limit) */\n semaphore_limit?: number | null\n\n /** Semaphore identifier. Functions with the same name share the same concurrency slot pool. Default: function name.\n * If a function is provided, it receives the same arguments as the wrapped function. */\n semaphore_name?: string | ((...args: any[]) => string) | null\n\n /** If true, proceed without concurrency limit when semaphore acquisition times out. Default: true */\n semaphore_lax?: boolean\n\n /** Semaphore scoping strategy. Default: 'global'\n * - 'multiprocess': all processes on the machine share one semaphore (Node.js only)\n * - 'global': all calls share one semaphore (keyed by semaphore_name)\n * - 'class': all instances of the same class share one semaphore (keyed by className.semaphore_name)\n * - 'instance': each object instance gets its own semaphore (keyed by instanceId.semaphore_name)\n * 'class' and 'instance' require `this` to be an object; they fall back to 'global' for standalone calls. */\n semaphore_scope?: SemaphoreScope\n\n /** Maximum seconds to wait for semaphore acquisition. Default: undefined \u2192 timeout * max(1, limit - 1) */\n semaphore_timeout?: number | null\n}\n\n// \u2500\u2500\u2500 Errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Thrown when a single attempt exceeds the per-attempt timeout. */\nexport class RetryTimeoutError extends Error {\n timeout_seconds: number\n attempt: number\n\n constructor(message: string, params: { timeout_seconds: number; attempt: number }) {\n super(message)\n this.name = 'RetryTimeoutError'\n this.timeout_seconds = params.timeout_seconds\n this.attempt = params.attempt\n }\n}\n\n/** Thrown (when semaphore_lax=false) if the semaphore cannot be acquired within the timeout. */\nexport class SemaphoreTimeoutError extends Error {\n semaphore_name: string\n semaphore_limit: number\n timeout_seconds: number\n\n constructor(message: string, params: { semaphore_name: string; semaphore_limit: number; timeout_seconds: number }) {\n super(message)\n this.name = 'SemaphoreTimeoutError'\n this.semaphore_name = params.semaphore_name\n this.semaphore_limit = params.semaphore_limit\n this.timeout_seconds = params.timeout_seconds\n }\n}\n\n// \u2500\u2500\u2500 Re-entrancy tracking via AsyncLocalStorage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Prevents deadlocks when a retry()-wrapped function calls another retry()-wrapped\n// function that shares the same semaphore (or calls itself recursively).\n//\n// Each async call stack tracks which semaphore names it currently holds. When a\n// nested call encounters a semaphore it already holds, it skips acquisition and\n// runs directly within the parent's slot.\n//\n// Uses the same AsyncLocalStorage polyfill as the rest of abxbus (see async_context.ts)\n// so it works in Node.js and gracefully degrades to a no-op in browsers.\n\ntype ReentrantStore = Set<string>\n\n// Separate AsyncLocalStorage instance for retry re-entrancy tracking.\n// Created via the shared factory in async_context.ts (returns null in browsers).\nconst retry_context_storage: AsyncLocalStorageLike | null = createAsyncLocalStorage()\n\nfunction getHeldSemaphores(): ReentrantStore {\n return (retry_context_storage?.getStore() as ReentrantStore | undefined) ?? new Set()\n}\n\nfunction runWithHeldSemaphores<T>(held: ReentrantStore, fn: () => T): T {\n if (!retry_context_storage) return fn()\n return retry_context_storage.run(held, fn)\n}\n\n// \u2500\u2500\u2500 Semaphore scope helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nlet _next_instance_id = 1\nconst _instance_ids = new WeakMap<object, number>()\n\nfunction scopedSemaphoreKey(base_name: string, scope: SemaphoreScope, context: unknown): string {\n if (scope === 'class' && context && typeof context === 'object') {\n return `${(context as object).constructor?.name ?? 'Object'}.${base_name}`\n }\n if (scope === 'instance' && context && typeof context === 'object') {\n let id = _instance_ids.get(context as object)\n if (id === undefined) {\n id = _next_instance_id++\n _instance_ids.set(context as object, id)\n }\n return `${id}.${base_name}`\n }\n return base_name\n}\n\n// \u2500\u2500\u2500 Global semaphore registry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nclass RetrySemaphore {\n readonly size: number\n private inUse: number\n private waiters: Array<() => void>\n\n constructor(size: number) {\n this.size = size\n this.inUse = 0\n this.waiters = []\n }\n\n async acquire(): Promise<void> {\n if (this.size === Infinity) {\n return\n }\n if (this.inUse < this.size) {\n this.inUse += 1\n return\n }\n await new Promise<void>((resolve) => {\n this.waiters.push(resolve)\n })\n }\n\n release(): void {\n if (this.size === Infinity) {\n return\n }\n const next = this.waiters.shift()\n if (next) {\n // Handoff: keep the permit accounted for and transfer it directly to the waiter.\n next()\n return\n }\n this.inUse = Math.max(0, this.inUse - 1)\n }\n}\n\nconst SEMAPHORE_REGISTRY = new Map<string, RetrySemaphore>()\n\nfunction getOrCreateSemaphore(name: string, limit: number): RetrySemaphore {\n const existing = SEMAPHORE_REGISTRY.get(name)\n if (existing && existing.size === limit) return existing\n const sem = new RetrySemaphore(limit)\n SEMAPHORE_REGISTRY.set(name, sem)\n return sem\n}\n\n/** Reset the global semaphore registry. Useful in tests. */\nexport function clearSemaphoreRegistry(): void {\n SEMAPHORE_REGISTRY.clear()\n multiprocess_fallback_reason_logged = null\n}\n\n// \u2500\u2500\u2500 retry() decorator / higher-order wrapper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Usage as a higher-order function (works on any async function):\n//\n// const fetchWithRetry = retry({ max_attempts: 3, retry_after: 1 })(async (url: string) => {\n// return await fetch(url)\n// })\n//\n// Usage as a TC39 Stage 3 decorator on class methods (TS 5.0+):\n//\n// class ApiClient {\n// @retry({ max_attempts: 3, retry_after: 1 })\n// async fetchData(): Promise<Data> { ... }\n// }\n//\n// Usage on event bus handlers:\n//\n// bus.on(MyEvent, retry({ max_attempts: 3 })(async (event) => {\n// await riskyOperation(event.data)\n// }))\n\nexport function retry(options: RetryOptions = {}) {\n const {\n max_attempts = 1,\n retry_after = 0,\n retry_backoff_factor = 1.0,\n retry_on_errors,\n timeout,\n semaphore_limit,\n semaphore_name: semaphore_name_option,\n semaphore_lax = true,\n semaphore_scope = 'global',\n semaphore_timeout,\n } = options\n\n return function decorator<T extends (...args: any[]) => any>(target: T, _context?: ClassMethodDecoratorContext): T {\n const fn_name = target.name || (_context?.name as string) || 'anonymous'\n const effective_max_attempts = Math.max(1, max_attempts)\n const effective_retry_after = Math.max(0, retry_after)\n\n async function retryWrapper(this: any, ...args: any[]): Promise<any> {\n const base_name = typeof semaphore_name_option === 'function' ? semaphore_name_option(...args) : (semaphore_name_option ?? fn_name)\n const sem_name = typeof base_name === 'string' ? base_name : String(base_name)\n // \u2500\u2500 Resolve scoped semaphore key at call time (uses `this` for class/instance scopes) \u2500\u2500\n const scoped_key = scopedSemaphoreKey(sem_name, semaphore_scope, this)\n\n // \u2500\u2500 Check re-entrancy: skip semaphore if we already hold it in this async context \u2500\u2500\n const held = getHeldSemaphores()\n const needs_semaphore = semaphore_limit != null && semaphore_limit > 0\n const is_reentrant = needs_semaphore && held.has(scoped_key)\n\n // \u2500\u2500 Semaphore acquisition (held across all retry attempts, skipped if re-entrant) \u2500\u2500\n let semaphore: RetrySemaphore | null = null\n let multiprocess_lock: MultiprocessLockHandle | null = null\n let semaphore_acquired = false\n\n if (needs_semaphore && !is_reentrant) {\n const effective_sem_timeout =\n semaphore_timeout != null ? semaphore_timeout : timeout != null ? timeout * Math.max(1, semaphore_limit! - 1) : null\n\n if (semaphore_scope === 'multiprocess') {\n if (isNodeRuntime()) {\n multiprocess_lock = await acquireMultiprocessSemaphore(scoped_key, semaphore_limit!, effective_sem_timeout, semaphore_lax)\n semaphore_acquired = multiprocess_lock !== null\n } else {\n logMultiprocessFallbackOnce('multiprocess semaphores require a Node.js runtime; falling back to in-process global scope')\n semaphore = getOrCreateSemaphore(scoped_key, semaphore_limit!)\n if (effective_sem_timeout != null && effective_sem_timeout > 0) {\n semaphore_acquired = await acquireWithTimeout(semaphore, effective_sem_timeout * 1000)\n if (!semaphore_acquired) {\n if (!semaphore_lax) {\n throw new SemaphoreTimeoutError(\n `Failed to acquire semaphore \"${scoped_key}\" within ${effective_sem_timeout}s (limit=${semaphore_limit})`,\n { semaphore_name: scoped_key, semaphore_limit: semaphore_limit!, timeout_seconds: effective_sem_timeout }\n )\n }\n }\n } else {\n await semaphore.acquire()\n semaphore_acquired = true\n }\n }\n } else {\n semaphore = getOrCreateSemaphore(scoped_key, semaphore_limit!)\n\n if (effective_sem_timeout != null && effective_sem_timeout > 0) {\n semaphore_acquired = await acquireWithTimeout(semaphore, effective_sem_timeout * 1000)\n if (!semaphore_acquired) {\n if (!semaphore_lax) {\n throw new SemaphoreTimeoutError(\n `Failed to acquire semaphore \"${scoped_key}\" within ${effective_sem_timeout}s (limit=${semaphore_limit})`,\n { semaphore_name: scoped_key, semaphore_limit: semaphore_limit!, timeout_seconds: effective_sem_timeout }\n )\n }\n }\n } else {\n await semaphore.acquire()\n semaphore_acquired = true\n }\n }\n }\n\n // \u2500\u2500 Build the set of held semaphores for nested calls \u2500\u2500\n const new_held = new Set(held)\n if (semaphore_acquired) {\n new_held.add(scoped_key)\n }\n\n // \u2500\u2500 Retry loop (runs inside the semaphore and re-entrancy context) \u2500\u2500\n const runRetryLoop = async (): Promise<any> => {\n for (let attempt = 1; attempt <= effective_max_attempts; attempt++) {\n try {\n if (timeout != null && timeout > 0) {\n return await _runWithTimeout(() => Promise.resolve(target.apply(this, args)), timeout * 1000, attempt)\n } else {\n return await Promise.resolve(target.apply(this, args))\n }\n } catch (error) {\n // Check if this error type should trigger a retry\n if (retry_on_errors && retry_on_errors.length > 0) {\n const is_retryable = retry_on_errors.some((matcher) =>\n typeof matcher === 'string'\n ? (error as Error)?.name === matcher\n : matcher instanceof RegExp\n ? matcher.test(String(error))\n : error instanceof matcher\n )\n if (!is_retryable) throw error\n }\n\n // Last attempt: rethrow\n if (attempt >= effective_max_attempts) throw error\n\n // Wait before next attempt with exponential backoff\n const delay_seconds = effective_retry_after * Math.pow(retry_backoff_factor, attempt - 1)\n if (delay_seconds > 0) {\n await sleep(delay_seconds * 1000)\n }\n }\n }\n\n // Unreachable, but satisfies the type checker\n throw new Error(`retry(${fn_name}): unexpected end of retry loop`)\n }\n\n try {\n return await runWithHeldSemaphores(new_held, runRetryLoop)\n } finally {\n if (semaphore_acquired && multiprocess_lock) {\n await multiprocess_lock.release()\n } else if (semaphore_acquired && semaphore) {\n semaphore.release()\n }\n }\n }\n\n Object.defineProperty(retryWrapper, 'name', { value: fn_name, configurable: true })\n return retryWrapper as unknown as T\n }\n}\n\n// \u2500\u2500\u2500 Internal helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Try to acquire a semaphore within a timeout. Returns true if acquired, false if timed out.\n * If the semaphore is acquired after the timeout (due to the waiter remaining queued),\n * it is immediately released to avoid leaking slots.\n */\nasync function acquireWithTimeout(semaphore: RetrySemaphore, timeout_ms: number): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n let settled = false\n\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true\n resolve(false)\n }\n }, timeout_ms)\n\n semaphore.acquire().then(() => {\n if (!settled) {\n settled = true\n clearTimeout(timer)\n resolve(true)\n } else {\n // Acquired after timeout fired \u2014 release immediately to avoid slot leak\n semaphore.release()\n }\n })\n })\n}\n\nfunction logMultiprocessFallbackOnce(reason: string): void {\n if (multiprocess_fallback_reason_logged === reason) return\n multiprocess_fallback_reason_logged = reason\n console.warn(`[abxbus.retry] ${reason}`)\n}\n\nasync function importNodeModule(specifier: string): Promise<any> {\n const dynamic_import = Function('module_name', 'return import(module_name)') as (module_name: string) => Promise<unknown>\n return dynamic_import(specifier) as Promise<any>\n}\n\nasync function acquireMultiprocessSemaphore(\n scoped_key: string,\n semaphore_limit: number,\n semaphore_timeout_seconds: number | null,\n semaphore_lax: boolean\n): Promise<MultiprocessLockHandle | null> {\n const [crypto, fs, os, path] = await Promise.all([\n importNodeModule('node:crypto'),\n importNodeModule('node:fs'),\n importNodeModule('node:os'),\n importNodeModule('node:path'),\n ])\n const semaphore_directory = path.join(os.tmpdir(), MULTIPROCESS_SEMAPHORE_DIRNAME)\n const lock_prefix = crypto.createHash('sha256').update(scoped_key).digest('hex').slice(0, 40)\n fs.mkdirSync(semaphore_directory, { recursive: true })\n\n const start = Date.now()\n let retry_delay_ms = 100\n\n while (true) {\n const elapsed_ms = Date.now() - start\n const remaining_ms =\n semaphore_timeout_seconds != null && semaphore_timeout_seconds > 0 ? semaphore_timeout_seconds * 1000 - elapsed_ms : null\n\n if (remaining_ms != null && remaining_ms <= 0) {\n break\n }\n\n for (let slot = 0; slot < semaphore_limit; slot++) {\n const slot_file = path.join(semaphore_directory, `${lock_prefix}.${String(slot).padStart(2, '0')}.lock`)\n const token = `${process.pid}:${Date.now()}:${Math.random().toString(16).slice(2)}`\n\n try {\n const fd = fs.openSync(slot_file, 'wx', 0o600)\n try {\n fs.writeFileSync(\n fd,\n JSON.stringify({\n token,\n pid: process.pid,\n semaphore_name: scoped_key,\n created_at_ms: Date.now(),\n }),\n 'utf8'\n )\n } finally {\n fs.closeSync(fd)\n }\n return {\n release: async () => {\n try {\n const raw = String(fs.readFileSync(slot_file, 'utf8') || '').trim()\n const current_owner = raw ? (JSON.parse(raw) as { token?: unknown }) : null\n if (current_owner?.token === token) {\n fs.unlinkSync(slot_file)\n }\n } catch {}\n },\n }\n } catch (error) {\n if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw error\n }\n\n try {\n const raw = String(fs.readFileSync(slot_file, 'utf8') || '').trim()\n const current_owner = raw ? (JSON.parse(raw) as { pid?: unknown }) : null\n const current_pid = typeof current_owner?.pid === 'number' ? current_owner.pid : null\n if (current_pid != null) {\n try {\n process.kill(current_pid, 0)\n continue\n } catch {}\n }\n\n const slot_age_ms = Date.now() - fs.statSync(slot_file).mtimeMs\n if (current_pid != null || slot_age_ms >= MULTIPROCESS_STALE_LOCK_MS) {\n fs.unlinkSync(slot_file)\n }\n } catch {}\n }\n }\n\n const sleep_ms = Math.min(retry_delay_ms, remaining_ms ?? retry_delay_ms)\n if (sleep_ms > 0) {\n await sleep(sleep_ms)\n }\n retry_delay_ms = Math.min(retry_delay_ms * 2, 1000)\n }\n\n if (!semaphore_lax) {\n throw new SemaphoreTimeoutError(\n `Failed to acquire semaphore \"${scoped_key}\" within ${semaphore_timeout_seconds}s (limit=${semaphore_limit})`,\n { semaphore_name: scoped_key, semaphore_limit, timeout_seconds: semaphore_timeout_seconds ?? 0 }\n )\n }\n\n return null\n}\n\n/** Run fn() with a timeout. Rejects with RetryTimeoutError if the timeout fires first. */\nasync function _runWithTimeout<T>(fn: () => Promise<T>, timeout_ms: number, attempt: number): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n let settled = false\n\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true\n reject(\n new RetryTimeoutError(`Timed out after ${timeout_ms / 1000}s (attempt ${attempt})`, {\n timeout_seconds: timeout_ms / 1000,\n attempt,\n })\n )\n }\n }, timeout_ms)\n\n fn().then(\n (value) => {\n if (!settled) {\n settled = true\n clearTimeout(timer)\n resolve(value)\n }\n },\n (error) => {\n if (!settled) {\n settled = true\n clearTimeout(timer)\n reject(error)\n }\n }\n )\n })\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -14,6 +14,7 @@ export declare const BaseEventSchema: z.ZodObject<{
|
|
|
14
14
|
event_slow_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
15
15
|
event_handler_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
16
16
|
event_handler_slow_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
17
|
+
event_blocks_parent_completion: z.ZodOptional<z.ZodBoolean>;
|
|
17
18
|
event_parent_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
18
19
|
event_path: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
19
20
|
event_result_type: z.ZodOptional<z.ZodUnknown>;
|
|
@@ -43,7 +44,7 @@ export declare const BaseEventSchema: z.ZodObject<{
|
|
|
43
44
|
}, z.core.$loose>;
|
|
44
45
|
export type BaseEventData = z.infer<typeof BaseEventSchema>;
|
|
45
46
|
export type BaseEventJSON = BaseEventData & Record<string, unknown>;
|
|
46
|
-
type BaseEventFields = Pick<BaseEventData, 'event_id' | 'event_created_at' | 'event_type' | 'event_version' | 'event_timeout' | 'event_slow_timeout' | 'event_handler_timeout' | 'event_handler_slow_timeout' | 'event_parent_id' | 'event_path' | 'event_result_type' | 'event_emitted_by_handler_id' | 'event_pending_bus_count' | 'event_status' | 'event_started_at' | 'event_completed_at' | 'event_results' | 'event_concurrency' | 'event_handler_concurrency' | 'event_handler_completion'>;
|
|
47
|
+
type BaseEventFields = Pick<BaseEventData, 'event_id' | 'event_created_at' | 'event_type' | 'event_version' | 'event_timeout' | 'event_slow_timeout' | 'event_handler_timeout' | 'event_handler_slow_timeout' | 'event_blocks_parent_completion' | 'event_parent_id' | 'event_path' | 'event_result_type' | 'event_emitted_by_handler_id' | 'event_pending_bus_count' | 'event_status' | 'event_started_at' | 'event_completed_at' | 'event_results' | 'event_concurrency' | 'event_handler_concurrency' | 'event_handler_completion'>;
|
|
47
48
|
export type BaseEventInit<TFields extends Record<string, unknown>> = TFields & Partial<BaseEventFields>;
|
|
48
49
|
type BaseEventSchemaShape = typeof BaseEventSchema.shape;
|
|
49
50
|
export type EventSchema<TShape extends z.ZodRawShape> = z.ZodObject<BaseEventSchemaShape & TShape>;
|
|
@@ -95,6 +96,7 @@ export declare class BaseEvent {
|
|
|
95
96
|
event_slow_timeout?: number | null;
|
|
96
97
|
event_handler_timeout?: number | null;
|
|
97
98
|
event_handler_slow_timeout?: number | null;
|
|
99
|
+
event_blocks_parent_completion: boolean;
|
|
98
100
|
event_parent_id: string | null;
|
|
99
101
|
event_path: string[];
|
|
100
102
|
event_result_type?: z.ZodTypeAny;
|
|
@@ -118,6 +120,7 @@ export declare class BaseEvent {
|
|
|
118
120
|
event_slow_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
119
121
|
event_handler_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
120
122
|
event_handler_slow_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
123
|
+
event_blocks_parent_completion: z.ZodOptional<z.ZodBoolean>;
|
|
121
124
|
event_parent_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
122
125
|
event_path: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
123
126
|
event_result_type: z.ZodOptional<z.ZodUnknown>;
|
|
@@ -145,12 +148,11 @@ export declare class BaseEvent {
|
|
|
145
148
|
first: "first";
|
|
146
149
|
}>>>;
|
|
147
150
|
}, z.core.$loose>;
|
|
148
|
-
|
|
151
|
+
event_bus?: EventBus;
|
|
149
152
|
_event_original?: BaseEvent;
|
|
150
153
|
_event_dispatch_context?: unknown | null;
|
|
151
154
|
_event_completed_signal: Deferred<this> | null;
|
|
152
155
|
_lock_for_event_handler: AsyncLock | null;
|
|
153
|
-
get event_bus(): EventBus;
|
|
154
156
|
constructor(data?: BaseEventInit<Record<string, unknown>>);
|
|
155
157
|
toString(): string;
|
|
156
158
|
static extend<TShape extends z.ZodRawShape>(event_type: string, shape?: TShape): EventFactory<TShape, ResultSchemaFromShape<TShape>>;
|
|
@@ -180,6 +182,7 @@ export declare class BaseEvent {
|
|
|
180
182
|
get event_parent(): BaseEvent | undefined;
|
|
181
183
|
get event_children(): BaseEvent[];
|
|
182
184
|
get event_descendants(): BaseEvent[];
|
|
185
|
+
emit<T extends BaseEvent>(event: T): T;
|
|
183
186
|
_cancelPendingChildProcessing(reason: unknown): void;
|
|
184
187
|
_markRemainingFirstModeResultCancelled(winner: EventResult): void;
|
|
185
188
|
_markCancelled(cause: Error): void;
|
|
@@ -189,6 +192,7 @@ export declare class BaseEvent {
|
|
|
189
192
|
eventResultsList(include: EventResultsListInclude<this>, options?: EventResultsListOptions<this>): Promise<Array<EventResultType<this> | undefined>>;
|
|
190
193
|
eventResultsList(options?: EventResultsListOptions<this>): Promise<Array<EventResultType<this> | undefined>>;
|
|
191
194
|
eventCompleted(): Promise<this>;
|
|
195
|
+
_markBlocksParentCompletionIfAwaitedFromEmittingHandler(): void;
|
|
192
196
|
_markPending(): this;
|
|
193
197
|
eventReset(): this;
|
|
194
198
|
_markStarted(started_at?: string | null, notify_hook?: boolean): void;
|
|
@@ -200,7 +204,7 @@ export declare class BaseEvent {
|
|
|
200
204
|
ignore_first_mode_control_errors?: boolean;
|
|
201
205
|
}): unknown | undefined;
|
|
202
206
|
get event_result(): EventResultType<this> | undefined;
|
|
203
|
-
_areAllChildrenComplete(): boolean;
|
|
207
|
+
_areAllChildrenComplete(visited?: Set<string>): boolean;
|
|
204
208
|
private _notifyDoneListeners;
|
|
205
209
|
_gc(): void;
|
|
206
210
|
}
|