Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/util/locking/FileSystemResourceLocker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ interface FileSystemResourceLockerArgs {
lockDirectory?: string;
/** Custom settings concerning retrying locks */
attemptSettings?: AttemptSettings;
/**
* Throws an error when a lock is compromised, instead of logging an error.
*/
throwOnCompromise?: boolean;
}

function isCodedError(err: unknown): err is { code: string } & Error {
Expand All @@ -65,6 +69,7 @@ export class FileSystemResourceLocker implements ResourceLocker, Initializable,
protected readonly logger = getLoggerFor(this);
private readonly attemptSettings: Required<AttemptSettings>;
private readonly lockOptions: LockOptions;
private readonly throwOnCompromise?: boolean;
/** Folder that stores the locks */
private readonly lockFolder: string;
private finalized = false;
Expand All @@ -79,6 +84,7 @@ export class FileSystemResourceLocker implements ResourceLocker, Initializable,
// Need to create lock options for this instance due to the custom `onCompromised`
this.lockOptions = { ...defaultLockOptions, onCompromised: this.customOnCompromised.bind(this) };
this.attemptSettings = { ...attemptDefaults, ...attemptSettings };
this.throwOnCompromise = args.throwOnCompromise;
this.lockFolder = joinFilePath(rootFilePath, lockDirectory ?? '/.internal/locks');
}

Expand Down Expand Up @@ -196,9 +202,12 @@ export class FileSystemResourceLocker implements ResourceLocker, Initializable,
* This allows for a clean shutdown procedure.
*/
private customOnCompromised(err: Error): void {
if (!this.finalized) {
if (this.finalized) {
this.logger.warn(`onCompromised was called with error: ${err.message}`);
} else if (this.throwOnCompromise) {
throw err;
} else {
this.logger.error(`Lock was compromised: ${err.message}`);
}
this.logger.warn(`onCompromised was called with error: ${err.message}`);
}
}
14 changes: 13 additions & 1 deletion test/unit/util/locking/FileSystemResourceLocker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,22 @@ describe('A FileSystemResourceLocker', (): void => {
});

it('stops proper-lock from throwing errors after finalize was called.', async(): Promise<void> => {
// Tests should never access private fields so we need to change this after splitting the test as mentioned above.
locker = new FileSystemResourceLocker({
rootFilePath,
attemptSettings: { retryCount: 19, retryDelay: 100 },
throwOnCompromise: true,
});
// Tests should never access private fields, so we need to change this after splitting the test as mentioned above.
// Once we have a mock we can check which parameters `unlock` was called with and extract the function from there.
expect((): void => (locker as any).customOnCompromised(new Error('test'))).toThrow('test');
await locker.finalize();
expect((locker as any).customOnCompromised(new Error('test'))).toBeUndefined();
});

it('by default does not throw an error if the lock is compromised.', async(): Promise<void> => {
// See comments above
expect((locker as any).customOnCompromised(new Error('test'))).toBeUndefined();
await locker.finalize();
expect((locker as any).customOnCompromised(new Error('test'))).toBeUndefined();
});
});