From 3c1b288beff13c47385c249a1048a296392a0208 Mon Sep 17 00:00:00 2001 From: Roger Qiu Date: Fri, 1 Apr 2022 14:47:11 +1100 Subject: [PATCH] WIP --- src/Lock.ts | 9 +- src/RWLockReader.ts | 15 +-- src/RWLockWriter.ts | 7 ++ src/index.ts | 3 + {tests => src}/utils.ts | 0 tests/Lock.test.ts | 12 +-- tests/RWLockReader.test.ts | 188 +++++++++++++++++-------------------- tests/RWLockWriter.test.ts | 22 ++--- 8 files changed, 125 insertions(+), 131 deletions(-) rename {tests => src}/utils.ts (100%) diff --git a/src/Lock.ts b/src/Lock.ts index 91d4e7a..2fef107 100644 --- a/src/Lock.ts +++ b/src/Lock.ts @@ -2,11 +2,11 @@ import type { MutexInterface } from 'async-mutex'; import type { ResourceAcquire } from '@matrixai/resources'; import { Mutex, withTimeout } from 'async-mutex'; import { withF, withG } from '@matrixai/resources'; +import { sleep } from './utils'; import { ErrorAsyncLocksTimeout } from './errors'; class Lock { protected _lock: Mutex = new Mutex(); - protected release: MutexInterface.Releaser; protected _count: number = 0; public lock(timeout?: number): ResourceAcquire { @@ -16,8 +16,9 @@ class Lock { if (timeout != null) { lock = withTimeout(this._lock, timeout, new ErrorAsyncLocksTimeout()); } + let release: MutexInterface.Releaser; try { - this.release = await lock.acquire(); + release = await lock.acquire(); } catch (e) { --this._count; throw e; @@ -25,7 +26,9 @@ class Lock { return [ async () => { --this._count; - this.release(); + release(); + // Allow semaphore to settle https://github.com/DirtyHairy/async-mutex/issues/54 + await sleep(0); }, this, ]; diff --git a/src/RWLockReader.ts b/src/RWLockReader.ts index fff5f80..957495e 100644 --- a/src/RWLockReader.ts +++ b/src/RWLockReader.ts @@ -2,6 +2,7 @@ import type { MutexInterface } from 'async-mutex'; import type { ResourceAcquire } from '@matrixai/resources'; import { Mutex, withTimeout } from 'async-mutex'; import { withF, withG } from '@matrixai/resources'; +import { sleep } from './utils'; import { ErrorAsyncLocksTimeout } from './errors'; /** @@ -23,24 +24,21 @@ class RWLockReader { lock = withTimeout(this.lock, timeout, new ErrorAsyncLocksTimeout()); } try { - console.log('ATTEMPT READ'); this.release = await lock.acquire(); - console.log('ACQUIRED READ'); - } catch (e) { - console.log('READ LOCK TIMEOUT', e.name); --this._readerCount; throw e; } } return [ async () => { - console.log('RELEASE READ LOCK'); const readerCount = --this._readerCount; // The last reader unlocks if (readerCount === 0) { this.release(); } + // Allow semaphore to settle https://github.com/DirtyHairy/async-mutex/issues/54 + await sleep(0); }, this, ]; @@ -56,20 +54,17 @@ class RWLockReader { } let release: MutexInterface.Releaser; try { - console.log('ATTEMPT WRITE'); release = await lock.acquire(); - console.log('ACQUIRED WRITE'); - } catch (e) { - console.log('WRITE LOCK TIMEOUT', e.name); --this._writerCount; throw e; } return [ async () => { - console.log('RELEASE WRITE LOCK'); release(); --this._writerCount; + // Allow semaphore to settle https://github.com/DirtyHairy/async-mutex/issues/54 + await sleep(0); }, this, ]; diff --git a/src/RWLockWriter.ts b/src/RWLockWriter.ts index 43d43a3..730c293 100644 --- a/src/RWLockWriter.ts +++ b/src/RWLockWriter.ts @@ -3,6 +3,7 @@ import type { ResourceAcquire } from '@matrixai/resources'; import { performance } from 'perf_hooks'; import { Mutex, withTimeout } from 'async-mutex'; import { withF, withG } from '@matrixai/resources'; +import { sleep } from './utils'; import { ErrorAsyncLocksTimeout } from './errors'; /** @@ -67,6 +68,8 @@ class RWLockWriter { // The last reader unlocks if (readerCount === 0) { this.readersRelease(); + // Allow semaphore to settle https://github.com/DirtyHairy/async-mutex/issues/54 + await sleep(0); } }, this, @@ -99,6 +102,8 @@ class RWLockWriter { } catch (e) { writersRelease(); --this._writerCount; + // Allow semaphore to settle https://github.com/DirtyHairy/async-mutex/issues/54 + await sleep(0); throw e; } return [ @@ -106,6 +111,8 @@ class RWLockWriter { this.readersRelease(); writersRelease(); --this._writerCount; + // Allow semaphore to settle https://github.com/DirtyHairy/async-mutex/issues/54 + await sleep(0); }, this, ]; diff --git a/src/index.ts b/src/index.ts index cf26ea2..260b83d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,6 @@ export { default as Lock } from './Lock'; export { default as RWLockReader } from './RWLockReader'; export { default as RWLockWriter } from './RWLockWriter'; +export * as errors from './errors'; +export * as utils from './utils'; +export * as types from './types'; diff --git a/tests/utils.ts b/src/utils.ts similarity index 100% rename from tests/utils.ts rename to src/utils.ts diff --git a/tests/Lock.test.ts b/tests/Lock.test.ts index 1da85fc..8ee1b32 100644 --- a/tests/Lock.test.ts +++ b/tests/Lock.test.ts @@ -1,7 +1,7 @@ import { withF, withG } from '@matrixai/resources'; import Lock from '@/Lock'; +import * as utils from '@/utils'; import * as errors from '@/errors'; -import * as testUtils from './utils'; describe(Lock.name, () => { test('withF', async () => { @@ -97,7 +97,7 @@ describe(Lock.name, () => { let value; const p1 = withF([lock.lock()], async () => { value = 'p1'; - await testUtils.sleep(100); + await utils.sleep(100); }); const p2 = lock.waitForUnlock().then(() => { value = 'p2'; @@ -124,12 +124,12 @@ describe(Lock.name, () => { await Promise.all([ lock.withF(async () => { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; }), lock.withF(async () => { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; }), ]); @@ -139,7 +139,7 @@ describe(Lock.name, () => { (async () => { const g = lock.withG(async function* (): AsyncGenerator { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; return 'last'; }); @@ -149,7 +149,7 @@ describe(Lock.name, () => { (async () => { const g = lock.withG(async function* (): AsyncGenerator { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; return 'last'; }); diff --git a/tests/RWLockReader.test.ts b/tests/RWLockReader.test.ts index 15a0724..6e347bb 100644 --- a/tests/RWLockReader.test.ts +++ b/tests/RWLockReader.test.ts @@ -1,7 +1,7 @@ import { withF, withG } from '@matrixai/resources'; import RWLockReader from '@/RWLockReader'; +import * as utils from '@/utils'; import * as errors from '@/errors'; -import * as testUtils from './utils'; describe(RWLockReader.name, () => { test('withF', async () => { @@ -189,7 +189,7 @@ describe(RWLockReader.name, () => { let value; const p1 = withF([lock.read()], async () => { value = 'p1'; - await testUtils.sleep(100); + await utils.sleep(100); }); const p2 = lock.waitForUnlock().then(() => { value = 'p2'; @@ -203,7 +203,7 @@ describe(RWLockReader.name, () => { let value; const p1 = withF([lock.write()], async () => { value = 'p1'; - await testUtils.sleep(100); + await utils.sleep(100); }); const p2 = lock.waitForUnlock().then(() => { value = 'p2'; @@ -239,12 +239,12 @@ describe(RWLockReader.name, () => { await Promise.all([ lock.withReadF(async () => { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; }), lock.withReadF(async () => { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; }), ]); @@ -253,12 +253,12 @@ describe(RWLockReader.name, () => { await Promise.all([ lock.withWriteF(async () => { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; }), lock.withWriteF(async () => { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; }), ]); @@ -268,7 +268,7 @@ describe(RWLockReader.name, () => { (async () => { const g = lock.withReadG(async function* (): AsyncGenerator { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; return 'last'; }); @@ -278,7 +278,7 @@ describe(RWLockReader.name, () => { (async () => { const g = lock.withReadG(async function* (): AsyncGenerator { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; return 'last'; }); @@ -292,7 +292,7 @@ describe(RWLockReader.name, () => { (async () => { const g = lock.withWriteG(async function* (): AsyncGenerator { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; return 'last'; }); @@ -302,7 +302,7 @@ describe(RWLockReader.name, () => { (async () => { const g = lock.withWriteG(async function* (): AsyncGenerator { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; return 'last'; }); @@ -351,104 +351,90 @@ describe(RWLockReader.name, () => { 'write2', ]); }); - test.only('timeout', async () => { + test('timeout', async () => { const lock = new RWLockReader(); - // await withF([lock.read(0)], async ([lock]) => { - // expect(lock.isLocked()).toBe(true); - // expect(lock.readerCount).toBe(1); - // expect(lock.writerCount).toBe(0); - // const f = jest.fn(); - // await expect( - // withF([lock.write(100)], f) - // ).rejects.toThrow(errors.ErrorAsyncLocksTimeout); - // expect(f).not.toBeCalled(); - // expect(lock.isLocked()).toBe(true); - // expect(lock.readerCount).toBe(1); - // expect(lock.writerCount).toBe(0); - // }); - // expect(lock.isLocked()).toBe(false); - // expect(lock.readerCount).toBe(0); - // expect(lock.writerCount).toBe(0); - // await withF([lock.write(0)], async ([lock]) => { - // expect(lock.isLocked()).toBe(true); - // expect(lock.readerCount).toBe(0); - // expect(lock.writerCount).toBe(1); - // const f = jest.fn(); - // await expect( - // withF([lock.read(100)], f) - // ).rejects.toThrow(errors.ErrorAsyncLocksTimeout); - // expect(f).not.toBeCalled(); - // expect(lock.isLocked()).toBe(true); - // expect(lock.readerCount).toBe(0); - // expect(lock.writerCount).toBe(1); - // }); - // await lock.withReadF(async () => { - // const f = jest.fn(); - // await expect(lock.withWriteF(f, 100)).rejects.toThrow(errors.ErrorAsyncLocksTimeout); - // expect(f).not.toBeCalled(); - // }, 100); - // await lock.withWriteF(async () => { - // const f = jest.fn(); - // await expect(lock.withReadF(f, 100)).rejects.toThrow(errors.ErrorAsyncLocksTimeout); - // expect(f).not.toBeCalled(); - // }, 100); - // await lock.withWriteF(async () => { - // const f = jest.fn(); - // await expect(lock.withWriteF(f, 100)).rejects.toThrow(errors.ErrorAsyncLocksTimeout); - // expect(f).not.toBeCalled(); - // }, 100); - // const gRead = lock.withReadG(async function *() { - // expect(lock.isLocked()).toBe(true); - // expect(lock.readerCount).toBe(1); - // expect(lock.writerCount).toBe(0); - // const f = jest.fn(); - // const g = lock.withWriteG(f, 100); - // await expect(g.next()).rejects.toThrow(errors.ErrorAsyncLocksTimeout); - // expect(f).not.toBeCalled(); - // expect(lock.isLocked()).toBe(true); - // expect(lock.readerCount).toBe(1); - // expect(lock.writerCount).toBe(0); - // }); - // await gRead.next(); - // expect(lock.isLocked()).toBe(false); - // expect(lock.readerCount).toBe(0); - // expect(lock.writerCount).toBe(0); - + await withF([lock.read(0)], async ([lock]) => { + expect(lock.isLocked()).toBe(true); + expect(lock.readerCount).toBe(1); + expect(lock.writerCount).toBe(0); + const f = jest.fn(); + await expect( + withF([lock.write(100)], f) + ).rejects.toThrow(errors.ErrorAsyncLocksTimeout); + expect(f).not.toBeCalled(); + expect(lock.isLocked()).toBe(true); + expect(lock.readerCount).toBe(1); + expect(lock.writerCount).toBe(0); + }); + expect(lock.isLocked()).toBe(false); + expect(lock.readerCount).toBe(0); + expect(lock.writerCount).toBe(0); + await withF([lock.write(0)], async ([lock]) => { + expect(lock.isLocked()).toBe(true); + expect(lock.readerCount).toBe(0); + expect(lock.writerCount).toBe(1); + const f = jest.fn(); + await expect( + withF([lock.read(100)], f) + ).rejects.toThrow(errors.ErrorAsyncLocksTimeout); + expect(f).not.toBeCalled(); + expect(lock.isLocked()).toBe(true); + expect(lock.readerCount).toBe(0); + expect(lock.writerCount).toBe(1); + }); + await lock.withReadF(async () => { + const f = jest.fn(); + await expect(lock.withWriteF(f, 100)).rejects.toThrow(errors.ErrorAsyncLocksTimeout); + expect(f).not.toBeCalled(); + }, 100); + await lock.withWriteF(async () => { + const f = jest.fn(); + await expect(lock.withReadF(f, 100)).rejects.toThrow(errors.ErrorAsyncLocksTimeout); + expect(f).not.toBeCalled(); + }, 100); + await lock.withWriteF(async () => { + const f = jest.fn(); + await expect(lock.withWriteF(f, 100)).rejects.toThrow(errors.ErrorAsyncLocksTimeout); + expect(f).not.toBeCalled(); + }, 100); + const gRead = lock.withReadG(async function *() { + expect(lock.isLocked()).toBe(true); + expect(lock.readerCount).toBe(1); + expect(lock.writerCount).toBe(0); + const f = jest.fn(); + const g = lock.withWriteG(f, 100); + await expect(g.next()).rejects.toThrow(errors.ErrorAsyncLocksTimeout); + expect(f).not.toBeCalled(); + expect(lock.isLocked()).toBe(true); + expect(lock.readerCount).toBe(1); + expect(lock.writerCount).toBe(0); + }); + await gRead.next(); + expect(lock.isLocked()).toBe(false); + expect(lock.readerCount).toBe(0); + expect(lock.writerCount).toBe(0); const gWrite = lock.withWriteG(async function *() { - // expect(lock.isLocked()).toBe(true); - // expect(lock.readerCount).toBe(0); - // expect(lock.writerCount).toBe(1); - + expect(lock.isLocked()).toBe(true); + expect(lock.readerCount).toBe(0); + expect(lock.writerCount).toBe(1); const f1 = jest.fn(); const g1 = lock.withReadG(f1, 100); await expect(g1.next()).rejects.toThrow(errors.ErrorAsyncLocksTimeout); - - // expect(f1).not.toBeCalled(); - // expect(lock.isLocked()).toBe(true); - // expect(lock.readerCount).toBe(0); - // expect(lock.writerCount).toBe(1); - + expect(f1).not.toBeCalled(); + expect(lock.isLocked()).toBe(true); + expect(lock.readerCount).toBe(0); + expect(lock.writerCount).toBe(1); const f2 = jest.fn(); const g2 = lock.withWriteG(f2, 100); await expect(g2.next()).rejects.toThrow(errors.ErrorAsyncLocksTimeout); - - // expect(f2).not.toBeCalled(); - // expect(lock.isLocked()).toBe(true); - // expect(lock.readerCount).toBe(0); - // expect(lock.writerCount).toBe(1); - + expect(f2).not.toBeCalled(); + expect(lock.isLocked()).toBe(true); + expect(lock.readerCount).toBe(0); + expect(lock.writerCount).toBe(1); }); - console.log(await gWrite.next()); - console.log(await gWrite.next()); - - console.log(lock.readerCount); - console.log(lock.writerCount); - console.log(lock.isLocked()); - await testUtils.sleep(0); - console.log('AFTER', lock.isLocked()); - - // expect(lock.isLocked()).toBe(false); - // expect(lock.readerCount).toBe(0); - // expect(lock.writerCount).toBe(0); + await gWrite.next(); + expect(lock.isLocked()).toBe(false); + expect(lock.readerCount).toBe(0); + expect(lock.writerCount).toBe(0); }); }); diff --git a/tests/RWLockWriter.test.ts b/tests/RWLockWriter.test.ts index 769118f..c2aaf40 100644 --- a/tests/RWLockWriter.test.ts +++ b/tests/RWLockWriter.test.ts @@ -1,7 +1,7 @@ import { withF, withG } from '@matrixai/resources'; import RWLockWriter from '@/RWLockWriter'; +import * as utils from '@/utils'; import * as errors from '@/errors'; -import * as testUtils from './utils'; describe(RWLockWriter.name, () => { test('withF', async () => { @@ -189,7 +189,7 @@ describe(RWLockWriter.name, () => { let value; const p1 = withF([lock.read()], async () => { value = 'p1'; - await testUtils.sleep(100); + await utils.sleep(100); }); const p2 = lock.waitForUnlock().then(() => { value = 'p2'; @@ -203,7 +203,7 @@ describe(RWLockWriter.name, () => { let value; const p1 = withF([lock.write()], async () => { value = 'p1'; - await testUtils.sleep(100); + await utils.sleep(100); }); const p2 = lock.waitForUnlock().then(() => { value = 'p2'; @@ -239,12 +239,12 @@ describe(RWLockWriter.name, () => { await Promise.all([ lock.withReadF(async () => { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; }), lock.withReadF(async () => { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; }), ]); @@ -253,12 +253,12 @@ describe(RWLockWriter.name, () => { await Promise.all([ lock.withWriteF(async () => { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; }), lock.withWriteF(async () => { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; }), ]); @@ -268,7 +268,7 @@ describe(RWLockWriter.name, () => { (async () => { const g = lock.withReadG(async function* (): AsyncGenerator { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; return 'last'; }); @@ -278,7 +278,7 @@ describe(RWLockWriter.name, () => { (async () => { const g = lock.withReadG(async function* (): AsyncGenerator { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; return 'last'; }); @@ -292,7 +292,7 @@ describe(RWLockWriter.name, () => { (async () => { const g = lock.withWriteG(async function* (): AsyncGenerator { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; return 'last'; }); @@ -302,7 +302,7 @@ describe(RWLockWriter.name, () => { (async () => { const g = lock.withWriteG(async function* (): AsyncGenerator { const value_ = value + 1; - await testUtils.sleep(100); + await utils.sleep(100); value = value_; return 'last'; });