mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Add Finalizable interface
This commit is contained in:
parent
93374f011a
commit
29ddf57341
@ -1,13 +1,18 @@
|
|||||||
|
import type { Server } from 'http';
|
||||||
|
import { promisify } from 'util';
|
||||||
import type { HttpServerFactory } from '../server/HttpServerFactory';
|
import type { HttpServerFactory } from '../server/HttpServerFactory';
|
||||||
|
import type { Finalizable } from './final/Finalizable';
|
||||||
import { Initializer } from './Initializer';
|
import { Initializer } from './Initializer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and starts an HTTP server.
|
* Creates and starts an HTTP server.
|
||||||
*/
|
*/
|
||||||
export class ServerInitializer extends Initializer {
|
export class ServerInitializer extends Initializer implements Finalizable {
|
||||||
private readonly serverFactory: HttpServerFactory;
|
private readonly serverFactory: HttpServerFactory;
|
||||||
private readonly port: number;
|
private readonly port: number;
|
||||||
|
|
||||||
|
private server?: Server;
|
||||||
|
|
||||||
public constructor(serverFactory: HttpServerFactory, port: number) {
|
public constructor(serverFactory: HttpServerFactory, port: number) {
|
||||||
super();
|
super();
|
||||||
this.serverFactory = serverFactory;
|
this.serverFactory = serverFactory;
|
||||||
@ -15,6 +20,12 @@ export class ServerInitializer extends Initializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async handle(): Promise<void> {
|
public async handle(): Promise<void> {
|
||||||
this.serverFactory.startServer(this.port);
|
this.server = this.serverFactory.startServer(this.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async finalize(): Promise<void> {
|
||||||
|
if (this.server) {
|
||||||
|
return promisify(this.server.close.bind(this.server))();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
src/init/final/Finalizable.ts
Normal file
6
src/init/final/Finalizable.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Allows for cleaning up an object and stopping relevant loops when the application needs to be stopped.
|
||||||
|
*/
|
||||||
|
export interface Finalizable {
|
||||||
|
finalize: () => Promise<void>;
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { Finalizable } from '../../init/final/Finalizable';
|
||||||
import { getLoggerFor } from '../../logging/LogUtil';
|
import { getLoggerFor } from '../../logging/LogUtil';
|
||||||
import { InternalServerError } from '../../util/errors/InternalServerError';
|
import { InternalServerError } from '../../util/errors/InternalServerError';
|
||||||
import type { ExpiringStorage } from './ExpiringStorage';
|
import type { ExpiringStorage } from './ExpiringStorage';
|
||||||
@ -11,9 +12,8 @@ export type Expires<T> = { expires?: string; payload: T };
|
|||||||
* Will delete expired entries when trying to get their value.
|
* Will delete expired entries when trying to get their value.
|
||||||
* Has a timer that will delete all expired data every hour (default value).
|
* Has a timer that will delete all expired data every hour (default value).
|
||||||
*/
|
*/
|
||||||
export class WrappedExpiringStorage<TKey, TValue> implements ExpiringStorage<TKey, TValue> {
|
export class WrappedExpiringStorage<TKey, TValue> implements ExpiringStorage<TKey, TValue>, Finalizable {
|
||||||
protected readonly logger = getLoggerFor(this);
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
|
||||||
private readonly source: KeyValueStorage<TKey, Expires<TValue>>;
|
private readonly source: KeyValueStorage<TKey, Expires<TValue>>;
|
||||||
private readonly timer: NodeJS.Timeout;
|
private readonly timer: NodeJS.Timeout;
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ export class WrappedExpiringStorage<TKey, TValue> implements ExpiringStorage<TKe
|
|||||||
/**
|
/**
|
||||||
* Stops the continuous cleanup timer.
|
* Stops the continuous cleanup timer.
|
||||||
*/
|
*/
|
||||||
public finalize(): void {
|
public async finalize(): Promise<void> {
|
||||||
clearInterval(this.timer);
|
clearInterval(this.timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import type { RedisClient } from 'redis';
|
|||||||
import { createClient } from 'redis';
|
import { createClient } from 'redis';
|
||||||
import type { Lock } from 'redlock';
|
import type { Lock } from 'redlock';
|
||||||
import Redlock from 'redlock';
|
import Redlock from 'redlock';
|
||||||
|
import type { Finalizable } from '../../init/final/Finalizable';
|
||||||
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||||
import { getLoggerFor } from '../../logging/LogUtil';
|
import { getLoggerFor } from '../../logging/LogUtil';
|
||||||
import { InternalServerError } from '../errors/InternalServerError';
|
import { InternalServerError } from '../errors/InternalServerError';
|
||||||
@ -37,7 +38,7 @@ const defaultRedlockConfig = {
|
|||||||
* in that sense it is kind of multithreaded.
|
* in that sense it is kind of multithreaded.
|
||||||
* - Redlock does not provide the ability to see which locks have expired
|
* - Redlock does not provide the ability to see which locks have expired
|
||||||
*/
|
*/
|
||||||
export class RedisResourceLocker implements ResourceLocker {
|
export class RedisResourceLocker implements ResourceLocker, Finalizable {
|
||||||
protected readonly logger = getLoggerFor(this);
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
|
||||||
private readonly redlock: Redlock;
|
private readonly redlock: Redlock;
|
||||||
@ -100,10 +101,13 @@ export class RedisResourceLocker implements ResourceLocker {
|
|||||||
public async finalize(): Promise<void> {
|
public async finalize(): Promise<void> {
|
||||||
// This for loop is an extra failsafe,
|
// This for loop is an extra failsafe,
|
||||||
// this extra code won't slow down anything, this function will only be called to shut down in peace
|
// this extra code won't slow down anything, this function will only be called to shut down in peace
|
||||||
for (const [ , { lock }] of this.lockMap.entries()) {
|
try {
|
||||||
await this.release({ path: lock.resource });
|
for (const [ , { lock }] of this.lockMap.entries()) {
|
||||||
|
await this.release({ path: lock.resource });
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await this.redlock.quit();
|
||||||
}
|
}
|
||||||
await this.redlock.quit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async acquire(identifier: ResourceIdentifier): Promise<void> {
|
public async acquire(identifier: ResourceIdentifier): Promise<void> {
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
|
import type { Server } from 'http';
|
||||||
import { ServerInitializer } from '../../../src/init/ServerInitializer';
|
import { ServerInitializer } from '../../../src/init/ServerInitializer';
|
||||||
import type { HttpServerFactory } from '../../../src/server/HttpServerFactory';
|
import type { HttpServerFactory } from '../../../src/server/HttpServerFactory';
|
||||||
|
|
||||||
describe('ServerInitializer', (): void => {
|
describe('ServerInitializer', (): void => {
|
||||||
const serverFactory: jest.Mocked<HttpServerFactory> = {
|
let server: Server;
|
||||||
startServer: jest.fn(),
|
let serverFactory: jest.Mocked<HttpServerFactory>;
|
||||||
};
|
|
||||||
|
|
||||||
let initializer: ServerInitializer;
|
let initializer: ServerInitializer;
|
||||||
beforeAll(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
|
server = {
|
||||||
|
close: jest.fn((fn: () => void): void => fn()),
|
||||||
|
} as any;
|
||||||
|
serverFactory = {
|
||||||
|
startServer: jest.fn().mockReturnValue(server),
|
||||||
|
};
|
||||||
initializer = new ServerInitializer(serverFactory, 3000);
|
initializer = new ServerInitializer(serverFactory, 3000);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -15,4 +21,15 @@ describe('ServerInitializer', (): void => {
|
|||||||
await initializer.handle();
|
await initializer.handle();
|
||||||
expect(serverFactory.startServer).toHaveBeenCalledWith(3000);
|
expect(serverFactory.startServer).toHaveBeenCalledWith(3000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can stop the server.', async(): Promise<void> => {
|
||||||
|
await initializer.handle();
|
||||||
|
await expect(initializer.finalize()).resolves.toBeUndefined();
|
||||||
|
expect(server.close).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only tries to stop the server if it was initialized.', async(): Promise<void> => {
|
||||||
|
await expect(initializer.finalize()).resolves.toBeUndefined();
|
||||||
|
expect(server.close).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -154,7 +154,7 @@ describe('A WrappedExpiringStorage', (): void => {
|
|||||||
yield* data;
|
yield* data;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(storage.finalize()).toBeUndefined();
|
await expect(storage.finalize()).resolves.toBeUndefined();
|
||||||
|
|
||||||
// Make sure clearInterval was called with the interval timer
|
// Make sure clearInterval was called with the interval timer
|
||||||
expect(mockClear.mock.calls).toHaveLength(1);
|
expect(mockClear.mock.calls).toHaveLength(1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user