@xbg.solutions/bpsk-utils-mutex 1.2.3
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/lib/constants/mutex.constants.d.ts +25 -0
- package/lib/constants/mutex.constants.d.ts.map +1 -0
- package/lib/constants/mutex.constants.js +26 -0
- package/lib/constants/mutex.constants.js.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +6 -0
- package/lib/index.js.map +1 -0
- package/lib/types/mutex.types.d.ts +42 -0
- package/lib/types/mutex.types.d.ts.map +1 -0
- package/lib/types/mutex.types.js +6 -0
- package/lib/types/mutex.types.js.map +1 -0
- package/lib/utils/mutex.d.ts +105 -0
- package/lib/utils/mutex.d.ts.map +1 -0
- package/lib/utils/mutex.js +258 -0
- package/lib/utils/mutex.js.map +1 -0
- package/package.json +21 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/lib/constants/mutex.constants.ts
|
|
3
|
+
* Constants for mutex utility
|
|
4
|
+
*
|
|
5
|
+
* @deprecated This file has been consolidated into central configuration.
|
|
6
|
+
* New code should import from '@/lib/config/app.config.ts' instead.
|
|
7
|
+
*
|
|
8
|
+
* Legacy mutex constants - use central config for new development.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Use APP_CONFIG.security.mutex instead
|
|
12
|
+
* Default maximum wait time for acquiring a lock (ms)
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_MUTEX_TIMEOUT: number;
|
|
15
|
+
/**
|
|
16
|
+
* @deprecated Use APP_CONFIG.security.mutex instead
|
|
17
|
+
* Default maximum lock retention time (ms)
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEFAULT_MUTEX_LOCK_EXPIRY: number;
|
|
20
|
+
/**
|
|
21
|
+
* @deprecated Use APP_CONFIG.security.mutex instead
|
|
22
|
+
* Polling interval when waiting for a lock (ms)
|
|
23
|
+
*/
|
|
24
|
+
export declare const MUTEX_POLLING_INTERVAL: number;
|
|
25
|
+
//# sourceMappingURL=mutex.constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutex.constants.d.ts","sourceRoot":"","sources":["../../src/constants/mutex.constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAA2C,CAAC;AAE9E;;;GAGG;AACH,eAAO,MAAM,yBAAyB,QAA8C,CAAC;AAErF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAA4C,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/lib/constants/mutex.constants.ts
|
|
3
|
+
* Constants for mutex utility
|
|
4
|
+
*
|
|
5
|
+
* @deprecated This file has been consolidated into central configuration.
|
|
6
|
+
* New code should import from '@/lib/config/app.config.ts' instead.
|
|
7
|
+
*
|
|
8
|
+
* Legacy mutex constants - use central config for new development.
|
|
9
|
+
*/
|
|
10
|
+
import { APP_CONFIG } from '@xbg.solutions/bpsk-core';
|
|
11
|
+
/**
|
|
12
|
+
* @deprecated Use APP_CONFIG.security.mutex instead
|
|
13
|
+
* Default maximum wait time for acquiring a lock (ms)
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_MUTEX_TIMEOUT = APP_CONFIG.security.mutex.defaultTimeout;
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use APP_CONFIG.security.mutex instead
|
|
18
|
+
* Default maximum lock retention time (ms)
|
|
19
|
+
*/
|
|
20
|
+
export const DEFAULT_MUTEX_LOCK_EXPIRY = APP_CONFIG.security.mutex.defaultLockExpiry;
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Use APP_CONFIG.security.mutex instead
|
|
23
|
+
* Polling interval when waiting for a lock (ms)
|
|
24
|
+
*/
|
|
25
|
+
export const MUTEX_POLLING_INTERVAL = APP_CONFIG.security.mutex.pollingInterval;
|
|
26
|
+
//# sourceMappingURL=mutex.constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutex.constants.js","sourceRoot":"","sources":["../../src/constants/mutex.constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC;AAE9E;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC;AAErF;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,6BAA6B,CAAC;AAG5C,mBAAmB,qBAAqB,CAAC;AAGzC,cAAc,eAAe,CAAC"}
|
package/lib/index.js
ADDED
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B,YAAY;AACZ,cAAc,6BAA6B,CAAC;AAK5C,QAAQ;AACR,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/lib/types/mutex.types.ts
|
|
3
|
+
* Type definitions for mutex utility
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Options for mutex operations
|
|
7
|
+
*/
|
|
8
|
+
export type MutexOptions = {
|
|
9
|
+
/** Maximum time to wait for lock acquisition (ms) */
|
|
10
|
+
timeout?: number;
|
|
11
|
+
/** Maximum time a lock can be held (ms) */
|
|
12
|
+
lockExpiry?: number;
|
|
13
|
+
/** Whether to track detailed acquisition info (stack traces etc.) */
|
|
14
|
+
debug?: boolean;
|
|
15
|
+
/** Internal testing flag - not for general use */
|
|
16
|
+
_testMode?: boolean;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Information about a held mutex lock
|
|
20
|
+
*/
|
|
21
|
+
export type MutexLockInfo = {
|
|
22
|
+
/** Name of the lock */
|
|
23
|
+
name: string;
|
|
24
|
+
/** Timestamp when the lock was acquired */
|
|
25
|
+
acquiredAt: number;
|
|
26
|
+
/** Timestamp when the lock will expire */
|
|
27
|
+
expiresAt: number;
|
|
28
|
+
/** Stack trace from when the lock was acquired (only in debug mode) */
|
|
29
|
+
stack?: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Result type for safe mutex operations
|
|
33
|
+
*/
|
|
34
|
+
export type MutexResult<T> = {
|
|
35
|
+
/** Whether the operation succeeded */
|
|
36
|
+
success: boolean;
|
|
37
|
+
/** Result of the operation if successful */
|
|
38
|
+
result?: T;
|
|
39
|
+
/** Error if operation failed */
|
|
40
|
+
error?: Error;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=mutex.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutex.types.d.ts","sourceRoot":"","sources":["../../src/types/mutex.types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IAEb,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IAEnB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAElB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IAEjB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,CAAC,CAAC;IAEX,gCAAgC;IAChC,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutex.types.js","sourceRoot":"","sources":["../../src/types/mutex.types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/lib/utils/mutex.ts
|
|
3
|
+
* Mutex utility for managing critical sections
|
|
4
|
+
*/
|
|
5
|
+
import { AppError } from '@xbg.solutions/bpsk-core';
|
|
6
|
+
import type { MutexOptions, MutexLockInfo, MutexResult } from '../types/mutex.types';
|
|
7
|
+
/**
|
|
8
|
+
* Error class for mutex-specific errors
|
|
9
|
+
*/
|
|
10
|
+
export declare class MutexError extends AppError {
|
|
11
|
+
/** Lock name that caused the error */
|
|
12
|
+
lockName?: string;
|
|
13
|
+
/** Timeout duration if applicable */
|
|
14
|
+
timeout?: number;
|
|
15
|
+
constructor(message: string, options?: {
|
|
16
|
+
lockName?: string;
|
|
17
|
+
timeout?: number;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Mutex implementation for managing critical sections
|
|
23
|
+
*/
|
|
24
|
+
declare class Mutex {
|
|
25
|
+
/** Map of active locks by name */
|
|
26
|
+
private locks;
|
|
27
|
+
/**
|
|
28
|
+
* Acquire a named mutex lock
|
|
29
|
+
*
|
|
30
|
+
* @param name Unique name of the lock
|
|
31
|
+
* @param options Lock acquisition options
|
|
32
|
+
* @returns Promise that resolves to a function that releases the lock
|
|
33
|
+
* @throws MutexError if lock cannot be acquired within timeout
|
|
34
|
+
*/
|
|
35
|
+
acquire(name: string, options?: MutexOptions): Promise<() => boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Release a mutex lock
|
|
38
|
+
*
|
|
39
|
+
* @param name Name of the lock to release
|
|
40
|
+
* @returns true if lock was released, false if it wasn't held
|
|
41
|
+
*/
|
|
42
|
+
release(name: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Force release a lock regardless of who holds it
|
|
45
|
+
*
|
|
46
|
+
* @param name Name of the lock to force release
|
|
47
|
+
* @returns true if a lock was force-released, false if it wasn't held
|
|
48
|
+
*/
|
|
49
|
+
forceRelease(name: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Check if a lock is currently held
|
|
52
|
+
*
|
|
53
|
+
* @param name Name of the lock to check
|
|
54
|
+
* @returns true if the lock is held, false otherwise
|
|
55
|
+
*/
|
|
56
|
+
isLocked(name: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Get information about a held lock
|
|
59
|
+
*
|
|
60
|
+
* @param name Name of the lock
|
|
61
|
+
* @returns Lock information or null if not held
|
|
62
|
+
*/
|
|
63
|
+
getLockInfo(name: string): MutexLockInfo | null;
|
|
64
|
+
/**
|
|
65
|
+
* Get all currently held locks
|
|
66
|
+
*
|
|
67
|
+
* @returns Array of lock information
|
|
68
|
+
*/
|
|
69
|
+
getAllLocks(): MutexLockInfo[];
|
|
70
|
+
/**
|
|
71
|
+
* Safe version of acquire that never throws
|
|
72
|
+
*
|
|
73
|
+
* @param name Lock name
|
|
74
|
+
* @param options Lock options
|
|
75
|
+
* @returns Promise that resolves to a release function or null if acquisition failed
|
|
76
|
+
*/
|
|
77
|
+
safeAcquire(name: string, options?: MutexOptions): Promise<(() => boolean) | null>;
|
|
78
|
+
/**
|
|
79
|
+
* Execute a function with a mutex lock
|
|
80
|
+
*
|
|
81
|
+
* @param name Lock name
|
|
82
|
+
* @param fn Function to execute with the lock
|
|
83
|
+
* @param options Lock options
|
|
84
|
+
* @returns Promise resolving to the function result
|
|
85
|
+
* @throws Original error from the function or MutexError if lock cannot be acquired
|
|
86
|
+
*/
|
|
87
|
+
withLock<T>(name: string, fn: () => Promise<T>, options?: MutexOptions): Promise<T>;
|
|
88
|
+
/**
|
|
89
|
+
* Safely execute a function with a mutex lock, never throws mutex errors
|
|
90
|
+
*
|
|
91
|
+
* @param name Lock name
|
|
92
|
+
* @param fn Function to execute with the lock
|
|
93
|
+
* @param options Lock options
|
|
94
|
+
* @returns Promise resolving to an object with success flag and result or error
|
|
95
|
+
*/
|
|
96
|
+
safeWithLock<T>(name: string, fn: () => Promise<T>, options?: MutexOptions): Promise<MutexResult<T>>;
|
|
97
|
+
/**
|
|
98
|
+
* Clean up any expired locks
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
101
|
+
private cleanupExpiredLocks;
|
|
102
|
+
}
|
|
103
|
+
export declare const mutexService: Mutex;
|
|
104
|
+
export {};
|
|
105
|
+
//# sourceMappingURL=mutex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutex.d.ts","sourceRoot":"","sources":["../../src/utils/mutex.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAiB,QAAQ,EAAkB,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAUrF;;GAEG;AACH,qBAAa,UAAW,SAAQ,QAAQ;IACtC,sCAAsC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IACzB,qCAAqC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;gBAEZ,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACf;CAWP;AAED;;GAEG;AACH,cAAM,KAAK;IACT,kCAAkC;IAClC,OAAO,CAAC,KAAK,CAAyC;IAEtD;;;;;;;OAOG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC;IAmE/E;;;;;OAKG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAgB9B;;;;;OAKG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAgBnC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAK/B;;;;;OAKG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAK/C;;;;OAIG;IACH,WAAW,IAAI,aAAa,EAAE;IAK9B;;;;;;OAMG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,IAAI,CAAC;IAc5F;;;;;;;;OAQG;IACG,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,CAAC,CAAC;IAU7F;;;;;;;OAOG;IACG,YAAY,CAAC,CAAC,EAClB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IA+B1B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;CAqB5B;AAGD,eAAO,MAAM,YAAY,OAAc,CAAC"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/lib/utils/mutex.ts
|
|
3
|
+
* Mutex utility for managing critical sections
|
|
4
|
+
*/
|
|
5
|
+
import { loggerService, AppError, normalizeError } from '@xbg.solutions/bpsk-core';
|
|
6
|
+
import { DEFAULT_MUTEX_TIMEOUT, DEFAULT_MUTEX_LOCK_EXPIRY, MUTEX_POLLING_INTERVAL } from '../constants/mutex.constants';
|
|
7
|
+
// Create a context-aware logger
|
|
8
|
+
const mutexLogger = loggerService.withContext('MutexUtility');
|
|
9
|
+
/**
|
|
10
|
+
* Error class for mutex-specific errors
|
|
11
|
+
*/
|
|
12
|
+
export class MutexError extends AppError {
|
|
13
|
+
constructor(message, options = {}) {
|
|
14
|
+
super(message, {
|
|
15
|
+
category: 'mutex',
|
|
16
|
+
statusCode: 423, // Locked (WebDAV status code)
|
|
17
|
+
userMessage: options.userMessage || 'Resource is currently locked. Please try again later.',
|
|
18
|
+
...options
|
|
19
|
+
});
|
|
20
|
+
this.lockName = options.lockName;
|
|
21
|
+
this.timeout = options.timeout;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Mutex implementation for managing critical sections
|
|
26
|
+
*/
|
|
27
|
+
class Mutex {
|
|
28
|
+
constructor() {
|
|
29
|
+
/** Map of active locks by name */
|
|
30
|
+
this.locks = new Map();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Acquire a named mutex lock
|
|
34
|
+
*
|
|
35
|
+
* @param name Unique name of the lock
|
|
36
|
+
* @param options Lock acquisition options
|
|
37
|
+
* @returns Promise that resolves to a function that releases the lock
|
|
38
|
+
* @throws MutexError if lock cannot be acquired within timeout
|
|
39
|
+
*/
|
|
40
|
+
async acquire(name, options = {}) {
|
|
41
|
+
const timeout = options.timeout || DEFAULT_MUTEX_TIMEOUT;
|
|
42
|
+
const lockExpiry = options.lockExpiry || DEFAULT_MUTEX_LOCK_EXPIRY;
|
|
43
|
+
const debug = options.debug ?? import.meta.env.DEV;
|
|
44
|
+
const testMode = options._testMode || false; // Private option for testing
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
// Clean up any expired locks first
|
|
47
|
+
this.cleanupExpiredLocks();
|
|
48
|
+
// Try to acquire the lock
|
|
49
|
+
while (this.locks.has(name)) {
|
|
50
|
+
// Check if we've exceeded timeout
|
|
51
|
+
if (Date.now() - startTime > timeout) {
|
|
52
|
+
const existingLock = this.locks.get(name);
|
|
53
|
+
mutexLogger.warn(`Mutex acquisition timeout for "${name}"`, {
|
|
54
|
+
timeout,
|
|
55
|
+
lockInfo: existingLock,
|
|
56
|
+
acquiredAgo: existingLock ? Date.now() - existingLock.acquiredAt : null,
|
|
57
|
+
timeRemaining: existingLock ? existingLock.expiresAt - Date.now() : null
|
|
58
|
+
});
|
|
59
|
+
throw new MutexError(`Failed to acquire mutex "${name}" after ${timeout}ms`, {
|
|
60
|
+
lockName: name,
|
|
61
|
+
timeout,
|
|
62
|
+
context: {
|
|
63
|
+
acquiredAt: existingLock?.acquiredAt,
|
|
64
|
+
expiresAt: existingLock?.expiresAt,
|
|
65
|
+
heldFor: existingLock ? Date.now() - existingLock.acquiredAt : null
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// In test mode, we fail fast instead of waiting
|
|
70
|
+
if (testMode) {
|
|
71
|
+
throw new MutexError(`Failed to acquire mutex "${name}" (test mode)`, {
|
|
72
|
+
lockName: name,
|
|
73
|
+
timeout
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// Wait a bit before trying again
|
|
77
|
+
await new Promise(resolve => setTimeout(resolve, MUTEX_POLLING_INTERVAL));
|
|
78
|
+
}
|
|
79
|
+
// Acquire the lock
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
const lockInfo = {
|
|
82
|
+
name,
|
|
83
|
+
acquiredAt: now,
|
|
84
|
+
expiresAt: now + lockExpiry,
|
|
85
|
+
stack: debug ? new Error().stack : undefined
|
|
86
|
+
};
|
|
87
|
+
this.locks.set(name, lockInfo);
|
|
88
|
+
mutexLogger.info(`Mutex "${name}" acquired`, {
|
|
89
|
+
lockInfo,
|
|
90
|
+
expiresIn: lockExpiry
|
|
91
|
+
});
|
|
92
|
+
// Return release function
|
|
93
|
+
return () => this.release(name);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Release a mutex lock
|
|
97
|
+
*
|
|
98
|
+
* @param name Name of the lock to release
|
|
99
|
+
* @returns true if lock was released, false if it wasn't held
|
|
100
|
+
*/
|
|
101
|
+
release(name) {
|
|
102
|
+
if (!this.locks.has(name)) {
|
|
103
|
+
mutexLogger.warn(`Attempted to release non-existent mutex "${name}"`);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const lockInfo = this.locks.get(name);
|
|
107
|
+
this.locks.delete(name);
|
|
108
|
+
mutexLogger.info(`Mutex "${name}" released`, {
|
|
109
|
+
heldFor: Date.now() - (lockInfo?.acquiredAt || 0)
|
|
110
|
+
});
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Force release a lock regardless of who holds it
|
|
115
|
+
*
|
|
116
|
+
* @param name Name of the lock to force release
|
|
117
|
+
* @returns true if a lock was force-released, false if it wasn't held
|
|
118
|
+
*/
|
|
119
|
+
forceRelease(name) {
|
|
120
|
+
if (!this.locks.has(name)) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const lockInfo = this.locks.get(name);
|
|
124
|
+
this.locks.delete(name);
|
|
125
|
+
mutexLogger.warn(`Mutex "${name}" force released`, {
|
|
126
|
+
lockInfo,
|
|
127
|
+
heldFor: Date.now() - (lockInfo?.acquiredAt || 0)
|
|
128
|
+
});
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check if a lock is currently held
|
|
133
|
+
*
|
|
134
|
+
* @param name Name of the lock to check
|
|
135
|
+
* @returns true if the lock is held, false otherwise
|
|
136
|
+
*/
|
|
137
|
+
isLocked(name) {
|
|
138
|
+
this.cleanupExpiredLocks();
|
|
139
|
+
return this.locks.has(name);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get information about a held lock
|
|
143
|
+
*
|
|
144
|
+
* @param name Name of the lock
|
|
145
|
+
* @returns Lock information or null if not held
|
|
146
|
+
*/
|
|
147
|
+
getLockInfo(name) {
|
|
148
|
+
this.cleanupExpiredLocks();
|
|
149
|
+
return this.locks.get(name) || null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get all currently held locks
|
|
153
|
+
*
|
|
154
|
+
* @returns Array of lock information
|
|
155
|
+
*/
|
|
156
|
+
getAllLocks() {
|
|
157
|
+
this.cleanupExpiredLocks();
|
|
158
|
+
return Array.from(this.locks.values());
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Safe version of acquire that never throws
|
|
162
|
+
*
|
|
163
|
+
* @param name Lock name
|
|
164
|
+
* @param options Lock options
|
|
165
|
+
* @returns Promise that resolves to a release function or null if acquisition failed
|
|
166
|
+
*/
|
|
167
|
+
async safeAcquire(name, options = {}) {
|
|
168
|
+
try {
|
|
169
|
+
return await this.acquire(name, options);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
const normalizedError = normalizeError(error);
|
|
173
|
+
mutexLogger.error(`Failed to safely acquire mutex "${name}"`, normalizedError, {
|
|
174
|
+
options
|
|
175
|
+
});
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Execute a function with a mutex lock
|
|
181
|
+
*
|
|
182
|
+
* @param name Lock name
|
|
183
|
+
* @param fn Function to execute with the lock
|
|
184
|
+
* @param options Lock options
|
|
185
|
+
* @returns Promise resolving to the function result
|
|
186
|
+
* @throws Original error from the function or MutexError if lock cannot be acquired
|
|
187
|
+
*/
|
|
188
|
+
async withLock(name, fn, options = {}) {
|
|
189
|
+
const release = await this.acquire(name, options);
|
|
190
|
+
try {
|
|
191
|
+
return await fn();
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
release();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Safely execute a function with a mutex lock, never throws mutex errors
|
|
199
|
+
*
|
|
200
|
+
* @param name Lock name
|
|
201
|
+
* @param fn Function to execute with the lock
|
|
202
|
+
* @param options Lock options
|
|
203
|
+
* @returns Promise resolving to an object with success flag and result or error
|
|
204
|
+
*/
|
|
205
|
+
async safeWithLock(name, fn, options = {}) {
|
|
206
|
+
try {
|
|
207
|
+
const release = await this.acquire(name, options);
|
|
208
|
+
try {
|
|
209
|
+
const result = await fn();
|
|
210
|
+
return { success: true, result };
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
const normalizedError = normalizeError(error);
|
|
214
|
+
mutexLogger.error(`Operation failed while holding mutex "${name}"`, normalizedError);
|
|
215
|
+
return {
|
|
216
|
+
success: false,
|
|
217
|
+
error: normalizedError
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
release();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
const normalizedError = normalizeError(error);
|
|
226
|
+
mutexLogger.error(`Failed to acquire mutex "${name}" for safe operation`, normalizedError);
|
|
227
|
+
return {
|
|
228
|
+
success: false,
|
|
229
|
+
error: normalizedError
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Clean up any expired locks
|
|
235
|
+
* @private
|
|
236
|
+
*/
|
|
237
|
+
cleanupExpiredLocks() {
|
|
238
|
+
const now = Date.now();
|
|
239
|
+
let expiredCount = 0;
|
|
240
|
+
for (const [name, info] of this.locks.entries()) {
|
|
241
|
+
if (info.expiresAt <= now) {
|
|
242
|
+
this.locks.delete(name);
|
|
243
|
+
expiredCount++;
|
|
244
|
+
mutexLogger.warn(`Mutex "${name}" expired and auto-released`, {
|
|
245
|
+
lockInfo: info,
|
|
246
|
+
heldFor: now - info.acquiredAt,
|
|
247
|
+
expiredAgo: now - info.expiresAt
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (expiredCount > 0) {
|
|
252
|
+
mutexLogger.info(`Cleaned up ${expiredCount} expired lock(s)`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Export a singleton instance
|
|
257
|
+
export const mutexService = new Mutex();
|
|
258
|
+
//# sourceMappingURL=mutex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutex.js","sourceRoot":"","sources":["../../src/utils/mutex.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xbg.solutions/bpsk-utils-mutex",
|
|
3
|
+
"version": "1.2.3",
|
|
4
|
+
"description": "XBG Mutex - Mutual exclusion for concurrent operations",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": ["lib"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"build:watch": "tsc --watch",
|
|
12
|
+
"clean": "rm -rf lib",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@xbg.solutions/bpsk-core": "^1.0.0"
|
|
20
|
+
}
|
|
21
|
+
}
|