@daiso-tech/core
Version:
The library offers flexible, framework-agnostic solutions for modern web applications, built on adaptable components that integrate seamlessly with popular frameworks like Next Js.
542 lines • 19.8 kB
JavaScript
/**
* @module SharedLock
*/
import { Task } from "../../../../task/_module-exports.js";
import { FailedAcquireWriterLockError, FailedRefreshReaderSemaphoreError, FailedRefreshWriterLockError, FailedReleaseReaderSemaphoreError, FailedReleaseWriterLockError, isSharedLockError, LimitReachedReaderSemaphoreError, SHARED_LOCK_EVENTS, SHARED_LOCK_STATE, } from "../../../../shared-lock/contracts/_module-exports.js";
import { TimeSpan } from "../../../../time-span/implementations/_module-exports.js";
import { resolveLazyable, resultFailure, resultSuccess, UnexpectedError, } from "../../../../utilities/_module-exports.js";
/**
* @internal
*/
export class SharedLock {
/**
* @internal
*/
static _internal_serialize(deserializedValue) {
return {
version: "1",
key: deserializedValue._key.get(),
limit: deserializedValue.limit,
lockId: deserializedValue.lockId,
ttlInMs: deserializedValue._ttl?.toMilliseconds() ?? null,
};
}
namespace;
adapter;
originalAdapter;
eventDispatcher;
_key;
lockId;
_ttl;
defaultBlockingInterval;
defaultBlockingTime;
defaultRefreshTime;
serdeTransformerName;
limit;
constructor(settings) {
const { namespace, adapter, originalAdapter, eventDispatcher, key, lockId, ttl, serdeTransformerName, defaultBlockingInterval, defaultBlockingTime, defaultRefreshTime, limit, } = settings;
this.limit = limit;
this.namespace = namespace;
this.originalAdapter = originalAdapter;
this.serdeTransformerName = serdeTransformerName;
this.adapter = adapter;
this.eventDispatcher = eventDispatcher;
this._key = key;
this.lockId = lockId;
this._ttl = ttl;
this.defaultBlockingInterval = defaultBlockingInterval;
this.defaultBlockingTime = defaultBlockingTime;
this.defaultRefreshTime = defaultRefreshTime;
}
_internal_getNamespace() {
return this.namespace;
}
_internal_getSerdeTransformerName() {
return this.serdeTransformerName;
}
_internal_getAdapter() {
return this.originalAdapter;
}
runReader(asyncFn) {
return new Task(async () => {
try {
const hasAquired = await this.acquireReader();
if (!hasAquired) {
return resultFailure(new LimitReachedReaderSemaphoreError(`Key "${this._key.toString()}" already acquired`));
}
return resultSuccess(await resolveLazyable(asyncFn));
}
finally {
await this.releaseReader();
}
});
}
runReaderOrFail(asyncFn) {
return new Task(async () => {
try {
await this.acquireReaderOrFail();
return await resolveLazyable(asyncFn);
}
finally {
await this.releaseReader();
}
});
}
runReaderBlocking(asyncFn, settings) {
return new Task(async () => {
try {
const hasAquired = await this.acquireReaderBlocking(settings);
if (!hasAquired) {
return resultFailure(new LimitReachedReaderSemaphoreError(`Key "${this._key.toString()}" has reached the limit ${String(this.limit)}`));
}
return resultSuccess(await resolveLazyable(asyncFn));
}
finally {
await this.releaseReader();
}
});
}
runReaderBlockingOrFail(asyncFn, settings) {
return new Task(async () => {
try {
await this.acquireReaderBlockingOrFail(settings);
return await resolveLazyable(asyncFn);
}
finally {
await this.releaseReader();
}
});
}
handleUnexpectedError = () => {
return async (args, next) => {
try {
return await next(...args);
}
catch (error) {
if (isSharedLockError(error)) {
throw error;
}
this.eventDispatcher
.dispatch(SHARED_LOCK_EVENTS.UNEXPECTED_ERROR, {
error,
sharedLock: this,
})
.detach();
throw error;
}
};
};
handleDispatch = (settings) => {
return async (args, next) => {
const result = await next(...args);
if (result && settings.on === "true") {
this.eventDispatcher
.dispatch(settings.eventName, settings.eventData)
.detach();
}
if (!result && settings.on === "false") {
this.eventDispatcher
.dispatch(settings.eventName, settings.eventData)
.detach();
}
return result;
};
};
acquireReader() {
return new Task(async () => {
return await this.adapter.acquireReader({
key: this._key.get(),
lockId: this.lockId,
limit: this.limit,
ttl: this._ttl,
});
}).pipe([
this.handleUnexpectedError(),
this.handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.READER_ACQUIRED,
eventData: {
sharedLock: this,
},
}),
this.handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.UNAVAILABLE,
eventData: {
sharedLock: this,
},
}),
]);
}
acquireReaderOrFail() {
return new Task(async () => {
const hasAquired = await this.acquireReader();
if (!hasAquired) {
throw new LimitReachedReaderSemaphoreError(`Key "${this._key.toString()}" has reached the limit`);
}
});
}
acquireReaderBlocking(settings = {}) {
return new Task(async () => {
const { time = this.defaultBlockingTime, interval = this.defaultBlockingInterval, } = settings;
const timeAsTimeSpan = TimeSpan.fromTimeSpan(time);
const endDate = timeAsTimeSpan.toEndDate();
while (endDate.getTime() > new Date().getTime()) {
const hasAquired = await this.acquireReader();
if (hasAquired) {
return true;
}
await Task.delay(interval);
}
return false;
});
}
acquireReaderBlockingOrFail(settings) {
return new Task(async () => {
const hasAquired = await this.acquireReaderBlocking(settings);
if (!hasAquired) {
throw new LimitReachedReaderSemaphoreError(`Key "${this._key.toString()}" has reached the limit`);
}
});
}
releaseReader() {
return new Task(async () => {
return await this.adapter.releaseReader(this._key.get(), this.lockId);
}).pipe([
this.handleUnexpectedError(),
this.handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.READER_RELEASED,
eventData: {
sharedLock: this,
},
}),
this.handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.READER_FAILED_RELEASE,
eventData: {
sharedLock: this,
},
}),
]);
}
releaseReaderOrFail() {
return new Task(async () => {
const hasReleased = await this.releaseReader();
if (!hasReleased) {
throw new FailedReleaseReaderSemaphoreError(`Failed to release lock "${this.lockId}" of key "${this._key.toString()}"`);
}
});
}
forceReleaseAllReaders() {
return new Task(async () => {
return await this.adapter.forceReleaseAllReaders(this._key.get());
}).pipe([
this.handleUnexpectedError(),
async (args, next) => {
const hasReleased = await next(...args);
this.eventDispatcher
.dispatch(SHARED_LOCK_EVENTS.READER_ALL_FORCE_RELEASED, {
sharedLock: this,
hasReleased,
})
.detach();
return hasReleased;
},
]);
}
refreshReader(ttl = this.defaultRefreshTime) {
return new Task(async () => {
return await this.adapter.refreshReader(this._key.get(), this.lockId, TimeSpan.fromTimeSpan(ttl));
}).pipe([
this.handleUnexpectedError(),
this.handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.READER_REFRESHED,
eventData: {
sharedLock: this,
},
}),
this.handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.READER_FAILED_REFRESH,
eventData: {
sharedLock: this,
},
}),
async (args, next) => {
const hasRefreshed = await next(...args);
this._ttl = TimeSpan.fromTimeSpan(ttl);
return hasRefreshed;
},
]);
}
refreshReaderOrFail(ttl) {
return new Task(async () => {
const hasRefreshed = await this.refreshReader(ttl);
if (!hasRefreshed) {
throw new FailedRefreshReaderSemaphoreError(`Failed to refresh lock "${this.lockId}" of key "${this._key.toString()}"`);
}
});
}
runWriter(asyncFn) {
return new Task(async () => {
try {
const hasAquired = await this.acquireWriter();
if (!hasAquired) {
return resultFailure(new FailedAcquireWriterLockError(`Key "${this._key.toString()}" already acquired`));
}
return resultSuccess(await resolveLazyable(asyncFn));
}
finally {
await this.releaseWriter();
}
});
}
runWriterOrFail(asyncFn) {
return new Task(async () => {
try {
await this.acquireWriterOrFail();
return await resolveLazyable(asyncFn);
}
finally {
await this.releaseWriter();
}
});
}
runWriterBlocking(asyncFn, settings) {
return new Task(async () => {
try {
const hasAquired = await this.acquireWriterBlocking(settings);
if (!hasAquired) {
return resultFailure(new FailedAcquireWriterLockError(`Key "${this._key.toString()}" already acquired`));
}
return resultSuccess(await resolveLazyable(asyncFn));
}
finally {
await this.releaseWriter();
}
});
}
runWriterBlockingOrFail(asyncFn, settings) {
return new Task(async () => {
try {
await this.acquireWriterBlockingOrFail(settings);
return await resolveLazyable(asyncFn);
}
finally {
await this.releaseWriter();
}
});
}
acquireWriter() {
return new Task(async () => {
return await this.adapter.acquireWriter(this._key.get(), this.lockId, this._ttl);
}).pipe([
this.handleUnexpectedError(),
this.handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.WRITER_ACQUIRED,
eventData: {
sharedLock: this,
},
}),
this.handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.UNAVAILABLE,
eventData: {
sharedLock: this,
},
}),
]);
}
acquireWriterOrFail() {
return new Task(async () => {
const hasAquired = await this.acquireWriter();
if (!hasAquired) {
throw new FailedAcquireWriterLockError(`Key "${this._key.toString()}" already acquired`);
}
});
}
acquireWriterBlocking(settings = {}) {
return new Task(async () => {
const { time = this.defaultBlockingTime, interval = this.defaultBlockingInterval, } = settings;
const timeAsTimeSpan = TimeSpan.fromTimeSpan(time);
const endDate = timeAsTimeSpan.toEndDate();
while (endDate > new Date()) {
const hasAquired = await this.acquireWriter();
if (hasAquired) {
return true;
}
await Task.delay(interval);
}
return false;
});
}
acquireWriterBlockingOrFail(settings) {
return new Task(async () => {
const hasAquired = await this.acquireWriterBlocking(settings);
if (!hasAquired) {
throw new FailedAcquireWriterLockError(`Key "${this._key.toString()}" already acquired`);
}
});
}
releaseWriter() {
return new Task(async () => {
return await this.adapter.releaseWriter(this._key.get(), this.lockId);
}).pipe([
this.handleUnexpectedError(),
this.handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.WRITER_RELEASED,
eventData: {
sharedLock: this,
},
}),
this.handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.WRITER_FAILED_RELEASE,
eventData: {
sharedLock: this,
},
}),
]);
}
releaseWriterOrFail() {
return new Task(async () => {
const hasRelased = await this.releaseWriter();
if (!hasRelased) {
throw new FailedReleaseWriterLockError(`Unonwed release on key "${this._key.toString()}" by owner "${this.lockId}"`);
}
});
}
forceReleaseWriter() {
return new Task(async () => {
return await this.adapter.forceReleaseWriter(this._key.get());
}).pipe([
this.handleUnexpectedError(),
async (args, next) => {
const hasReleased = await next(...args);
this.eventDispatcher
.dispatch(SHARED_LOCK_EVENTS.WRITER_FORCE_RELEASED, {
sharedLock: this,
hasReleased,
})
.detach();
return hasReleased;
},
]);
}
refreshWriter(ttl = this.defaultRefreshTime) {
return new Task(async () => {
return await this.adapter.refreshWriter(this._key.get(), this.lockId, TimeSpan.fromTimeSpan(ttl));
}).pipe([
this.handleUnexpectedError(),
this.handleDispatch({
on: "true",
eventName: SHARED_LOCK_EVENTS.WRITER_REFRESHED,
eventData: {
sharedLock: this,
},
}),
this.handleDispatch({
on: "false",
eventName: SHARED_LOCK_EVENTS.WRITER_FAILED_REFRESH,
eventData: {
sharedLock: this,
},
}),
async (args, next) => {
const hasRefreshed = await next(...args);
if (hasRefreshed) {
this._ttl = TimeSpan.fromTimeSpan(ttl);
}
return hasRefreshed;
},
]);
}
refreshWriterOrFail(ttl) {
return new Task(async () => {
const hasRefreshed = await this.refreshWriter(ttl);
if (!hasRefreshed) {
throw new FailedRefreshWriterLockError(`Unonwed refresh on key "${this._key.toString()}" by owner "${this.lockId}"`);
}
});
}
get key() {
return this._key.toString();
}
get id() {
return this.lockId;
}
get ttl() {
return this._ttl;
}
forceRelease() {
return new Task(async () => {
return await this.adapter.forceRelease(this._key.get());
}).pipe([this.handleUnexpectedError()]);
}
getState() {
return new Task(async () => {
const state = await this.adapter.getState(this._key.get());
if (state === null) {
return {
type: SHARED_LOCK_STATE.EXPIRED,
};
}
if (state.writer && state.writer.owner === this.lockId) {
return {
type: SHARED_LOCK_STATE.WRITER_ACQUIRED,
remainingTime: state.writer.expiration === null
? null
: TimeSpan.fromDateRange({
start: new Date(),
end: state.writer.expiration,
}),
};
}
if (state.writer && state.writer.owner !== this.lockId) {
return {
type: SHARED_LOCK_STATE.WRITER_UNAVAILABLE,
owner: state.writer.owner,
};
}
if (state.reader !== null &&
state.reader.acquiredSlots.size >= state.reader.limit) {
return {
type: SHARED_LOCK_EVENTS.READER_LIMIT_REACHED,
limit: state.reader.limit,
acquiredSlots: [...state.reader.acquiredSlots.keys()],
};
}
const slotExpiration = state.reader?.acquiredSlots.get(this.lockId);
if (state.reader !== null && slotExpiration === undefined) {
return {
type: SHARED_LOCK_STATE.READER_UNACQUIRED,
limit: state.reader.limit,
freeSlotsCount: state.reader.limit - state.reader.acquiredSlots.size,
acquiredSlotsCount: state.reader.acquiredSlots.size,
acquiredSlots: [...state.reader.acquiredSlots.keys()],
};
}
if (state.reader !== null && slotExpiration !== undefined) {
return {
type: SHARED_LOCK_STATE.READER_ACQUIRED,
acquiredSlots: [...state.reader.acquiredSlots.keys()],
acquiredSlotsCount: state.reader.acquiredSlots.size,
freeSlotsCount: state.reader.limit - state.reader.acquiredSlots.size,
limit: state.reader.limit,
remainingTime: slotExpiration === null
? null
: TimeSpan.fromDateRange({
start: new Date(),
end: slotExpiration,
}),
};
}
throw new UnexpectedError("Invalid ISharedLockAdapterState, expected either the reader field must be defined or the writer field must be defined, but not both.");
}).pipe(this.handleUnexpectedError());
}
}
//# sourceMappingURL=shared-lock.js.map