mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
refactor: Move lock stuff in its own folder
This commit is contained in:
committed by
Joachim Van Herwegen
parent
ee312910d7
commit
dacfb74a6a
14
src/util/locking/ExpiringLock.ts
Normal file
14
src/util/locking/ExpiringLock.ts
Normal 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;
|
||||
}
|
||||
7
src/util/locking/ExpiringResourceLocker.ts
Normal file
7
src/util/locking/ExpiringResourceLocker.ts
Normal 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
10
src/util/locking/Lock.ts
Normal 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>;
|
||||
}
|
||||
15
src/util/locking/ResourceLocker.ts
Normal file
15
src/util/locking/ResourceLocker.ts
Normal 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>;
|
||||
}
|
||||
37
src/util/locking/SingleThreadedResourceLocker.ts
Normal file
37
src/util/locking/SingleThreadedResourceLocker.ts
Normal 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();
|
||||
} });
|
||||
}));
|
||||
}
|
||||
}
|
||||
65
src/util/locking/WrappedExpiringLock.ts
Normal file
65
src/util/locking/WrappedExpiringLock.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
37
src/util/locking/WrappedExpiringResourceLocker.ts
Normal file
37
src/util/locking/WrappedExpiringResourceLocker.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user