diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 70bb6acde..505e14ab9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -3,6 +3,7 @@ ## v3.0.0 ### New features - The Identity Provider now uses the `webid` scope as required for Solid-OIDC. +- The `VoidLocker` can be used to disable locking for development/testing purposes. This can be enabled by changing the `/config/util/resource-locker/` import to `debug-void.json` ### Configuration changes You might need to make changes to your v2 configuration if you use a custom config. diff --git a/config/util/README.md b/config/util/README.md index 831e18d23..5dc8dc3c2 100644 --- a/config/util/README.md +++ b/config/util/README.md @@ -36,6 +36,7 @@ to the ChainedConverter list. ## Resource-locker Which locking mechanism to use to for example prevent 2 write simultaneous write requests. +* *debug-void*: No locking mechanism, does not prevent simultaneous read/writes. * *memory*: Uses an in-memory locking mechanism. * *redis*: Uses a Redis store for locking. diff --git a/config/util/resource-locker/debug-void.json b/config/util/resource-locker/debug-void.json new file mode 100644 index 000000000..c02f6e129 --- /dev/null +++ b/config/util/resource-locker/debug-void.json @@ -0,0 +1,13 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^2.0.0/components/context.jsonld", + "@graph": [ + { + "comment": [ + "DO NOT USE IN PRODUCTION. ONLY FOR DEVELOPMENT, TESTING, OR DEBUGGING.", + "Allows multiple simultaneous read operations and write operations without locks." + ], + "@id": "urn:solid-server:default:ResourceLocker", + "@type": "VoidLocker" + } + ] +} diff --git a/src/index.ts b/src/index.ts index 8621ee257..6e77d9807 100644 --- a/src/index.ts +++ b/src/index.ts @@ -363,6 +363,7 @@ export * from './util/locking/RedisResourceLocker'; export * from './util/locking/ResourceLocker'; export * from './util/locking/SingleThreadedResourceLocker'; export * from './util/locking/WrappedExpiringReadWriteLocker'; +export * from './util/locking/VoidLocker'; // Util/Templates export * from './util/templates/ChainedTemplateEngine'; diff --git a/src/util/locking/VoidLocker.ts b/src/util/locking/VoidLocker.ts new file mode 100644 index 000000000..bc79f792e --- /dev/null +++ b/src/util/locking/VoidLocker.ts @@ -0,0 +1,34 @@ +import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier'; +import { getLoggerFor } from '../../logging/LogUtil'; +import type { ExpiringReadWriteLocker } from './ExpiringReadWriteLocker'; + +/** + * This locker will execute the whileLocked function without any locking mechanism + * + * Do not use this locker in combination with storages that doesn't handle concurrent read/writes gracefully + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +function noop(): void {} + +export class VoidLocker implements ExpiringReadWriteLocker { + protected readonly logger = getLoggerFor(this); + + public constructor() { + this.logger.warn('Locking mechanism disabled; data integrity during parallel requests not guaranteed.'); + } + + public async withReadLock( + identifier: ResourceIdentifier, + whileLocked: (maintainLock: () => void) => T | Promise, + ): Promise { + return whileLocked(noop); + } + + public async withWriteLock( + identifier: ResourceIdentifier, + whileLocked: (maintainLock: () => void) => T | Promise, + ): Promise { + return whileLocked(noop); + } +} diff --git a/test/unit/util/locking/VoidLocker.test.ts b/test/unit/util/locking/VoidLocker.test.ts new file mode 100644 index 000000000..7bb5c0885 --- /dev/null +++ b/test/unit/util/locking/VoidLocker.test.ts @@ -0,0 +1,27 @@ +import type { ResourceIdentifier } from '../../../../src'; +import { VoidLocker } from '../../../../src/util/locking/VoidLocker'; + +describe('A VoidLocker', (): void => { + it('invokes the whileLocked function immediately with readLock.', async(): Promise => { + const locker = new VoidLocker(); + const identifier: ResourceIdentifier = { path: 'http://test.com/res' }; + const whileLocked = jest.fn().mockImplementation((maintainLockFn: () => void): void => { + maintainLockFn(); + }); + + await locker.withReadLock(identifier, whileLocked); + + expect(whileLocked).toHaveBeenCalledTimes(1); + }); + + it('invokes the whileLocked function immediately with writeLock.', async(): Promise => { + const locker = new VoidLocker(); + const identifier: ResourceIdentifier = { path: 'http://test.com/res' }; + const whileLocked = jest.fn().mockImplementation((maintainLockFn: () => void): void => { + maintainLockFn(); + }); + await locker.withWriteLock(identifier, whileLocked); + + expect(whileLocked).toHaveBeenCalledTimes(1); + }); +});