/// /// 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} */ static lock: (key: string, timeout?: number) => Promise; /** * check if a lock exists for a path using the static config * @param key * @returns {Promise} */ static checkLock: (key: string) => Promise; /** * 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} */ connect: (delay?: number) => Promise; /** * disconnect zookeeper client, and remove all event listeners from it * @returns {Promise} */ disconnect: () => Promise; /** * destroy the lock, disconnect and remove all listeners from the 'signal' event emitter * @returns {Promise} */ destroy: () => Promise; /** * 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} */ unlock: (destroy?: boolean) => Promise; /** * 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} */ lock: (key: string, timeout?: number) => Promise; /** * check if a lock exists, connecting to zk client if not connected * @param key * @returns {Promise} */ checkLocked: (key: string) => Promise; private changeState(newState); /** * create a zookeeper client which powers the lock, done when creating a new lock * or zk connection expires * @returns {Promise} */ private createClient(); private traceLog; private connectHelper; private disconnectHelper; /** * internal method to reconnect, wired up to disconnect event of zk client * @returns {Promise} */ private reconnect; private lockHelper; /** * make the zk node that will hold the locks if it doens't already exist * @param path * @returns {Promise} */ 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} */ private initLock; /** * loop until lock is available or timeout occurs * @param path * @returns {Promise} */ 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; }