refactor: Move lock stuff in its own folder

This commit is contained in:
smessie
2020-11-17 14:58:00 +01:00
committed by Joachim Van Herwegen
parent ee312910d7
commit dacfb74a6a
13 changed files with 16 additions and 16 deletions

View File

@@ -0,0 +1,14 @@
import type { EventEmitter } from 'events';
import type { Lock } from './Lock';
/**
* Interface for a lock that expires after a certain period of inactivity.
* Activity can be signaled by calling `renew`, which resets the expiration timeout.
* When the lock has expired, an `expired` event is emitted and the lock is released.
*/
export interface ExpiringLock extends Lock, EventEmitter {
/**
* Reset the lock expiration timeout.
*/
renew: () => void;
}

View File

@@ -0,0 +1,7 @@
import type { ExpiringLock } from './ExpiringLock';
import type { ResourceLocker } from './ResourceLocker';
/**
* Interface for a factory of expiring locks.
*/
export interface ExpiringResourceLocker<T extends ExpiringLock = ExpiringLock> extends ResourceLocker<T> {}

10
src/util/locking/Lock.ts Normal file
View File

@@ -0,0 +1,10 @@
/**
* Lock used by a {@link ResourceLocker} for non-atomic operations.
*/
export interface Lock {
/**
* Release this lock.
* @returns A promise resolving when the release is finished.
*/
release: () => Promise<void>;
}

View File

@@ -0,0 +1,15 @@
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import type { Lock } from './Lock';
/**
* Allows the locking of resources which is needed for non-atomic {@link ResourceStore}s.
*/
export interface ResourceLocker<T extends Lock = Lock> {
/**
* Lock the given resource.
* @param identifier - Identifier of the resource that needs to be locked.
*
* @returns A promise containing the lock on the resource.
*/
acquire: (identifier: ResourceIdentifier) => Promise<T>;
}

View File

@@ -0,0 +1,37 @@
import AsyncLock from 'async-lock';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../../logging/LogUtil';
import type { Lock } from './Lock';
import type { ResourceLocker } from './ResourceLocker';
/**
* A resource locker making use of the `async-lock` library.
*/
export class SingleThreadedResourceLocker implements ResourceLocker {
protected readonly logger = getLoggerFor(this);
private readonly locks: AsyncLock;
public constructor() {
this.locks = new AsyncLock();
}
/**
* Acquires a new lock for the requested identifier.
* Will resolve when the lock is available.
* @param identifier - Identifier of resource that needs to be locked.
*
* @returns The {@link Lock} when it's available. Its release function needs to be called when finished.
*/
public async acquire(identifier: ResourceIdentifier): Promise<Lock> {
this.logger.verbose(`Acquiring lock for ${identifier.path}`);
return new Promise(async(resolve): Promise<Lock> =>
this.locks.acquire(identifier.path, (done): void => {
this.logger.verbose(`Acquired lock for ${identifier.path}`);
resolve({ release: async(): Promise<void> => {
this.logger.verbose(`Released lock for ${identifier.path}`);
done();
} });
}));
}
}

View File

@@ -0,0 +1,65 @@
import { EventEmitter } from 'events';
import { getLoggerFor } from '../../logging/LogUtil';
import type { ExpiringLock } from './ExpiringLock';
import type { Lock } from './Lock';
/**
* An implementation of an expiring lock which defines the expiration logic.
*
* ExpiringLock used by a {@link ExpiringResourceLocker} for non-atomic operations.
* Emits an "expired" event when internal timer runs out and calls release function when this happen.
*/
export class WrappedExpiringLock extends EventEmitter implements ExpiringLock {
protected readonly logger = getLoggerFor(this);
protected readonly innerLock: Lock;
protected readonly expiration: number;
protected timeoutHandle?: NodeJS.Timeout;
/**
* @param innerLock - Instance of ResourceLocker to use for acquiring a lock.
* @param expiration - Time in ms after which the lock expires.
*/
public constructor(innerLock: Lock, expiration: number) {
super();
this.innerLock = innerLock;
this.expiration = expiration;
this.scheduleTimeout();
}
/**
* Release this lock.
* @returns A promise resolving when the release is finished.
*/
public async release(): Promise<void> {
this.clearTimeout();
return this.innerLock.release();
}
/**
* Reset the unlock timer.
*/
public renew(): void {
this.clearTimeout();
this.scheduleTimeout();
}
private async expire(): Promise<void> {
this.logger.verbose(`Lock expired after ${this.expiration}ms`);
this.emit('expired');
try {
await this.innerLock.release();
} catch (error: unknown) {
this.emit('error', error);
}
}
private clearTimeout(): void {
clearTimeout(this.timeoutHandle!);
}
private scheduleTimeout(): void {
this.logger.verbose(`Renewed expiring lock`);
this.timeoutHandle = setTimeout((): any => this.expire(), this.expiration);
}
}

View File

@@ -0,0 +1,37 @@
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../../logging/LogUtil';
import type { ExpiringLock } from './ExpiringLock';
import type { ExpiringResourceLocker } from './ExpiringResourceLocker';
import type { ResourceLocker } from './ResourceLocker';
import { WrappedExpiringLock } from './WrappedExpiringLock';
/**
* Allows the locking of resources which is needed for non-atomic {@link ResourceStore}s.
* Differs from {@Link ResourceLocker} by adding expiration logic.
*/
export class WrappedExpiringResourceLocker implements ExpiringResourceLocker {
protected readonly logger = getLoggerFor(this);
protected readonly locker: ResourceLocker;
protected readonly expiration: number;
/**
* @param locker - Instance of ResourceLocker to use for acquiring a lock.
* @param expiration - Time in ms after which the lock expires.
*/
public constructor(locker: ResourceLocker, expiration: number) {
this.locker = locker;
this.expiration = expiration;
}
/**
* Lock the given resource with a lock providing expiration functionality.
* @param identifier - Identifier of the resource that needs to be locked.
*
* @returns A promise containing the expiring lock on the resource.
*/
public async acquire(identifier: ResourceIdentifier): Promise<ExpiringLock> {
const innerLock = await this.locker.acquire(identifier);
return new WrappedExpiringLock(innerLock, this.expiration);
}
}