@prairielearn/named-locks 1.3.3 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +2 -2
- package/dist/index.d.ts +15 -6
- package/dist/index.js +22 -12
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +44 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @prairielearn/named-locks
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cdb0f2109: Add optional `onNotAcquired` function to `doWithLock` and `tryWithLock`
|
|
8
|
+
|
|
9
|
+
## 1.4.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 297bbce5a: Allow `tryLockAsync` and `tryWithLock` to accept a timeout
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 2b003b4d9: Upgrade all dependencies
|
|
18
|
+
- Updated dependencies [2b003b4d9]
|
|
19
|
+
- @prairielearn/postgres@1.7.2
|
|
20
|
+
|
|
3
21
|
## 1.3.3
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,9 @@ interface LockOptions {
|
|
|
27
27
|
*/
|
|
28
28
|
autoRenew?: boolean;
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
interface WithLockOptions<T> extends LockOptions {
|
|
31
|
+
onNotAcquired?: () => Promise<T> | T;
|
|
32
|
+
}
|
|
31
33
|
export declare const pool: PostgresPool;
|
|
32
34
|
/**
|
|
33
35
|
* Initializes a new {@link PostgresPool} that will be used to acquire named locks.
|
|
@@ -45,7 +47,7 @@ export declare function close(): Promise<void>;
|
|
|
45
47
|
*
|
|
46
48
|
* @param name The name of the lock to acquire.
|
|
47
49
|
*/
|
|
48
|
-
export declare function tryLockAsync(name: string, options?:
|
|
50
|
+
export declare function tryLockAsync(name: string, options?: LockOptions): Promise<Lock | null>;
|
|
49
51
|
export declare const tryLock: (arg1: string, callback: (err: NodeJS.ErrnoException, result: Lock | null) => void) => void;
|
|
50
52
|
/**
|
|
51
53
|
* Wait until a lock can be successfully acquired.
|
|
@@ -65,12 +67,19 @@ export declare const releaseLock: (arg1: Lock, callback: (err: NodeJS.ErrnoExcep
|
|
|
65
67
|
/**
|
|
66
68
|
* Acquires the given lock, executes the provided function with the lock held,
|
|
67
69
|
* and releases the lock once the function has executed.
|
|
70
|
+
*
|
|
71
|
+
* If the lock cannot be acquired, the function is not executed. If an `onNotAcquired`
|
|
72
|
+
* function was provided, this function is called and its return value is returned.
|
|
73
|
+
* Otherwise, an error is thrown to indicate that the lock could not be acquired.
|
|
68
74
|
*/
|
|
69
|
-
export declare function doWithLock<T>(name: string, options:
|
|
75
|
+
export declare function doWithLock<T, U = never>(name: string, options: WithLockOptions<U>, func: () => Promise<T>): Promise<T | U>;
|
|
70
76
|
/**
|
|
71
77
|
* Tries to acquire the given lock, executes the provided function with the lock held,
|
|
72
|
-
* and releases the lock once the function has executed.
|
|
73
|
-
*
|
|
78
|
+
* and releases the lock once the function has executed.
|
|
79
|
+
*
|
|
80
|
+
* If the lock cannot be acquired, the function is not executed. If an `onNotAcquired`
|
|
81
|
+
* function was provided, this function is called and its return value is returned.
|
|
82
|
+
* Otherwise, `null` is returned.
|
|
74
83
|
*/
|
|
75
|
-
export declare function tryWithLock<T>(name: string, options:
|
|
84
|
+
export declare function tryWithLock<T, U = null>(name: string, options: WithLockOptions<U>, func: () => Promise<T>): Promise<T | U>;
|
|
76
85
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -109,9 +109,21 @@ exports.releaseLock = util_1.default.callbackify(releaseLockAsync);
|
|
|
109
109
|
/**
|
|
110
110
|
* Acquires the given lock, executes the provided function with the lock held,
|
|
111
111
|
* and releases the lock once the function has executed.
|
|
112
|
+
*
|
|
113
|
+
* If the lock cannot be acquired, the function is not executed. If an `onNotAcquired`
|
|
114
|
+
* function was provided, this function is called and its return value is returned.
|
|
115
|
+
* Otherwise, an error is thrown to indicate that the lock could not be acquired.
|
|
112
116
|
*/
|
|
113
117
|
async function doWithLock(name, options, func) {
|
|
114
|
-
const lock = await
|
|
118
|
+
const lock = await tryLockAsync(name, options);
|
|
119
|
+
if (!lock) {
|
|
120
|
+
if (options.onNotAcquired) {
|
|
121
|
+
return await options.onNotAcquired();
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
throw new Error(`failed to acquire lock: ${name}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
115
127
|
try {
|
|
116
128
|
return await func();
|
|
117
129
|
}
|
|
@@ -122,19 +134,17 @@ async function doWithLock(name, options, func) {
|
|
|
122
134
|
exports.doWithLock = doWithLock;
|
|
123
135
|
/**
|
|
124
136
|
* Tries to acquire the given lock, executes the provided function with the lock held,
|
|
125
|
-
* and releases the lock once the function has executed.
|
|
126
|
-
*
|
|
137
|
+
* and releases the lock once the function has executed.
|
|
138
|
+
*
|
|
139
|
+
* If the lock cannot be acquired, the function is not executed. If an `onNotAcquired`
|
|
140
|
+
* function was provided, this function is called and its return value is returned.
|
|
141
|
+
* Otherwise, `null` is returned.
|
|
127
142
|
*/
|
|
128
143
|
async function tryWithLock(name, options, func) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return await func();
|
|
134
|
-
}
|
|
135
|
-
finally {
|
|
136
|
-
await releaseLockAsync(lock);
|
|
137
|
-
}
|
|
144
|
+
return await doWithLock(name, {
|
|
145
|
+
onNotAcquired: () => null,
|
|
146
|
+
...options,
|
|
147
|
+
}, func);
|
|
138
148
|
}
|
|
139
149
|
exports.tryWithLock = tryWithLock;
|
|
140
150
|
/**
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAwB;AACxB,qDAAkE;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAwB;AACxB,qDAAkE;AAoClE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEU,QAAA,IAAI,GAAG,IAAI,uBAAY,EAAE,CAAC;AACvC,IAAI,eAAe,GAAG,KAAM,CAAC;AAE7B;;GAEG;AACI,KAAK,UAAU,IAAI,CACxB,QAAoB,EACpB,gBAA4D,EAC5D,mBAAqC,EAAE;IAEvC,eAAe,GAAG,gBAAgB,CAAC,eAAe,IAAI,eAAe,CAAC;IACtE,MAAM,YAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IACjD,MAAM,YAAI,CAAC,UAAU,CACnB,+FAA+F,EAC/F,EAAE,CACH,CAAC;AACJ,CAAC;AAXD,oBAWC;AAED;;GAEG;AACI,KAAK,UAAU,KAAK;IACzB,MAAM,YAAI,CAAC,UAAU,EAAE,CAAC;AAC1B,CAAC;AAFD,sBAEC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,UAAuB,EAAE;IACxE,OAAO,OAAO,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;AACnD,CAAC;AAFD,oCAEC;AAEY,QAAA,OAAO,GAAG,cAAI,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;AAEtD;;;;;GAKG;AACI,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,OAAoB;IACpE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1C,IAAI,IAAI,IAAI,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAC;IACrE,OAAO,IAAI,CAAC;AACd,CAAC;AAJD,sCAIC;AAEY,QAAA,QAAQ,GAAG,cAAI,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;AAExD;;;;GAIG;AACI,KAAK,UAAU,gBAAgB,CAAC,IAAU;IAC/C,IAAI,IAAI,IAAI,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;IAClD,aAAa,CAAC,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC,CAAC;IAC5C,MAAM,YAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AACpD,CAAC;AAJD,4CAIC;AAEY,QAAA,WAAW,GAAG,cAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC;AAE9D;;;;;;;GAOG;AACI,KAAK,UAAU,UAAU,CAC9B,IAAY,EACZ,OAA2B,EAC3B,IAAsB;IAEtB,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAE/C,IAAI,CAAC,IAAI,EAAE;QACT,IAAI,OAAO,CAAC,aAAa,EAAE;YACzB,OAAO,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC;SACtC;aAAM;YACL,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAC;SACpD;KACF;IAED,IAAI;QACF,OAAO,MAAM,IAAI,EAAE,CAAC;KACrB;YAAS;QACR,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;KAC9B;AACH,CAAC;AApBD,gCAoBC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,WAAW,CAC/B,IAAY,EACZ,OAA2B,EAC3B,IAAsB;IAEtB,OAAO,MAAM,UAAU,CACrB,IAAI,EACJ;QACE,aAAa,EAAE,GAAG,EAAE,CAAC,IAAS;QAC9B,GAAG,OAAO;KACX,EACD,IAAI,CACL,CAAC;AACJ,CAAC;AAbD,kCAaC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,OAAO,CAAC,IAAY,EAAE,OAAoB;IACvD,MAAM,YAAI,CAAC,UAAU,CACnB,8EAA8E,EAC9E,EAAE,IAAI,EAAE,CACT,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,YAAI,CAAC,qBAAqB,EAAE,CAAC;IAElD,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAI;QACF,IAAI,OAAO,CAAC,OAAO,EAAE;YACnB,+DAA+D;YAC/D,6DAA6D;YAC7D,oDAAoD;YACpD,MAAM,YAAI,CAAC,oBAAoB,CAC7B,MAAM,EACN,4BAA4B,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,EAAE,EAC9E,EAAE,CACH,CAAC;SACH;QAED,wEAAwE;QACxE,gEAAgE;QAChE,uEAAuE;QACvE,uEAAuE;QACvE,OAAO;QACP,MAAM,eAAe,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO;YAC9B,CAAC,CAAC,0CAA0C,eAAe,cAAc;YACzE,CAAC,CAAC,0CAA0C,eAAe,0BAA0B,CAAC;QACxF,MAAM,MAAM,GAAG,MAAM,YAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3E,YAAY,GAAG,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC;KACtC;IAAC,OAAO,GAAG,EAAE;QACZ,mEAAmE;QACnE,SAAS;QACT,MAAM,YAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,GAAY,CAAC,CAAC;QACrD,MAAM,GAAG,CAAC;KACX;IAED,IAAI,CAAC,YAAY,EAAE;QACjB,6DAA6D;QAC7D,qDAAqD;QACrD,MAAM,YAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;KACb;IAED,IAAI,UAAU,GAAG,IAAI,CAAC;IACtB,IAAI,OAAO,CAAC,SAAS,EAAE;QACrB,mDAAmD;QACnD,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC5C,CAAC,EAAE,eAAe,CAAC,CAAC;KACrB;IAED,uEAAuE;IACvE,uEAAuE;IACvE,0BAA0B;IAC1B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAChC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prairielearn/named-locks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@prairielearn/tsconfig": "^0.0.0",
|
|
16
|
-
"@types/node": "^18.16.
|
|
17
|
-
"typescript": "^5.1.
|
|
16
|
+
"@types/node": "^18.16.19",
|
|
17
|
+
"typescript": "^5.1.6"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@prairielearn/postgres": "^1.7.
|
|
20
|
+
"@prairielearn/postgres": "^1.7.2"
|
|
21
21
|
}
|
|
22
22
|
}
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ interface Lock {
|
|
|
19
19
|
interface LockOptions {
|
|
20
20
|
/** How many milliseconds to wait (anything other than a positive number means forever) */
|
|
21
21
|
timeout?: number;
|
|
22
|
+
|
|
22
23
|
/**
|
|
23
24
|
* Whether or not this lock should automatically renew itself periodically.
|
|
24
25
|
* By default, locks will not renew themselves.
|
|
@@ -30,7 +31,9 @@ interface LockOptions {
|
|
|
30
31
|
autoRenew?: boolean;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
interface WithLockOptions<T> extends LockOptions {
|
|
35
|
+
onNotAcquired?: () => Promise<T> | T;
|
|
36
|
+
}
|
|
34
37
|
|
|
35
38
|
/*
|
|
36
39
|
* The functions here all identify locks by "name", which is a plain
|
|
@@ -84,13 +87,13 @@ let renewIntervalMs = 60_000;
|
|
|
84
87
|
export async function init(
|
|
85
88
|
pgConfig: PoolConfig,
|
|
86
89
|
idleErrorHandler: (error: Error, client: PoolClient) => void,
|
|
87
|
-
namedLocksConfig: NamedLocksConfig = {}
|
|
90
|
+
namedLocksConfig: NamedLocksConfig = {},
|
|
88
91
|
) {
|
|
89
92
|
renewIntervalMs = namedLocksConfig.renewIntervalMs ?? renewIntervalMs;
|
|
90
93
|
await pool.initAsync(pgConfig, idleErrorHandler);
|
|
91
94
|
await pool.queryAsync(
|
|
92
95
|
'CREATE TABLE IF NOT EXISTS named_locks (id bigserial PRIMARY KEY, name text NOT NULL UNIQUE);',
|
|
93
|
-
{}
|
|
96
|
+
{},
|
|
94
97
|
);
|
|
95
98
|
}
|
|
96
99
|
|
|
@@ -109,10 +112,7 @@ export async function close() {
|
|
|
109
112
|
*
|
|
110
113
|
* @param name The name of the lock to acquire.
|
|
111
114
|
*/
|
|
112
|
-
export async function tryLockAsync(
|
|
113
|
-
name: string,
|
|
114
|
-
options: TryLockOptions = {}
|
|
115
|
-
): Promise<Lock | null> {
|
|
115
|
+
export async function tryLockAsync(name: string, options: LockOptions = {}): Promise<Lock | null> {
|
|
116
116
|
return getLock(name, { timeout: 0, ...options });
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -148,13 +148,26 @@ export const releaseLock = util.callbackify(releaseLockAsync);
|
|
|
148
148
|
/**
|
|
149
149
|
* Acquires the given lock, executes the provided function with the lock held,
|
|
150
150
|
* and releases the lock once the function has executed.
|
|
151
|
+
*
|
|
152
|
+
* If the lock cannot be acquired, the function is not executed. If an `onNotAcquired`
|
|
153
|
+
* function was provided, this function is called and its return value is returned.
|
|
154
|
+
* Otherwise, an error is thrown to indicate that the lock could not be acquired.
|
|
151
155
|
*/
|
|
152
|
-
export async function doWithLock<T>(
|
|
156
|
+
export async function doWithLock<T, U = never>(
|
|
153
157
|
name: string,
|
|
154
|
-
options:
|
|
155
|
-
func: () => Promise<T
|
|
156
|
-
): Promise<T> {
|
|
157
|
-
const lock = await
|
|
158
|
+
options: WithLockOptions<U>,
|
|
159
|
+
func: () => Promise<T>,
|
|
160
|
+
): Promise<T | U> {
|
|
161
|
+
const lock = await tryLockAsync(name, options);
|
|
162
|
+
|
|
163
|
+
if (!lock) {
|
|
164
|
+
if (options.onNotAcquired) {
|
|
165
|
+
return await options.onNotAcquired();
|
|
166
|
+
} else {
|
|
167
|
+
throw new Error(`failed to acquire lock: ${name}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
158
171
|
try {
|
|
159
172
|
return await func();
|
|
160
173
|
} finally {
|
|
@@ -164,21 +177,25 @@ export async function doWithLock<T>(
|
|
|
164
177
|
|
|
165
178
|
/**
|
|
166
179
|
* Tries to acquire the given lock, executes the provided function with the lock held,
|
|
167
|
-
* and releases the lock once the function has executed.
|
|
168
|
-
*
|
|
180
|
+
* and releases the lock once the function has executed.
|
|
181
|
+
*
|
|
182
|
+
* If the lock cannot be acquired, the function is not executed. If an `onNotAcquired`
|
|
183
|
+
* function was provided, this function is called and its return value is returned.
|
|
184
|
+
* Otherwise, `null` is returned.
|
|
169
185
|
*/
|
|
170
|
-
export async function tryWithLock<T>(
|
|
186
|
+
export async function tryWithLock<T, U = null>(
|
|
171
187
|
name: string,
|
|
172
|
-
options:
|
|
173
|
-
func: () => Promise<T
|
|
174
|
-
): Promise<T |
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
188
|
+
options: WithLockOptions<U>,
|
|
189
|
+
func: () => Promise<T>,
|
|
190
|
+
): Promise<T | U> {
|
|
191
|
+
return await doWithLock<T, U>(
|
|
192
|
+
name,
|
|
193
|
+
{
|
|
194
|
+
onNotAcquired: () => null as U,
|
|
195
|
+
...options,
|
|
196
|
+
},
|
|
197
|
+
func,
|
|
198
|
+
);
|
|
182
199
|
}
|
|
183
200
|
|
|
184
201
|
/**
|
|
@@ -192,7 +209,7 @@ export async function tryWithLock<T>(
|
|
|
192
209
|
async function getLock(name: string, options: LockOptions) {
|
|
193
210
|
await pool.queryAsync(
|
|
194
211
|
'INSERT INTO named_locks (name) VALUES ($name) ON CONFLICT (name) DO NOTHING;',
|
|
195
|
-
{ name }
|
|
212
|
+
{ name },
|
|
196
213
|
);
|
|
197
214
|
|
|
198
215
|
const client = await pool.beginTransactionAsync();
|
|
@@ -206,7 +223,7 @@ async function getLock(name: string, options: LockOptions) {
|
|
|
206
223
|
await pool.queryWithClientAsync(
|
|
207
224
|
client,
|
|
208
225
|
`SET LOCAL lock_timeout = ${client.escapeLiteral(options.timeout.toString())}`,
|
|
209
|
-
{}
|
|
226
|
+
{},
|
|
210
227
|
);
|
|
211
228
|
}
|
|
212
229
|
|