Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
CMCDragonkai committed Jun 30, 2022
1 parent 511a742 commit d50ac51
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 64 deletions.
125 changes: 65 additions & 60 deletions src/LockBox.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import type { ResourceAcquire, ResourceRelease } from '@matrixai/resources';
import type { Lockable, ToString, LockRequest } from './types';
import type {
ToString,
Lockable,
MultiLockRequest,
MultiLockAcquire,
MultiLockAcquired
} from './types';
import { withF, withG } from '@matrixai/resources';
import { ErrorAsyncLocksLockBoxConflict } from './errors';

class LockBox<L extends Lockable = Lockable> implements Lockable {
protected _locks: Map<string, L> = new Map();

public lock(...requests: Array<LockRequest<L>>): ResourceAcquire<LockBox<L>> {
public lock(...requests: Array<MultiLockRequest<L>>): ResourceAcquire<LockBox<L>> {
return async () => {
// Convert to strings
// This creates a copy of the requests
Expand Down Expand Up @@ -79,8 +85,8 @@ class LockBox<L extends Lockable = Lockable> implements Lockable {
}

public lockMulti(
...requests: Array<LockRequest<L>>
): Array<[ToString, ResourceAcquire<L>]> {
...requests: Array<MultiLockRequest<L>>
): Array<MultiLockAcquire<L>> {
// Convert to strings
// This creates a copy of the requests
let requests_: Array<
Expand All @@ -101,51 +107,49 @@ class LockBox<L extends Lockable = Lockable> implements Lockable {
requests_ = requests_.filter(
([key], i, arr) => i === 0 || key !== arr[i - 1][0],
);
const lockAcquires: Array<[ToString, ResourceAcquire<L>]> = [];
const lockAcquires: Array<MultiLockAcquire<L>> = [];
for (const [key, keyOrig, LockConstructor, ...lockingParams] of requests_) {
lockAcquires.push([
keyOrig,
async () => {
let lock = this._locks.get(key);
let lockRelease: ResourceRelease;
try {
if (lock == null) {
lock = new LockConstructor();
this._locks.set(key, lock);
} else {
// It is possible to swap the lock class, but only after the lock key is released
if (!(lock instanceof LockConstructor)) {
throw new ErrorAsyncLocksLockBoxConflict(
`Lock ${key} is already locked with class ${lock.constructor.name}, which conflicts with class ${LockConstructor.name}`,
);
}
const lockAcquire: ResourceAcquire<L> = async () => {
let lock = this._locks.get(key);
let lockRelease: ResourceRelease;
try {
if (lock == null) {
lock = new LockConstructor();
this._locks.set(key, lock);
} else {
// It is possible to swap the lock class, but only after the lock key is released
if (!(lock instanceof LockConstructor)) {
throw new ErrorAsyncLocksLockBoxConflict(
`Lock ${key} is already locked with class ${lock.constructor.name}, which conflicts with class ${LockConstructor.name}`,
);
}
const lockAcquire = lock.lock(...lockingParams);
[lockRelease] = await lockAcquire();
} catch (e) {
}
const lockAcquire = lock.lock(...lockingParams);
[lockRelease] = await lockAcquire();
} catch (e) {
// If it is still locked, then it is held by a different context
// only delete if no contexts are locking the lock
if (!lock!.isLocked()) {
this._locks.delete(key);
}
throw e;
}
let released = false;
return [
async () => {
if (released) return;
released = true;
await lockRelease();
// If it is still locked, then it is held by a different context
// only delete if no contexts are locking the lock
if (!lock!.isLocked()) {
this._locks.delete(key);
}
throw e;
}
let released = false;
return [
async () => {
if (released) return;
released = true;
await lockRelease();
// If it is still locked, then it is held by a different context
// only delete if no contexts are locking the lock
if (!lock!.isLocked()) {
this._locks.delete(key);
}
},
lock,
];
},
]);
},
lock,
];
};
lockAcquires.push([keyOrig, lockAcquire, ...lockingParams]);
}
return lockAcquires;
}
Expand Down Expand Up @@ -194,71 +198,72 @@ class LockBox<L extends Lockable = Lockable> implements Lockable {

public async withF<T>(
...params: [
...requests: Array<LockRequest<L>>,
...requests: Array<MultiLockRequest<L>>,
f: (lockBox: LockBox<L>) => Promise<T>,
]
): Promise<T> {
const f = params.pop() as (lockBox: LockBox<L>) => Promise<T>;
return withF(
[this.lock(...(params as Array<LockRequest<L>>))],
[this.lock(...(params as Array<MultiLockRequest<L>>))],
([lockBox]) => f(lockBox),
);
}

public async withMultiF<T>(
...params: [
...requests: Array<LockRequest<L>>,
f: (multiLocks: Array<[ToString, L]>) => Promise<T>,
...requests: Array<MultiLockRequest<L>>,
f: (multiLocks: Array<MultiLockAcquired<L>>) => Promise<T>,
]
): Promise<T> {
const f = params.pop() as (multiLocks: Array<[ToString, L]>) => Promise<T>;
const lockAcquires = this.lockMulti(...(params as Array<LockRequest<L>>));
const lockAcquires_: Array<ResourceAcquire<[ToString, L]>> =
const f = params.pop() as (multiLocks: Array<MultiLockAcquired<L>>) => Promise<T>;
const lockAcquires = this.lockMulti(...(params as Array<MultiLockRequest<L>>));

const lockAcquires_: Array<ResourceAcquire<MultiLockAcquired<L>>> =
lockAcquires.map(
([key, lockAcquire]) =>
([key, lockAcquire, ...lockingParams]) =>
(...r) =>
lockAcquire(...r).then(
([lockRelease, lock]) =>
[lockRelease, [key, lock]] as [ResourceRelease, [ToString, L]],
[lockRelease, [key, lock, ...lockingParams]] as [ResourceRelease, MultiLockAcquired<L>],
),
);
return withF(lockAcquires_, f);
}

public withG<T, TReturn, TNext>(
...params: [
...requests: Array<LockRequest<L>>,
...requests: Array<MultiLockRequest<L>>,
g: (lockBox: LockBox<L>) => AsyncGenerator<T, TReturn, TNext>,
]
): AsyncGenerator<T, TReturn, TNext> {
const g = params.pop() as (
lockBox: LockBox<L>,
) => AsyncGenerator<T, TReturn, TNext>;
return withG(
[this.lock(...(params as Array<LockRequest<L>>))],
[this.lock(...(params as Array<MultiLockRequest<L>>))],
([lockBox]) => g(lockBox),
);
}

public withMultiG<T, TReturn, TNext>(
...params: [
...requests: Array<LockRequest<L>>,
...requests: Array<MultiLockRequest<L>>,
g: (
multiLocks: Array<[ToString, L]>,
multiLocks: Array<MultiLockAcquired<L>>,
) => AsyncGenerator<T, TReturn, TNext>,
]
) {
const g = params.pop() as (
multiLocks: Array<[ToString, L]>,
multiLocks: Array<MultiLockAcquired<L>>,
) => AsyncGenerator<T, TReturn, TNext>;
const lockAcquires = this.lockMulti(...(params as Array<LockRequest<L>>));
const lockAcquires_: Array<ResourceAcquire<[ToString, L]>> =
const lockAcquires = this.lockMulti(...(params as Array<MultiLockRequest<L>>));
const lockAcquires_: Array<ResourceAcquire<MultiLockAcquired<L>>> =
lockAcquires.map(
([key, lockAcquire]) =>
([key, lockAcquire, ...lockingParams]) =>
(...r) =>
lockAcquire(...r).then(
([lockRelease, lock]) =>
[lockRelease, [key, lock]] as [ResourceRelease, [ToString, L]],
[lockRelease, [key, lock, ...lockingParams]] as [ResourceRelease, MultiLockAcquired<L>],
),
);
return withG(lockAcquires_, g);
Expand Down
16 changes: 14 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,22 @@ interface Lockable {
): AsyncGenerator<T, TReturn, TNext>;
}

type LockRequest<L extends Lockable = Lockable> = [
type MultiLockRequest<L extends Lockable = Lockable> = [
key: ToString,
lockConstructor: new () => L,
...lockingParams: Parameters<L['lock']>,
];

export type { POJO, ToString, Lockable, LockRequest };
type MultiLockAcquire<L extends Lockable = Lockable> = [
key: ToString,
lockAcquire: ResourceAcquire<L>,
...lockingParams: Parameters<L['lock']>,
];

type MultiLockAcquired<L extends Lockable = Lockable> = [
key: ToString,
lock: L,
...lockingParams: Parameters<L['lock']>,
];

export type { POJO, ToString, Lockable, MultiLockRequest, MultiLockAcquire, MultiLockAcquired };
4 changes: 2 additions & 2 deletions tests/LockBox.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ResourceRelease } from '@matrixai/resources';
import type { LockRequest } from '@/types';
import type { MultiLockRequest } from '@/types';
import { withF, withG } from '@matrixai/resources';
import LockBox from '@/LockBox';
import Lock from '@/Lock';
Expand Down Expand Up @@ -339,7 +339,7 @@ describe(LockBox.name, () => {
test('can map keys to LockBox locks', async () => {
const lockBox = new LockBox();
const keys = ['1', '2', '3', '4'];
const locks: Array<LockRequest<RWLockWriter>> = keys.map((key) => [
const locks: Array<MultiLockRequest<RWLockWriter>> = keys.map((key) => [
key,
RWLockWriter,
'write',
Expand Down

0 comments on commit d50ac51

Please sign in to comment.