Skip to content

Commit 0be72f9

Browse files
committed
fix(semaphore): release acquired permits "allocated" to a recently failed tryLock
1
1 parent a006044 commit 0be72f9

File tree

2 files changed

+16
-7
lines changed

2 files changed

+16
-7
lines changed

src/semaphore/semaphore.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,10 @@ describe("Semaphore", () => {
264264
}, delay);
265265

266266
await Promise.all([
267-
// second acquire in time
268-
new Promise(resolve => setTimeout(resolve, 10)).then(() => semaphore.acquire(2)),
267+
// `acquire` after the `tryAcquire`
268+
new Promise(resolve => setTimeout(resolve, delay / 2)).then(() =>
269+
semaphore.acquire(3)
270+
),
269271

270272
semaphore
271273
.tryAcquire(delay * 2, 3)
@@ -286,7 +288,7 @@ describe("Semaphore", () => {
286288
})
287289
]);
288290

289-
// The state is reset: 2 permits released for a single successful acquire (+ the initial one)
291+
// The state is reset: 3 permits released for a single successful acquire (+ the initial one)
290292
expect(semaphore.permitsAvailable).toBe(1);
291293
expect(semaphore.permitsRequired).toBe(0);
292294
expect(semaphore.queueLength).toBe(0);

src/semaphore/semaphore.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,22 @@ export class Semaphore implements Synchronizer {
142142
const { reject, resolvers } = item;
143143

144144
setTimeout(() => {
145-
// Re-establish th permits: the previous permits + the releases that were called during the wait
146-
this.permits += permitsBck + (permitsRemaining - resolvers.length);
147-
148-
// Removes the item form the queue
145+
// Remove the item from the queue
149146
const index = this.queue.findIndex(i => i === item);
150147
if (index >= 0) {
151148
this.queue.splice(index, 1);
152149
}
153150

151+
// Re-establish the permits: the previous permits + the releases that were called during the wait
152+
const permitsTaken = permitsBck + (permitsRemaining - resolvers.length);
153+
const permitsToRelease = Math.min(permitsTaken, this.permitsRequired);
154+
155+
this.permits += permitsTaken - permitsToRelease;
156+
if (permitsToRelease > 0) {
157+
// "free" the permits for this failed `tryLock`
158+
this.release(permitsToRelease);
159+
}
160+
154161
reject(
155162
new ConcurrencyExceedTimeoutException(
156163
`Timeout of ${timeout}ms exceed when acquiring.`

0 commit comments

Comments
 (0)