Expose locks utils
This commit is contained in:
parent
b56e8f6c15
commit
d9ffcfeb5c
2 changed files with 42 additions and 5 deletions
|
|
@ -20,3 +20,8 @@ export type { ClientCursors } from "./services/types/ClientCursors";
|
||||||
export type { NetworkConnectionStatus } from "./types/network-connection-status";
|
export type { NetworkConnectionStatus } from "./types/network-connection-status";
|
||||||
export { DocumentUpdateStatus } from "./types/document-update-status";
|
export { DocumentUpdateStatus } from "./types/document-update-status";
|
||||||
export { SyncClient } from "./sync-client";
|
export { SyncClient } from "./sync-client";
|
||||||
|
|
||||||
|
import { Locks } from "./utils/locks";
|
||||||
|
export const helpers = {
|
||||||
|
Locks
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,27 @@
|
||||||
import type { Logger } from "../tracing/logger";
|
import type { Logger } from "../tracing/logger";
|
||||||
|
|
||||||
// Manages locks on T to prevent concurrent modifications
|
/**
|
||||||
// allowing the client's FileOperations implementation to be simpler.
|
* Manages exclusive locks on items to prevent concurrent modifications.
|
||||||
// Locks are granted in a first-in-first-out order.
|
* Locks are granted in FIFO order.
|
||||||
|
*
|
||||||
|
* @template T The type of the key used for locking
|
||||||
|
*/
|
||||||
export class Locks<T> {
|
export class Locks<T> {
|
||||||
|
/** Currently locked keys */
|
||||||
private readonly locked = new Set<T>();
|
private readonly locked = new Set<T>();
|
||||||
|
|
||||||
|
/** Queue of resolve functions waiting for each key */
|
||||||
private readonly waiters = new Map<T, (() => unknown)[]>();
|
private readonly waiters = new Map<T, (() => unknown)[]>();
|
||||||
|
|
||||||
public constructor(private readonly logger: Logger) {}
|
public constructor(private readonly logger: Logger) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to acquire a lock immediately without waiting.
|
||||||
|
* Must call `unlock()` if successful.
|
||||||
|
*
|
||||||
|
* @param key The key to lock
|
||||||
|
* @returns `true` if lock acquired, `false` if already locked
|
||||||
|
*/
|
||||||
public tryLock(key: T): boolean {
|
public tryLock(key: T): boolean {
|
||||||
if (this.locked.has(key)) {
|
if (this.locked.has(key)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -19,6 +32,13 @@ export class Locks<T> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits to acquire a lock, blocking until available.
|
||||||
|
* Operations are queued in FIFO order. Must call `unlock()` when done.
|
||||||
|
*
|
||||||
|
* @param key The key to wait for and lock
|
||||||
|
* @returns Promise that resolves when lock is acquired
|
||||||
|
*/
|
||||||
public async waitForLock(key: T): Promise<void> {
|
public async waitForLock(key: T): Promise<void> {
|
||||||
if (this.tryLock(key)) {
|
if (this.tryLock(key)) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|
@ -27,6 +47,7 @@ export class Locks<T> {
|
||||||
this.logger.debug(`Waiting for lock on ${key}`);
|
this.logger.debug(`Waiting for lock on ${key}`);
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
// DefaultDict behavior
|
||||||
let waiting = this.waiters.get(key);
|
let waiting = this.waiters.get(key);
|
||||||
if (!waiting) {
|
if (!waiting) {
|
||||||
waiting = [];
|
waiting = [];
|
||||||
|
|
@ -37,12 +58,19 @@ export class Locks<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases a lock and grants access to the next waiting operation in FIFO order.
|
||||||
|
* Removes the key from locked set if no waiters.
|
||||||
|
*
|
||||||
|
* @param key The key to unlock
|
||||||
|
* @throws {Error} If key is not currently locked
|
||||||
|
*/
|
||||||
public unlock(key: T): void {
|
public unlock(key: T): void {
|
||||||
if (!this.locked.has(key)) {
|
if (!this.locked.has(key)) {
|
||||||
throw new Error(`Document ${key} is not locked, cannot unlock`);
|
throw new Error(`Key ${key} is not locked, cannot unlock`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the first element to ensure FIFO unblocking order
|
// Remove first waiter to ensure FIFO order
|
||||||
const nextWaiting = this.waiters.get(key)?.shift();
|
const nextWaiting = this.waiters.get(key)?.shift();
|
||||||
|
|
||||||
if (nextWaiting) {
|
if (nextWaiting) {
|
||||||
|
|
@ -53,6 +81,10 @@ export class Locks<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all locks and waiters. Causes waiting operations to hang indefinitely.
|
||||||
|
* Use with caution.
|
||||||
|
*/
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
this.locked.clear();
|
this.locked.clear();
|
||||||
this.waiters.clear();
|
this.waiters.clear();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue