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 { DocumentUpdateStatus } from "./types/document-update-status";
|
||||
export { SyncClient } from "./sync-client";
|
||||
|
||||
import { Locks } from "./utils/locks";
|
||||
export const helpers = {
|
||||
Locks
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,27 @@
|
|||
import type { Logger } from "../tracing/logger";
|
||||
|
||||
// Manages locks on T to prevent concurrent modifications
|
||||
// allowing the client's FileOperations implementation to be simpler.
|
||||
// Locks are granted in a first-in-first-out order.
|
||||
/**
|
||||
* Manages exclusive locks on items to prevent concurrent modifications.
|
||||
* Locks are granted in FIFO order.
|
||||
*
|
||||
* @template T The type of the key used for locking
|
||||
*/
|
||||
export class Locks<T> {
|
||||
/** Currently locked keys */
|
||||
private readonly locked = new Set<T>();
|
||||
|
||||
/** Queue of resolve functions waiting for each key */
|
||||
private readonly waiters = new Map<T, (() => unknown)[]>();
|
||||
|
||||
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 {
|
||||
if (this.locked.has(key)) {
|
||||
return false;
|
||||
|
|
@ -19,6 +32,13 @@ export class Locks<T> {
|
|||
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> {
|
||||
if (this.tryLock(key)) {
|
||||
return Promise.resolve();
|
||||
|
|
@ -27,6 +47,7 @@ export class Locks<T> {
|
|||
this.logger.debug(`Waiting for lock on ${key}`);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// DefaultDict behavior
|
||||
let waiting = this.waiters.get(key);
|
||||
if (!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 {
|
||||
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();
|
||||
|
||||
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 {
|
||||
this.locked.clear();
|
||||
this.waiters.clear();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue