@nocobase/lock-manager 2.1.40 → 2.1.41
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/lock-manager.d.ts +2 -0
- package/lib/lock-manager.js +51 -32
- package/package.json +3 -3
- package/src/__tests__/lock-manager.test.ts +14 -0
- package/src/lock-manager.ts +58 -35
package/lib/lock-manager.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type Releaser = () => void | Promise<void>;
|
|
|
19
19
|
*/
|
|
20
20
|
export interface ILock {
|
|
21
21
|
acquire(ttl: number): Releaser | Promise<Releaser>;
|
|
22
|
+
/** Extend the currently held lease from now, when supported by the adapter. */
|
|
23
|
+
extend?(ttl: number): void | Promise<void>;
|
|
22
24
|
runExclusive<T>(fn: () => Promise<T>, ttl: number): Promise<T>;
|
|
23
25
|
release(): void | Promise<void>;
|
|
24
26
|
}
|
package/lib/lock-manager.js
CHANGED
|
@@ -112,43 +112,63 @@ const _LocalLockAdapter = class _LocalLockAdapter {
|
|
|
112
112
|
throw new LockAcquireError("lock acquire timed out", { cause: e });
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
let
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
115
|
+
let reservation = preAcquiredRelease;
|
|
116
|
+
let activeLease;
|
|
117
|
+
const releaseLease = /* @__PURE__ */ __name(async (lease) => {
|
|
118
|
+
if (activeLease !== lease) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
activeLease = void 0;
|
|
122
|
+
if (lease.timer) {
|
|
123
|
+
clearTimeout(lease.timer);
|
|
124
|
+
}
|
|
125
|
+
await lease.release();
|
|
126
|
+
}, "releaseLease");
|
|
127
|
+
const resetTimer = /* @__PURE__ */ __name((ttl) => {
|
|
128
|
+
if (!activeLease) {
|
|
129
|
+
throw new LockAcquireError("lock is not active");
|
|
130
|
+
}
|
|
131
|
+
const lease = activeLease;
|
|
132
|
+
if (lease.timer) {
|
|
133
|
+
clearTimeout(lease.timer);
|
|
134
|
+
}
|
|
135
|
+
lease.timer = setTimeout(() => {
|
|
136
|
+
releaseLease(lease).catch(() => void 0);
|
|
137
|
+
}, ttl);
|
|
138
|
+
}, "resetTimer");
|
|
126
139
|
return {
|
|
127
140
|
release: /* @__PURE__ */ __name(async () => {
|
|
128
|
-
|
|
129
|
-
|
|
141
|
+
if (reservation) {
|
|
142
|
+
const releaseReservation = reservation;
|
|
143
|
+
reservation = void 0;
|
|
144
|
+
await releaseReservation();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (activeLease) {
|
|
148
|
+
await releaseLease(activeLease);
|
|
149
|
+
}
|
|
130
150
|
}, "release"),
|
|
131
151
|
acquire: /* @__PURE__ */ __name(async (ttl) => {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return () => {
|
|
139
|
-
release();
|
|
140
|
-
clearTimeout(timer);
|
|
141
|
-
};
|
|
152
|
+
const rawRelease = reservation || await mutex.acquire();
|
|
153
|
+
reservation = void 0;
|
|
154
|
+
const lease = { release: rawRelease };
|
|
155
|
+
activeLease = lease;
|
|
156
|
+
resetTimer(ttl);
|
|
157
|
+
return () => releaseLease(lease);
|
|
142
158
|
}, "acquire"),
|
|
159
|
+
extend: /* @__PURE__ */ __name(async (ttl) => {
|
|
160
|
+
resetTimer(ttl);
|
|
161
|
+
}, "extend"),
|
|
143
162
|
runExclusive: /* @__PURE__ */ __name(async (fn, ttl) => {
|
|
144
|
-
const release = await
|
|
145
|
-
|
|
163
|
+
const release = await (async () => {
|
|
164
|
+
const rawRelease = reservation || await mutex.acquire();
|
|
165
|
+
reservation = void 0;
|
|
166
|
+
const lease = { release: rawRelease };
|
|
167
|
+
activeLease = lease;
|
|
168
|
+
resetTimer(ttl);
|
|
169
|
+
return () => releaseLease(lease);
|
|
170
|
+
})();
|
|
146
171
|
try {
|
|
147
|
-
timer = setTimeout(() => {
|
|
148
|
-
if (mutex.isLocked()) {
|
|
149
|
-
release();
|
|
150
|
-
}
|
|
151
|
-
}, ttl);
|
|
152
172
|
return await fn();
|
|
153
173
|
} catch (e) {
|
|
154
174
|
if (e === import_async_mutex.E_CANCELED) {
|
|
@@ -157,8 +177,7 @@ const _LocalLockAdapter = class _LocalLockAdapter {
|
|
|
157
177
|
throw e;
|
|
158
178
|
}
|
|
159
179
|
} finally {
|
|
160
|
-
|
|
161
|
-
release();
|
|
180
|
+
await release();
|
|
162
181
|
}
|
|
163
182
|
}, "runExclusive")
|
|
164
183
|
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/lock-manager",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.41",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"devDependencies": {
|
|
7
|
-
"@nocobase/utils": "2.1.
|
|
7
|
+
"@nocobase/utils": "2.1.41",
|
|
8
8
|
"async-mutex": "^0.5.0"
|
|
9
9
|
},
|
|
10
|
-
"gitHead": "
|
|
10
|
+
"gitHead": "91c01d0941d758d97d8000d0c8b8a2a48af6c1ae"
|
|
11
11
|
}
|
|
@@ -149,6 +149,7 @@ describe('lock manager', () => {
|
|
|
149
149
|
await release();
|
|
150
150
|
const lock = await lockManager.tryAcquire('test');
|
|
151
151
|
expect(lock.acquire).toBeTypeOf('function');
|
|
152
|
+
expect(lock.extend).toBeTypeOf('function');
|
|
152
153
|
expect(lock.runExclusive).toBeTypeOf('function');
|
|
153
154
|
|
|
154
155
|
const order = [];
|
|
@@ -221,6 +222,19 @@ describe('lock manager', () => {
|
|
|
221
222
|
await release2();
|
|
222
223
|
});
|
|
223
224
|
|
|
225
|
+
it('tryAcquire: extends an active lease', async () => {
|
|
226
|
+
const lock = await lockManager.tryAcquire('extend-test');
|
|
227
|
+
const release = await lock.acquire(500);
|
|
228
|
+
await sleep(100);
|
|
229
|
+
await lock.extend(500);
|
|
230
|
+
await sleep(200);
|
|
231
|
+
await expect(lockManager.tryAcquire('extend-test')).rejects.toThrowError(LockAcquireError);
|
|
232
|
+
await release();
|
|
233
|
+
|
|
234
|
+
const nextLock = await lockManager.tryAcquire('extend-test');
|
|
235
|
+
await nextLock.release();
|
|
236
|
+
});
|
|
237
|
+
|
|
224
238
|
it('tryAcquire: release() abandons the pre-acquired lock without executing work', async () => {
|
|
225
239
|
// Acquire the lock via tryAcquire, then immediately release it.
|
|
226
240
|
const lock = await lockManager.tryAcquire('cancel-test');
|
package/src/lock-manager.ts
CHANGED
|
@@ -24,6 +24,8 @@ export type Releaser = () => void | Promise<void>;
|
|
|
24
24
|
*/
|
|
25
25
|
export interface ILock {
|
|
26
26
|
acquire(ttl: number): Releaser | Promise<Releaser>;
|
|
27
|
+
/** Extend the currently held lease from now, when supported by the adapter. */
|
|
28
|
+
extend?(ttl: number): void | Promise<void>;
|
|
27
29
|
runExclusive<T>(fn: () => Promise<T>, ttl: number): Promise<T>;
|
|
28
30
|
release(): void | Promise<void>;
|
|
29
31
|
}
|
|
@@ -123,49 +125,71 @@ class LocalLockAdapter implements ILockAdapter {
|
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
|
|
126
|
-
let
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
: ((await mutex.acquire()) as Releaser);
|
|
132
|
-
// Idempotency guard: prevents double-release when both the TTL auto-
|
|
133
|
-
// release timer and the caller-facing releaser (or finally block) fire.
|
|
134
|
-
let released = false;
|
|
135
|
-
return () => {
|
|
136
|
-
if (!released) {
|
|
137
|
-
released = true;
|
|
138
|
-
return (rawRelease as () => void | Promise<void>)();
|
|
128
|
+
let reservation: Releaser | undefined = preAcquiredRelease;
|
|
129
|
+
let activeLease:
|
|
130
|
+
| {
|
|
131
|
+
release: Releaser;
|
|
132
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
139
133
|
}
|
|
140
|
-
|
|
134
|
+
| undefined;
|
|
135
|
+
|
|
136
|
+
const releaseLease = async (lease: NonNullable<typeof activeLease>): Promise<void> => {
|
|
137
|
+
if (activeLease !== lease) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
activeLease = undefined;
|
|
141
|
+
if (lease.timer) {
|
|
142
|
+
clearTimeout(lease.timer);
|
|
143
|
+
}
|
|
144
|
+
await lease.release();
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const resetTimer = (ttl: number) => {
|
|
148
|
+
if (!activeLease) {
|
|
149
|
+
throw new LockAcquireError('lock is not active');
|
|
150
|
+
}
|
|
151
|
+
const lease = activeLease;
|
|
152
|
+
if (lease.timer) {
|
|
153
|
+
clearTimeout(lease.timer);
|
|
154
|
+
}
|
|
155
|
+
lease.timer = setTimeout(() => {
|
|
156
|
+
releaseLease(lease).catch(() => undefined);
|
|
157
|
+
}, ttl);
|
|
141
158
|
};
|
|
142
159
|
|
|
143
160
|
return {
|
|
144
161
|
release: async (): Promise<void> => {
|
|
145
|
-
|
|
146
|
-
|
|
162
|
+
if (reservation) {
|
|
163
|
+
const releaseReservation = reservation;
|
|
164
|
+
reservation = undefined;
|
|
165
|
+
await releaseReservation();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (activeLease) {
|
|
169
|
+
await releaseLease(activeLease);
|
|
170
|
+
}
|
|
147
171
|
},
|
|
148
172
|
acquire: async (ttl: number): Promise<Releaser> => {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
};
|
|
173
|
+
const rawRelease = reservation || ((await mutex.acquire()) as Releaser);
|
|
174
|
+
reservation = undefined;
|
|
175
|
+
const lease = { release: rawRelease };
|
|
176
|
+
activeLease = lease;
|
|
177
|
+
resetTimer(ttl);
|
|
178
|
+
return () => releaseLease(lease);
|
|
179
|
+
},
|
|
180
|
+
extend: async (ttl: number): Promise<void> => {
|
|
181
|
+
resetTimer(ttl);
|
|
159
182
|
},
|
|
160
183
|
runExclusive: async <T>(fn: () => Promise<T>, ttl: number): Promise<T> => {
|
|
161
|
-
const release = await
|
|
162
|
-
|
|
184
|
+
const release = await (async () => {
|
|
185
|
+
const rawRelease = reservation || ((await mutex.acquire()) as Releaser);
|
|
186
|
+
reservation = undefined;
|
|
187
|
+
const lease = { release: rawRelease };
|
|
188
|
+
activeLease = lease;
|
|
189
|
+
resetTimer(ttl);
|
|
190
|
+
return () => releaseLease(lease);
|
|
191
|
+
})();
|
|
163
192
|
try {
|
|
164
|
-
timer = setTimeout(() => {
|
|
165
|
-
if (mutex.isLocked()) {
|
|
166
|
-
release();
|
|
167
|
-
}
|
|
168
|
-
}, ttl);
|
|
169
193
|
return await fn();
|
|
170
194
|
} catch (e) {
|
|
171
195
|
if (e === E_CANCELED) {
|
|
@@ -174,8 +198,7 @@ class LocalLockAdapter implements ILockAdapter {
|
|
|
174
198
|
throw e;
|
|
175
199
|
}
|
|
176
200
|
} finally {
|
|
177
|
-
|
|
178
|
-
release();
|
|
201
|
+
await release();
|
|
179
202
|
}
|
|
180
203
|
},
|
|
181
204
|
};
|