zk-lock
Version:
A distributed lock using zookeeper
245 lines (244 loc) • 8.9 kB
TypeScript
/// <reference types="node" />
/// <reference types="bluebird" />
import * as Promise from 'bluebird';
import { EventEmitter } from 'events';
import { Locator } from 'locators';
import * as zk from 'node-zookeeper-client';
/**
* Error thrown by locking action when blocking wait for lock reaches a timeout period
*/
export declare class ZookeeperLockTimeoutError extends Error {
lockPath: string;
timeout?: number;
constructor(message: string, path: string, timeout?: number);
}
/**
* Error thrown by locking action when config.failImmediate == true when a lock is already locked
*/
export declare class ZookeeperLockAlreadyLockedError extends Error {
lockPath: string;
constructor(message: string, path: string);
}
export declare class ZookeeperLockConfiguration {
/**
* locators (https://github.com/metamx/locators) compatible zookeeper server locator
*/
serverLocator?: Locator;
/**
* prefix which will be placed in front of all locks created from this lock
*/
pathPrefix?: string;
/**
* zookeeper client session timeout
*/
sessionTimeout?: number;
/**
* milliseconds, dual function parameter, functioning both as zookeeper lock 'reconnect' delay
* as well as internal zookeeper client spinDelay
*/
spinDelay?: number;
/**
* milliseconds, dual function parameter, functioning both as zookeeper lock 'reconnect' limit
* as well as internal zookeeper client retries
*/
retries?: number;
/**
* when true, all calls to unlock will destroy the lock, detaching all event listeners, in addition
* to the normal disconnect. defaults to true to reduce the chance of leaky usage
*/
autoDestroyOnUnlock?: boolean;
/**
* when true, if the lock is not obtainable immediately, fail with a ZookeeperLockAlreadyLockedError and
* disconnect or destroy depending on autoDestroyOnUnlock the lock
*/
failImmediate?: boolean;
/**
* allowed number of maximum concurrent holders of a lock, defaults to 1 for traditional lock-like
* behavior. Note that this value is NOT enforced, it's merely an agreement that all lock clients
* agree to follow when working with this lock path, but allows using the zookeeper lock for additional
* cluster orchestration roles like controlling the maximum number of concurrent workers
*/
maxConcurrentHolders?: number;
/**
* if set to true, set a timeout defaulting to 10 seconds to give status updates on the lock while it
* is connected to zookeeper, used to help debug working with the locks to detect leaks or what not,
* visible by launching the app with the environment variable NODE_DEBUG=zk-lock set
*/
enableTraceLog?: boolean;
/**
* milliseconds, the rate at which debug trace logs are emitted when enableTraceLog is set to true
*/
traceLogRefresh?: number;
/**
* milliseconds, the quiet period after a lock is connected until the traceLog will begin reporting
* long held locks and suspected connection leaks in a more verbose manner
*/
traceLogQuietPeriod?: number;
}
export declare class ZookeeperLock extends EventEmitter {
static Signals: {
LOST: string;
TIMEOUT: string;
};
static States: {
ALREADY_LOCKED: string;
DESTROYED: string;
ERROR: string;
LOCKED: string;
LOCKING: string;
LOST: string;
TIMEOUT: string;
UNLOCKED: string;
UNLOCKING: string;
};
private static config;
path: string;
key: string;
client: zk.Client;
state: string;
private config;
private retryCount;
private timeout;
private created;
/**
* set static config to use by static helper methods
* @param config
*/
static initialize: (config: any) => void;
/**
* create a new lock using the static stored config
* @returns {ZookeeperLock}
*/
static lockFactory: () => ZookeeperLock;
/**
* create a new lock and lock it using the static stored config, with optional timeout
* @param key
* @param timeout
* @returns {Promise<ZookeeperLock>}
*/
static lock: (key: string, timeout?: number) => Promise<ZookeeperLock>;
/**
* check if a lock exists for a path using the static config
* @param key
* @returns {Promise<boolean>}
*/
static checkLock: (key: string) => Promise<boolean>;
/**
* get the numeric part of the lock key
* @param path
* @returns {number}
*/
private static getSequenceNumber;
/**
* create a new zk lock
* @param config
*/
constructor(config: ZookeeperLockConfiguration);
/**
* connect underlying zookeeper client, with optional delay
* @param [delay=0]
* @returns {Promise<any>}
*/
connect: (delay?: number) => Promise<any>;
/**
* disconnect zookeeper client, and remove all event listeners from it
* @returns {Promise<any>}
*/
disconnect: () => Promise<any>;
/**
* destroy the lock, disconnect and remove all listeners from the 'signal' event emitter
* @returns {Promise<any>}
*/
destroy: () => Promise<boolean>;
/**
* unlock a lock, removing the key from zookeeper, and disconnecting
* the zk client and all event listeners. By default this also destroys
* the lock and removes event listeners on the locks 'signals' event
* @param [destroy=true] - remove listeners from lock in addition
* to disconnecting zk client on completion, defaults to true
* @returns {Promise<any>}
*/
unlock: (destroy?: boolean) => Promise<any>;
/**
* wait for a lock to become free for a given key and acquire it, with an optional
* timeout upon which the lock will fail. if not currently connected to zookeeper,
* this will connect, and on timeout, the lock will disconnect from zookeeper
* @param key
* @param [timeout]
* @returns {Promise<any>}
*/
lock: (key: string, timeout?: number) => Promise<any>;
/**
* check if a lock exists, connecting to zk client if not connected
* @param key
* @returns {Promise<boolean>}
*/
checkLocked: (key: string) => Promise<boolean>;
private changeState(newState);
/**
* create a zookeeper client which powers the lock, done when creating a new lock
* or zk connection expires
* @returns {Promise<any>}
*/
private createClient();
private traceLog;
private connectHelper;
private disconnectHelper;
/**
* internal method to reconnect, wired up to disconnect event of zk client
* @returns {Promise<any>}
*/
private reconnect;
private lockHelper;
/**
* make the zk node that will hold the locks if it doens't already exist
* @param path
* @returns {Promise<any>}
*/
private makeLockDir;
/**
* create a lock as a ephemeral sequential child node of the supplied path, prefixed with 'lock-',
* to state intent to acquire a lock
* @param path
* @returns {Promise<any>}
*/
private initLock;
/**
* loop until lock is available or timeout occurs
* @param path
* @returns {Promise<any>}
*/
private waitForLock;
/**
* are we on the happy path to continue locking?
* @returns {boolean|zk.Client}
*/
private continueLocking;
/**
* check for states that result from triggers that resolve the external promise chain of the locking process.
* The zookeeper client fires all event handlers when it is disconnected, so events like timeouts, already
* locked errors, and even unlocking can cause unintended stray events, so we should just bail from these
* handlers rather than trigger unintended rejections from race conditions with the intended external rejections
* @returns {boolean}
*/
private shouldRejectPromise;
/**
* helper method that does the grunt of the work of waiting for the lock. This method does 2 things, first
* reads the lock path to compare the locks key to the other keys that are children of the path. if this locks
* sequence number is the lowest, the lock has been aquired. If not, this method reactively responds to
* children changed events from the zk-client for the path we want to aqcuire the lock for, and recurses to
* repeat this process until the sequence is the lowest
* @param resolve
* @param reject
* @param path
*/
private waitForLockHelper;
/**
* method to filter zk node children to contain only those that are prefixed with 'lock-',
* which are assumed to be created by this library
* @param children
* @returns {string[]|T[]}
*/
private filterLocks;
private checkedLockedHelper;
}