Skip to content

Commit

Permalink
feat: added Barrier
Browse files Browse the repository at this point in the history
  • Loading branch information
CMCDragonkai committed Jul 14, 2022
1 parent 03d3fd8 commit c843d2b
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 0 deletions.
38 changes: 38 additions & 0 deletions src/Barrier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { ResourceRelease } from '@matrixai/resources';
import Lock from './Lock';
import { ErrorAsyncLocksBarrierCount } from './errors';

class Barrier {
protected lock: Lock;
protected count: number;
protected release: ResourceRelease;

public static async createBarrier(count: number) {
const lock = new Lock();
const [release] = await lock.lock()();
return new this(count, lock, release);
}

protected constructor(count: number, lock: Lock, release: ResourceRelease) {
if (count < 0) {
throw new ErrorAsyncLocksBarrierCount();
}
this.lock = lock;
this.release = release;
this.count = count;
}

public async wait(timeout?: number): Promise<void> {
if (!this.lock.isLocked()) {
return;
}
this.count = Math.max(this.count - 1, 0);
if (this.count === 0) {
await this.release();
} else {
await this.lock.waitForUnlock(timeout);
}
}
}

export default Barrier;
5 changes: 5 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ class ErrorAsyncLocksLockBoxConflict<T> extends ErrorAsyncLocks<T> {
'LockBox cannot lock same ID with different Lockable classes';
}

class ErrorAsyncLocksBarrierCount<T> extends ErrorAsyncLocks<T> {
static description = 'Barrier must be created with a count >= 0';
}

export {
ErrorAsyncLocks,
ErrorAsyncLocksTimeout,
ErrorAsyncLocksLockBoxConflict,
ErrorAsyncLocksBarrierCount,
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { default as Lock } from './Lock';
export { default as RWLockReader } from './RWLockReader';
export { default as RWLockWriter } from './RWLockWriter';
export { default as LockBox } from './LockBox';
export { default as Barrier } from './Barrier';
export * as utils from './utils';
export * as errors from './errors';
export * from './types';
89 changes: 89 additions & 0 deletions tests/Barrier.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Barrier from '@/Barrier';
import * as utils from '@/utils';
import * as errors from '@/errors';

describe(Barrier.name, () => {
test('barrier only takes count >= 0', async () => {
await expect(Barrier.createBarrier(-1)).rejects.toThrow(
errors.ErrorAsyncLocksBarrierCount,
);
});
test('barrier blocks until concurrent count is reached', async () => {
const barrier = await Barrier.createBarrier(3);
let called1 = false;
let called2 = false;
const t1 = async () => {
await barrier.wait();
called1 = true;
};
const t2 = async () => {
await barrier.wait();
called2 = true;
};
const p1 = t1();
const p2 = t2();
await utils.sleep(1);
expect(called1).toBe(false);
expect(called2).toBe(false);
await barrier.wait();
expect(called1).toBe(true);
expect(called2).toBe(true);
const results = await Promise.allSettled([p1, p2]);
expect(results.every((result) => result.status === 'fulfilled')).toBe(true);
});
test('barrier does not block if concurrent count starts as 0', async () => {
const barrier = await Barrier.createBarrier(0);
let called1 = false;
let called2 = false;
const t1 = async () => {
await barrier.wait();
called1 = true;
};
const t2 = async () => {
await barrier.wait();
called2 = true;
};
const p1 = t1();
const p2 = t2();
await utils.sleep(1);
expect(called1).toBe(true);
expect(called2).toBe(true);
const results = await Promise.allSettled([p1, p2]);
expect(results.every((result) => result.status === 'fulfilled')).toBe(true);
});
test('barrier does not block if concurrent count starts as 1', async () => {
const barrier = await Barrier.createBarrier(1);
let called1 = false;
let called2 = false;
const t1 = async () => {
await barrier.wait();
called1 = true;
};
const t2 = async () => {
await barrier.wait();
called2 = true;
};
const p1 = t1();
const p2 = t2();
await utils.sleep(1);
expect(called1).toBe(true);
expect(called2).toBe(true);
const results = await Promise.allSettled([p1, p2]);
expect(results.every((result) => result.status === 'fulfilled')).toBe(true);
});
test('barrier wait with timeout', async () => {
const barrier = await Barrier.createBarrier(2);
let called1 = false;
const t1 = async () => {
await expect(barrier.wait(10)).rejects.toThrow(errors.ErrorAsyncLocksTimeout);
called1 = true;
};
const p1 = t1();
expect(called1).toBe(false);
await utils.sleep(5);
expect(called1).toBe(false);
await utils.sleep(10);
expect(called1).toBe(true);
await p1;
});
});

0 comments on commit c843d2b

Please sign in to comment.