Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
havelessbemore committed May 10, 2024
1 parent 30e42fb commit e51c085
Show file tree
Hide file tree
Showing 32 changed files with 1,730 additions and 2,413 deletions.
979 changes: 376 additions & 603 deletions dist/semafy.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/semafy.cjs.map

Large diffs are not rendered by default.

731 changes: 344 additions & 387 deletions dist/semafy.d.ts

Large diffs are not rendered by default.

969 changes: 369 additions & 600 deletions dist/semafy.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/semafy.mjs.map

Large diffs are not rendered by default.

89 changes: 40 additions & 49 deletions src/conditionVariable.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,32 @@
import { ATOMICS_NOT_EQUAL, ATOMICS_TIMED_OUT } from "./types/atomicsStatus";
import type { BasicLockable } from "./types/basicLockable";
import { type CVStatus, CV_OK, CV_TIMED_OUT } from "./types/cvStatus";
import type { SharedResource } from "./types/sharedResource";

import { ERR_CV_VALUE } from "./errors/constants";
import { MutexOwnershipError } from "./errors/mutexOwnershipError";

import { Mutex } from "./mutex";
import { OwnershipError } from "./errors/ownershipError";

/**
* Represents a condition variable similar to those used in C++.
*
* A condition variable manages an atomic wait/block mechanism that
* is tightly coupled with a mutex for safe cross-agent synchronization.
*
* Behavior is undefined if:
* - The shared memory location is modified externally.
*
* @privateRemarks
* 1. {@link https://en.cppreference.com/w/cpp/thread/condition_variable | C++ std::condition_variable}
* 1. {@link https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html | Alexander Terekhov, Howard Hinnant. (2007-09-09). Mutex, Lock, Condition Variable Rationale}
*/
export class ConditionVariable {
export class ConditionVariable implements SharedResource {
/**
* The shared atomic memory where the condition variable stores its state.
*/
private _mem: Int32Array;

/**
* Creates a new instance of ConditionVariable.
*/
constructor();
/**
* Creates a new instance of ConditionVariable.
*
* @param sharedBuffer The {@link SharedArrayBuffer} that backs the condition variable.
* @param byteOffset The byte offset within `sharedBuffer`. Defaults to `0`.
*
* Note: The shared memory location should not be modified outside
* of this condition variable. Doing so may cause errors.
*/
constructor(sharedBuffer: SharedArrayBuffer, byteOffset?: number);
constructor(sharedBuffer?: SharedArrayBuffer, byteOffset = 0) {
Expand All @@ -47,80 +40,78 @@ export class ConditionVariable {
Atomics.store(this._mem, 0, 0);
}

/**
* Gets the underlying shared buffer.
*/
get buffer(): SharedArrayBuffer {
return this._mem.buffer as SharedArrayBuffer;
}

/**
* Gets the byte offset in the underlying shared buffer.
*/
get byteLength(): number {
return this._mem.byteLength;
}

get byteOffset(): number {
return this._mem.byteOffset;
}

/**
* Notify waiting workers that are blocked on this condition variable.
* Notify waiting agents that are blocked on this condition variable.
*
* @param count - The number of workers to notify.
* @param count - The number of agents to notify.
*
* @returns The number of workers that were woken up.
* @returns The number of agents that were notified.
*/
notify(count: number): number {
return Atomics.notify(this._mem, 0, count);
}

/**
* Notify all waiting workers that are blocked on this condition variable.
* Notify all waiting agents that are blocked on this condition variable.
*
* @returns The number of workers that were woken up.
* @returns The number of agents that were notified.
*/
notifyAll(): number {
return Atomics.notify(this._mem, 0);
}

/**
* Notify one waiting worker that is blocked on this condition variable.
* Notify one waiting agent that is blocked on this condition variable.
*
* @returns The number of workers that were woken up.
* @returns The number of agents that were notified.
*/
notifyOne(): number {
return Atomics.notify(this._mem, 0, 1);
}

/**
* Blocks the current worker until this condition variable is notified,
* or an optional timeout expires. The associated mutex is atomically
* released before blocking and re-acquired after waking up.
* Blocks the current agent until this condition variable is notified.
* The associated mutex is released before blocking and re-acquired
* after waking up.
*
* @param mutex The {@link Mutex} that must be locked by the current agent.
* @param mutex The mutex that must be locked by the current agent.
*
* @throws A {@link MutexOwnershipError} If the mutex is not owned by the caller.
* @throws A {@link RangeError} If the condition variable's internal value is not expected.
* @throws An {@link OwnershipError} If the mutex is not owned by the caller.
* @throws A {@link RangeError} If the shared memory data is unexpected.
*/
async wait(mutex: Mutex): Promise<void> {
async wait(mutex: BasicLockable): Promise<void> {
await this.waitFor(mutex, Infinity);
}

/**
* Blocks the current worker until this condition variable is notified,
* or an optional timeout expires. The associated mutex is atomically
* released before blocking and re-acquired after waking up.
* Blocks the current agent until this condition variable is notified,
* or an optional timeout expires. The associated mutex is released
* before blocking and re-acquired after waking up.
*
* @param mutex The mutex that must be locked by the current agent.
* @param timeout An optional timeout in milliseconds after which the wait is aborted.
* @param timeout A timeout in milliseconds after which the wait is aborted.
*
* @throws A {@link MutexOwnershipError} If the mutex is not owned by the caller.
* @throws A {@link RangeError} If the condition variable's internal value is not expected.
* @throws An {@link OwnershipError} If the mutex is not owned by the caller.
* @throws A {@link RangeError} If the shared memory data is unexpected.
*
* @returns A {@link CVStatus} representing the result of the operation.
*/
async waitFor(mutex: Mutex, timeout: number): Promise<CVStatus> {
async waitFor(mutex: BasicLockable, timeout: number): Promise<CVStatus> {
// Check mutex is owned
if (!mutex.ownsLock) {
throw new MutexOwnershipError();
throw new OwnershipError();
}
try {
// Start waiting BEFORE releasing mutex
Expand All @@ -143,18 +134,18 @@ export class ConditionVariable {

/**
* Blocks the current agent until this condition variable is notified,
* or until a specified point in time is reached. This is a convenience
* method for waiting within a deadline.
* or until a specified point in time is reached. The associated mutex
* is released before blocking and re-acquired after waking up.
*
* @param mutex The {@link Mutex} that must be locked by the current agent.
* @param timestamp The absolute time (in milliseconds) at which to stop waiting.
* @param mutex The mutex that must be locked by the current agent.
* @param timestamp The absolute time in milliseconds at which the wait is aborted.
*
* @throws A {@link MutexOwnershipError} If the mutex is not owned by the caller.
* @throws A {@link RangeError} If the condition variable's internal value is not expected.
* @throws A {@link OwnershipError} If the mutex is not owned by the caller.
* @throws A {@link RangeError} If the shared memory data is unexpected.
*
* @returns A {@link CVStatus} representing the result of the operation.
*/
async waitUntil(mutex: Mutex, timestamp: number): Promise<CVStatus> {
async waitUntil(mutex: BasicLockable, timestamp: number): Promise<CVStatus> {
return this.waitFor(mutex, timestamp - performance.now());
}
}
12 changes: 6 additions & 6 deletions src/errors/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ export const ERR_TIMEOUT = "Operation timed out.";
export const ERR_CV_VALUE = "Unexpected value in shared memory location";

// Mutex
export const ERR_MUTEX = "Mutex has encountered an error.";
export const ERR_LOCK = "A lock has encountered an error.";

export const ERR_MUTEX_OWNERSHIP =
"Operation not permitted. Mutex must be acquired first.";
export const ERR_LOCK_OWNERSHIP =
"Operation not permitted. Lock must be acquired first.";

export const ERR_MUTEX_RELOCK =
"Attempted relock of already acquired mutex. Deadlock would occur.";
export const ERR_LOCK_RELOCK =
"Attempted relock of already acquired lock. Deadlock would occur.";

export const ERR_MUTEX_TIMEOUT = "Timed out acquiring mutex.";
export const ERR_LOCK_TIMEOUT = "Timed out acquiring lock.";

// Recursive Mutex

Expand Down
10 changes: 4 additions & 6 deletions src/errors/mutexError.ts → src/errors/lockError.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { ERR_MUTEX } from "./constants";
import { ERR_LOCK } from "./constants";

/**
* Represents a generic error originating from a mutex.
* Represents a generic error originating from a lock.
*/
export class MutexError extends Error {
export class LockError extends Error {
/**
* Creates a new `MutexError`.
*
* @param message - An optional custom error message.
*/
constructor(message?: string) {
super(message ?? ERR_MUTEX);
super(message ?? ERR_LOCK);
this.name = this.constructor.name;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
Expand Down
16 changes: 0 additions & 16 deletions src/errors/mutexOwnershipError.ts

This file was deleted.

16 changes: 0 additions & 16 deletions src/errors/mutexRelockError.ts

This file was deleted.

14 changes: 14 additions & 0 deletions src/errors/ownershipError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ERR_LOCK_OWNERSHIP } from "./constants";
import { LockError } from "./lockError";

/**
* Represents an ownership error originating from a lock.
*/
export class OwnershipError extends LockError {
/**
* @param message - An optional custom error message.
*/
constructor(message?: string) {
super(message ?? ERR_LOCK_OWNERSHIP);
}
}
14 changes: 14 additions & 0 deletions src/errors/relockError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ERR_LOCK_RELOCK } from "./constants";
import { LockError } from "./lockError";

/**
* Represents an error relocking a lock.
*/
export class RelockError extends LockError {
/**
* @param message - An optional custom error message.
*/
constructor(message?: string) {
super(message ?? ERR_LOCK_RELOCK);
}
}
4 changes: 1 addition & 3 deletions src/errors/timeoutError.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ERR_TIMEOUT } from "./constants";

/**
* Represents an error that occurs when a process exceeds a set timeout.
* Represents an error that occurs when a process exceeds a set time.
*/
export class TimeoutError extends Error {
/**
Expand All @@ -17,8 +17,6 @@ export class TimeoutError extends Error {
timeout?: number;

/**
* Create a new `TimeoutError`.
*
* @param message - A custom error message. Defaults to `undefined`.
* @param timeout - The timeout duration in milliseconds. Defaults to `undefined`.
* @param deadline - The absolute time in milliseconds. Defaults to `undefined`.
Expand Down
37 changes: 24 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
// Types
export { BasicLockable } from "./types/basicLockable";
export type { BasicLockable } from "./types/basicLockable";
export { type CVStatus, CV_OK, CV_TIMED_OUT } from "./types/cvStatus";
export { Lockable } from "./types/lockable";
export { SharedLockable } from "./types/sharedLockable";
export { SharedTimedLockable } from "./types/sharedTimedLockable";
export { TimedLockable } from "./types/timedLockable";
export type { Lockable } from "./types/lockable";
export type { SharedLockable } from "./types/sharedLockable";
export type { SharedResource } from "./types/sharedResource";
export type { SharedTimedLockable } from "./types/sharedTimedLockable";
export type { TimedLockable } from "./types/timedLockable";

// Errors
export { MutexError } from "./errors/mutexError";
export { MutexOwnershipError } from "./errors/mutexOwnershipError";
export { MutexRelockError } from "./errors/mutexRelockError";
export { LockError } from "./errors/lockError";
export { OwnershipError } from "./errors/ownershipError";
export { RelockError } from "./errors/relockError";
export { TimeoutError } from "./errors/timeoutError";

// Core
// Mutex
export { Mutex } from "./mutex/mutex";
export { RecursiveMutex } from "./mutex/recursiveMutex";
export { RecursiveTimedMutex } from "./mutex/recursiveTimedMutex";
export { SharedMutex } from "./mutex/sharedMutex";
export { TimedMutex } from "./mutex/timedMutex";

// Mutex Management
export { lockGuard } from "./utils/lockGuard";
export { SharedLock } from "./utils/sharedLock";

// Condition Variables
export { ConditionVariable } from "./conditionVariable";
export { Mutex } from "./mutex";
export { RecursiveMutex } from "./recursiveMutex";
export { Semaphore } from "./semaphore";
export { SharedMutex } from "./sharedMutex";

// Semaphores
export { CountingSemaphore } from "./semaphore/countingSemaphore";
Loading

0 comments on commit e51c085

Please sign in to comment.