UNPKG

mage-module-shard

Version:

Server-side sharding and broadcasting module for MAGE apps.

652 lines 75.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const cluster = require("cluster"); const crypto = require("crypto"); const mage = require("mage"); const { msgServer, serviceDiscovery } = mage.core; const isFunction = require('is-function-x'); const errorToObject = require('serialize-error'); const shortid = require('shortid'); /** * RemoteError class * * RemoteErrors encapsulate an error that occured * either while sending a ShardedRequest, or * while executing the request on the remote MAGE node. * * @export * @class RemoteError */ class RemoteError extends Error { constructor(data) { super(data.message); Object.assign(this, data); this.name = 'RemoteError'; } } exports.RemoteError = RemoteError; /** * ShardedRequests * * @export * @class ShardedRequest */ class ShardedRequest { constructor(id, mmrpNode, eventName, target, method, args) { this.id = id; this.mmrpNode = mmrpNode; this.eventName = eventName; this.target = target; this.method = method; this.args = args; } /** * Send the request * * Note that the sending module is responsible for listening for the * response, and passing it to this request through `resolve` or * `reject`. * * The sending module may also call `reject` should it judge a request * has timed out. * * @returns * @memberof ShardedRequest */ async send() { const Envelope = msgServer.mmrp.Envelope; const messages = [this.id, this.method, JSON.stringify(this.args)]; const source = [this.mmrpNode.clusterId, this.mmrpNode.identity]; const requestEnvelope = new Envelope(this.eventName, messages, this.target, source, 'TRACK_ROUTE'); return new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; this.mmrpNode.send(requestEnvelope, 1, (error) => { if (error) { return reject(error); } }); }); } } exports.ShardedRequest = ShardedRequest; function serializeError(data) { return JSON.stringify(errorToObject(data)); } /** * AbstractShardedModule * * This abstract module will take care of maintaining * a consistent view of the cluster, and of creating * proxy objects (or shards) which can then be used to * consistently forward requests to a server. * * See Readme.md for more details on how to use this class. * * @export * @class AbstractShardedModule */ class AbstractShardedModule { /** * Creates an instance of AbstractShardedModul * * @param {string} [name] * @memberof AbstractShardedModule */ constructor(name, hashingAlgorithm = 'md5', gcTimeoutTime = 5 * 1000) { /** * Hashing algorithm to for sharding */ this.hashingAlgorithm = 'md5'; /** * Hashes of available workers in the clusters * * A given node in the cluster may be running more than * one worker; each worker will have its own accessible address. * * @type {string[]} * @memberof AbstractShardedModule */ this.addressHashes = []; /** * Hash to MMRP address key-value map * * @type {{ [hash: string]: string[] }} * @memberof AbstractShardedModule */ this.clusterAddressMap = {}; /** * Number of nodes in the cluster * * @type {number} * @memberof AbstractShardedModule */ this.clusterSize = 0; /** * Pendng requests that are being executed on a remote MAGE node * * When requests are forwarded to a remote MAGE node, a reference to * the original request will be placed here; upon reception of * the response (an error or a value), the code execution * will then continue locally. * * Requests will timeout after a certain amount of time * * @type {Map<IShardedRequestMeta, ShardedRequest>} * @memberof AbstractShardedModule */ this.pendingRequests = new Map(); /** * Key-value for fetching the pendingRequests * map key * * This is used to allow quick by-reference access to * pending requests stored in this.pendingRequests * * ```typescript * const key = this.pendingRequestsKeyMap['some-id'] * const request = this.pendingRequests(key) * ``` * * @memberof AbstractShardedModule */ this.pendingRequestsKeyMap = {}; if (!name) { name = this.getClassName(); } this.name = name; this.hashingAlgorithm = hashingAlgorithm; this.REQUEST_EVENT_NAME = `sharded.${name}.request`; this.RESPONSE_EVENT_NAME = `sharded.${name}.response`; // Stalled requests garbage collection this.scheduleGarbageCollection(gcTimeoutTime); } /** * Setup method called by MAGE during initialization * * @param {mage.core.IState} _state * @param {(error?: Error) => void} callback * @memberof AbstractShardedModule */ async setup(_state, callback) { this.logger = mage.logger.context('ShardedModule', this.name); const { name, REQUEST_EVENT_NAME, RESPONSE_EVENT_NAME } = this; // See https://github.com/dylang/shortid#shortidworkerinteger /* istanbul ignore if */ if (cluster.isWorker) { const id = cluster.worker.id; shortid.worker(id % 16); } const mmrpNode = this.getMmrpNode(); /* istanbul ignore if */ if (!mmrpNode) { return callback(new Error('mmrpNode does not exist. Did you configure mmrp and service discovery in your config file ?')); } // Cluster communication - run module method locally, and forward the response mmrpNode.on(`delivery.${REQUEST_EVENT_NAME}`, async (requestEnvelope) => { const request = requestEnvelope.messages; const requestId = request.shift().toString(); let responseError; let responseData; try { responseData = await this.onRequest(request); } catch (e) { responseError = e; } finally { const Envelope = msgServer.mmrp.Envelope; const response = responseError ? 'false' : JSON.stringify(responseData); const error = responseError ? serializeError(responseError) : 'false'; const messages = [requestId, response, error]; const responseEnvelope = new Envelope(this.RESPONSE_EVENT_NAME, messages, requestEnvelope.returnRoute); mmrpNode.send(responseEnvelope, 1, (mmrpError) => { /* istanbul ignore if */ if (mmrpError) { this.logger.error('Error sending reply', error); } }); } }); mmrpNode.on(`delivery.${RESPONSE_EVENT_NAME}`, async (envelope) => this.onResponse(envelope.messages)); // Service information tracking const service = this.service = this.getServiceDiscovery().createService(name, 'tcp'); const address = [mmrpNode.clusterId, mmrpNode.identity]; service.on('up', (node) => this.registerNodeAddress(node)); service.on('down', (node) => this.unregisterNodeAddress(node)); service.announce(this.getPseudoPort(), address, (error) => { /* istanbul ignore if */ if (error) { return callback(error); } service.discover(); callback(); }); this.localNodeHash = this.hash(address.join('')); } /** * Teardown method called by MAGE during shutdown * * @param {mage.core.IState} _state * @param {(error?: Error) => void} callback * @memberof AbstractShardedModule */ /* istanbul ignore next */ teardown(_state, callback) { if (!this.service) { callback(); } this.service.close(callback); } /** * Retrieve a shard using a deserialized shard instance (IShard) * * @param {IShard} shard */ getShard(shard) { const { id } = shard; const address = this.clusterAddressMap[id]; const toJson = function () { return { id }; }; return new Proxy(this, { get: (target, name) => { // Return shard ID if requested if (name === 'toJSON') { return toJson; } if (name === 'id') { return id; } if (name === 'inspect') { return function () { return { id }; }; } // Only functions are made available by the proxy const val = target[name]; if (isFunction(val) !== true) { // Do not send local requests over the network if (id === this.localNodeHash) { return Promise.resolve(val); } this.assertClusterId(id); return this.addPendingRequest(address, name).send(); } // Encapsulate request return async (...args) => { if (id === this.localNodeHash) { return val.bind(this)(...args); } this.assertClusterId(id); return this.addPendingRequest(address, name, args).send(); }; }, has(_target, key) { return key === 'id'; }, ownKeys() { return ['id']; } }); } /** * Retrieve a shard using a deserialized shard instance (IShard) * * */ createBroadcast() { return new Proxy(this, { get: (target, name) => { const val = target[name]; const hashes = Object.keys(this.clusterAddressMap); const promises = []; const responses = {}; const errors = {}; const data = async () => { await Promise.all(promises); if (Object.keys(errors).length > 0) { return [errors, responses]; } else { return [null, responses]; } }; if (isFunction(val) !== true) { for (const id of hashes) { const shard = target.getShard({ id }); const promise = shard[name] .then((response) => responses[id] = response) .catch((error) => errors[id] = error); promises.push(promise); } return data(); } // Encapsulate request return async (...args) => { for (const id of hashes) { const shard = target.getShard({ id }); const promise = shard[name](...args) .then((response) => responses[id] = response) .catch((error) => errors[id] = error); promises.push(promise); } return data(); }; }, /* istanbul ignore next */ has(_target, _key) { return false; }, /* istanbul ignore next */ ownKeys() { return []; } }); } /** * Retrieve the local shard value * * Useful when you wish to make it so that future requests * be routed to the current local server. * * Note that this does NOT return a proxy; this returns just the forwardable * data of a proxy. */ getLocalShard() { return { id: this.localNodeHash }; } /** * Create a new shard from a shard key * * You will want to call this to receive the shard * reference. Then, for subsequent related calls, * you will want to make sure to use the returned * reference to forward your requests. * * @param {string} shardKey */ createShard(shardKey) { const shardId = this.getShardId(shardKey); return this.getShard(shardId); } /** * * @param {string} shardKey */ getShardId(shardKey) { const hash = this.hash(shardKey, 'buffer'); let sum = 0; // Fastest way I could find to walk through the Buffer // See: https://stackoverflow.com/a/3762735/262831 for (let i = hash.length; i--;) { sum += hash[i]; } return { id: this.addressHashes[sum % this.clusterSize] }; } /** * Hash a string using the configured algorithm * * Inspired by https://github.com/3rd-Eden/node-hashring/blob/master/index.js#L13 * * @param str */ hash(str, encoding = 'hex') { return crypto.createHash(this.hashingAlgorithm).update(str).digest(encoding); } /* istanbul ignore next */ /* tslint:disable-next-line:prefer-function-over-method */ getMmrpNode() { return msgServer.getMmrpNode(); } /* istanbul ignore next */ /* tslint:disable-next-line:prefer-function-over-method */ getServiceDiscovery() { return serviceDiscovery; } /** * Make up a fake port * * The current serviceDiscovery will consider an announced service to * be the same if the hostnames and ports are the same; this is an issue in * our case, since each workers on a single server will be announced, * and one might want to run more than one MAGE instance on a single server. * * To palliate to this issue, we pretend the PID is a port. Since PID's, depending * on the subsystem, can possibly be very large (Linux's default is 32768, but is * configurable through /proc/sys/kernel/pid_max), we use a modulo operator to limit * how big the port can be. * * 2017/07 (stelcheck): This is a huge hack, and I am aware this may break under specific * circumstances; namely, if other external services are announced on the same host, * on the same port. If you believe you are hitting this case, please let me know. */ /* istanbul ignore next */ /* tslint:disable-next-line:prefer-function-over-method */ getPseudoPort() { return (process.pid % 65535) + 1; } /** * Retrieve the current classes's name * * @returns {string} * @memberof AbstractShardedModule */ getClassName() { return this.constructor.name; } /** * Schedule garbage collection * * Note that this will cancel any previously scheduled GC, the timeout * time value passed as an argument will be used for all future GC schedulings * * @private * @param {number} gcTimeoutTime * @memberof AbstractShardedModule */ scheduleGarbageCollection(gcTimeoutTime) { if (this.gcTimer) { clearTimeout(this.gcTimer); } this.gcTimer = setTimeout(() => { // Clean up the timer reference this.gcTimer = null; const now = Date.now(); const keys = this.pendingRequests.keys(); for (const key of keys) { // All other requests are valid, cancel iteration if (key.timestamp >= now - gcTimeoutTime) { break; } // Reject the request const request = this.getAndDeletePendingRequest(key.id); request.reject(new Error('Request timed out')); } // Schedule next GC this.scheduleGarbageCollection(gcTimeoutTime); }, gcTimeoutTime); } /** * * * @private * @param {mage.core.IServiceNode} node * @memberof AbstractShardedModule */ registerNodeAddress(node) { const address = node.data; const hash = this.hash(address.join('')); if (this.clusterAddressMap[hash]) { this.logger.warning.data(node).log('Tried to re-register a known node'); return; } this.logger.notice.data(node).log('Registering new node'); // Add adress to our map // これではなかろうか this.clusterAddressMap[hash] = address; // Add hash to our list of accessible addresses this.addressHashes.push(hash); this.addressHashes.sort(); this.clusterSize += 1; } /** * * * @private * @param {mage.core.IServiceNode} node * @memberof AbstractShardedModule */ unregisterNodeAddress(node) { const address = node.data; const hash = this.hash(address.join('')); // Remove hash from list of accessible address const index = this.addressHashes.indexOf(hash); if (index === -1) { this.logger.warning.data(node).log('Tried to unregister an unknown node'); return; } this.logger.notice.data(node).log('Unegistering node'); this.addressHashes.splice(index, 1); // Remove address from map delete this.clusterAddressMap[hash]; // Reduce cluster size this.clusterSize -= 1; } /** * * * @private * @param {MmrpEnvelopeMessage[]} messages * @returns * @memberof AbstractShardedModule */ async onRequest(messages) { const rawAttributeName = messages.shift(); const rawArgs = messages.shift(); if (!rawAttributeName) { throw new Error('Method name is missing'); } const attributeName = rawAttributeName.toString(); if (!rawArgs) { return this[attributeName]; } const method = this[attributeName]; const args = JSON.parse(rawArgs.toString()); if (!method) { throw new Error(`Method is not locally available (requested method: ${attributeName})`); } return method.apply(this, args); } /** * Process a response * * @private * @param {MmrpEnvelopeMessage[]} messages * @returns * @memberof AbstractShardedModule */ async onResponse(messages) { const [requestId, rawData, rawError] = messages; const request = this.getAndDeletePendingRequest(requestId.toString()); // For void methods (no return, or undefined attributes) if (messages.length < 3) { return request.resolve(undefined); } if (rawError && rawError.toString() !== 'false') { const data = JSON.parse(rawError.toString()); const error = new RemoteError(data); return request.reject(error); } request.resolve(JSON.parse(rawData.toString())); } /** * Create a request, and add it to our list of pending requests * * The request is returned so that the calling code may * call the request's `send` method and `await` a response. * * @private * @param {string[]} target * @param {string} method * @param {any[]} args * @returns * @memberof AbstractShardedModule */ addPendingRequest(target, method, args) { const id = shortid.generate(); const timestamp = Date.now(); const key = { id, timestamp }; this.pendingRequestsKeyMap[id] = key; const request = new ShardedRequest(id, this.getMmrpNode(), this.REQUEST_EVENT_NAME, target, method, args); this.pendingRequests.set(key, request); return request; } /** * Retrieve a pending response by request ID * * @private * @param {string} id * @returns * @memberof AbstractShardedModule */ getPendingRequest(id) { const key = this.pendingRequestsKeyMap[id]; if (!key) { throw new Error(`Key not found in request key map (id: ${id})`); } const request = this.pendingRequests.get(key); if (!request) { throw new Error(`Pending request not found (id: ${key.id}, timestamp: ${key.timestamp})`); } return request; } /** * Delete a pending response * * This is normally called once a request has been completed, * or when a request has timed out. * * @private * @param {string} id * @memberof AbstractShardedModule */ deletePendingRequest(id) { const key = this.pendingRequestsKeyMap[id]; if (!key) { throw new Error(`Key not found in request key map (id: ${id})`); } delete this.pendingRequestsKeyMap[id]; this.pendingRequests.delete(key); } /** * Retrieve a request, by ID, then delete it from the * list of pending requests * * @private * @param {string} id * @returns * @memberof AbstractShardedModule */ getAndDeletePendingRequest(id) { const request = this.getPendingRequest(id); this.deletePendingRequest(id); return request; } /** * Ensure that we know this cluster ID * * @param id */ assertClusterId(id) { if (!this.clusterAddressMap[id]) { throw new RemoteError({ message: 'Remote node is no longer available' }); } } } exports.default = AbstractShardedModule; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxtQ0FBa0M7QUFDbEMsaUNBQWdDO0FBQ2hDLDZCQUE0QjtBQUU1QixNQUFNLEVBQ0osU0FBUyxFQUNULGdCQUFnQixFQUNqQixHQUFHLElBQUksQ0FBQyxJQUFJLENBQUE7QUFFYixNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsZUFBZSxDQUFDLENBQUE7QUFDM0MsTUFBTSxhQUFhLEdBQUcsT0FBTyxDQUFDLGlCQUFpQixDQUFDLENBQUE7QUFDaEQsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFBO0FBOERsQzs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFhLFdBQVksU0FBUSxLQUFLO0lBQ3BDLFlBQVksSUFBUztRQUNuQixLQUFLLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ25CLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFBO1FBQ3pCLElBQUksQ0FBQyxJQUFJLEdBQUcsYUFBYSxDQUFBO0lBQzNCLENBQUM7Q0FDRjtBQU5ELGtDQU1DO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFhLGNBQWM7SUFzRXpCLFlBQVksRUFBVSxFQUFFLFFBQWEsRUFBRSxTQUFpQixFQUFFLE1BQWdCLEVBQUUsTUFBYyxFQUFFLElBQVk7UUFDdEcsSUFBSSxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUE7UUFDWixJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQTtRQUN4QixJQUFJLENBQUMsU0FBUyxHQUFHLFNBQVMsQ0FBQTtRQUMxQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtRQUNwQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtRQUNwQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtJQUNsQixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7OztP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QUMxQyxJQUFJLENBQUMsb0JBQW9CLENBQUMsRUFBRSxDQUFDLENBQUE7UUFFN0IsT0FBTyxPQUFPLENBQUE7SUFDaEIsQ0FBQztJQUVEOzs7O09BSUc7SUFDSyxlQUFlLENBQUMsRUFBVTtRQUNoQyxJQUFJLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEVBQUUsQ0FBQyxFQUFFO1lBQy9CLE1BQU0sSUFBSSxXQUFXLENBQUM7Z0JBQ3BCLE9BQU8sRUFBRSxvQ0FBb0M7YUFDOUMsQ0FBQyxDQUFBO1NBQ0g7SUFDSCxDQUFDO0NBQ0Y7QUF6dUJELHdDQXl1QkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBjbHVzdGVyIGZyb20gJ2NsdXN0ZXInXG5pbXBvcnQgKiBhcyBjcnlwdG8gZnJvbSAnY3J5cHRvJ1xuaW1wb3J0ICogYXMgbWFnZSBmcm9tICdtYWdlJ1xuXG5jb25zdCB7XG4gIG1zZ1NlcnZlcixcbiAgc2VydmljZURpc2NvdmVyeVxufSA9IG1hZ2UuY29yZVxuXG5jb25zdCBpc0Z1bmN0aW9uID0gcmVxdWlyZSgnaXMtZnVuY3Rpb24teCcpXG5jb25zdCBlcnJvclRvT2JqZWN0ID0gcmVxdWlyZSgnc2VyaWFsaXplLWVycm9yJylcbmNvbnN0IHNob3J0aWQgPSByZXF1aXJlKCdzaG9ydGlkJylcblxuLyoqXG4gKiBUaGlzIHR5cGUgaXMgbm90IGV4cG9zZWQgYXQgdGhlIG1vbWVudCBieSBNQUdFLCBzb1xuICogd2UgaW1wb3J0ZWQgaXRzIGRlZmluaXRpb24gYmFjayBpbnRvIHRoaXMgcHJvamVjdC5cbiAqL1xudHlwZSBNbXJwRW52ZWxvcGVNZXNzYWdlID0gc3RyaW5nIHwgQnVmZmVyXG5cbi8qKlxuICogU2hhcmQgbW9kdWxlIGF0dHJpYnV0ZSBtYXBwaW5nXG4gKi9cbmV4cG9ydCB0eXBlIFNoYXJkQXR0cmlidXRlPFQ+ID1cbiAgVCBleHRlbmRzICguLi5hcmdzOiBpbmZlciBQKSA9PiBQcm9taXNlPGluZmVyIFI+ID8gKC4uLmFyZ3M6IFApID0+IFByb21pc2U8Uj4gOlxuICBUIGV4dGVuZHMgKC4uLmFyZ3M6IGluZmVyIFApID0+IGluZmVyIFIgPyAoLi4uYXJnczogUCkgPT4gUHJvbWlzZTxSPiA6XG4gIFByb21pc2U8VD5cblxuLyoqXG4gKiBTaGFyZCBwcm94eSB0eXBlIHJldHVybmVkIHdpdGggbW9kdWxlLmNyZWF0ZVNoYXJkKClcbiAqL1xuZXhwb3J0IHR5cGUgU2hhcmQ8VCBleHRlbmRzIEFic3RyYWN0U2hhcmRlZE1vZHVsZT4gPSBJU2hhcmQgJiB7XG4gIHJlYWRvbmx5IFtBIGluIGtleW9mIFRdOiBTaGFyZEF0dHJpYnV0ZTxUW0FdPlxufVxuXG4vKipcbiAqIFJlc3BvbnNlcyBmb3IgYnJvYWRjYXN0XG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSUhhc2hNYXA8VD4ge1xuICBba2V5OiBzdHJpbmddOiBUXG59XG5cbi8qKlxuICogQnJvYWRjYXN0IG1vZHVsZSBhdHRyaWJ1dGUgbWFwcGluZ1xuICovXG5leHBvcnQgdHlwZSBCcm9hZGNhc3RBdHRyaWJ1dGU8VD4gPVxuICBUIGV4dGVuZHMgKC4uLmFyZ3M6IGluZmVyIFApID0+IFByb21pc2U8aW5mZXIgUz4gPyAoLi4uYXJnczogUCkgPT4gUHJvbWlzZTxbSUhhc2hNYXA8RXJyb3I+LCBJSGFzaE1hcDxTPl0+IDpcbiAgVCBleHRlbmRzICguLi5hcmdzOiBpbmZlciBQKSA9PiBpbmZlciBSID8gKC4uLmFyZ3M6IFApID0+IFByb21pc2U8W0lIYXNoTWFwPEVycm9yPiwgSUhhc2hNYXA8Uj5dPiA6XG4gIFByb21pc2U8W0lIYXNoTWFwPEVycm9yPiwgSUhhc2hNYXA8VD5dPlxuXG4vKipcbiAqIEJyb2FkY2FzdCBwcm94eSB0eXBlIHJldHVybmVkIHdpdGggbW9kdWxlLmNyZWF0ZUJyb2FkY2FzdCgpXG4gKi9cbmV4cG9ydCB0eXBlIEJyb2FkY2FzdDxUIGV4dGVuZHMgQWJzdHJhY3RTaGFyZGVkTW9kdWxlPiA9IHtcbiAgcmVhZG9ubHkgW0EgaW4ga2V5b2YgVF06IEJyb2FkY2FzdEF0dHJpYnV0ZTxUW0FdPlxufVxuXG4vKipcbiAqIElTaGFyZGVkUmVxdWVzdE1ldGFcbiAqXG4gKiBJU2hhcmRlZFJlcXVlc3RNZXRhIGFyZSB1c2VkIGFzIGtleXMgdG8gYWNjZXNzIFNoYXJkZWRSZXF1ZXN0XG4gKiBpbnN0YW5jZXMgc3RvcmVkIGluIGEgbWFwOyB0aGV5IGNvbnRhaW4gdGhlIGluZm9ybWF0aW9uIHJlcXVpcmVkIHRvIGlkZW50aWZ5XG4gKiB0aGUgcmVsYXRlZCByZXF1ZXN0LCBhbmQgYSB0aW1lc3RhbXAgdXNlZCB0byBkZXRlcm1pbmUgd2hldGhlciBhIHJlcXVlc3RcbiAqIGhhcyB0aW1lZCBvdXQgb3Igbm90LlxuICpcbiAqIEBleHBvcnRcbiAqIEBpbnRlcmZhY2UgSVNoYXJkZWRSZXF1ZXN0TWV0YVxuICovXG5leHBvcnQgaW50ZXJmYWNlIElTaGFyZGVkUmVxdWVzdE1ldGEge1xuICBpZDogc3RyaW5nXG4gIHRpbWVzdGFtcDogbnVtYmVyXG5cbn1cblxuLyoqXG4gKiBSZW1vdGVFcnJvciBjbGFzc1xuICpcbiAqIFJlbW90ZUVycm9ycyBlbmNhcHN1bGF0ZSBhbiBlcnJvciB0aGF0IG9jY3VyZWRcbiAqIGVpdGhlciB3aGlsZSBzZW5kaW5nIGEgU2hhcmRlZFJlcXVlc3QsIG9yXG4gKiB3aGlsZSBleGVjdXRpbmcgdGhlIHJlcXVlc3Qgb24gdGhlIHJlbW90ZSBNQUdFIG5vZGUuXG4gKlxuICogQGV4cG9ydFxuICogQGNsYXNzIFJlbW90ZUVycm9yXG4gKi9cbmV4cG9ydCBjbGFzcyBSZW1vdGVFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgY29uc3RydWN0b3IoZGF0YTogYW55KSB7XG4gICAgc3VwZXIoZGF0YS5tZXNzYWdlKVxuICAgIE9iamVjdC5hc3NpZ24odGhpcywgZGF0YSlcbiAgICB0aGlzLm5hbWUgPSAnUmVtb3RlRXJyb3InXG4gIH1cbn1cblxuLyoqXG4gKiBTaGFyZGVkUmVxdWVzdHNcbiAqXG4gKiBAZXhwb3J0XG4gKiBAY2xhc3MgU2hhcmRlZFJlcXVlc3RcbiAqL1xuZXhwb3J0IGNsYXNzIFNoYXJkZWRSZXF1ZXN0IHtcbiAgLyoqXG4gICAqIFJlcXVlc3QgSURcbiAgICpcbiAgICogVGhpcyBJRCBpcyB1c2VkIGJ5IHRoZSByZW1vdGUgc2VydmVyIHdoZW4gc2VuZGluZyBiYWNrIGEgcmVzcG9uc2UsXG4gICAqIGFuZCBieSB0aGUgbG9jYWwgc2VydmVyIHRvIHJvdXRlIHRoZSByZXNwb25zZSBiYWNrIHRvIHRoZSBjb3JyZWN0IFNoYXJkZWRSZXF1ZXN0XG4gICAqIGluc3RhbmNlLlxuICAgKlxuICAgKiBAdHlwZSB7c3RyaW5nfVxuICAgKiBAbWVtYmVyb2YgU2hhcmRlZFJlcXVlc3RcbiAgICovXG4gIHB1YmxpYyBpZDogc3RyaW5nXG5cbiAgLyoqXG4gICAqIFRoZSBtbXJwTm9kZSBpbnN0YW5jZSB0byB1c2UgdG8gc2VuZCB0aGUgbWVzc2FnZVxuICAgKlxuICAgKiBAdHlwZSB7YW55fVxuICAgKiBAbWVtYmVyb2YgU2hhcmRlZFJlcXVlc3RcbiAgICovXG4gIHB1YmxpYyBtbXJwTm9kZTogYW55XG5cbiAgLyoqXG4gICAqIFRoZSBldmVudCBuYW1lXG4gICAqXG4gICAqIFNob3VsZCBub3JtYWxseSBiZSBgQWJzdHJhY3RTaGFyZGVkTW9kdWxlLlJFUVVFU1RfRVZFTlRfTkFNRWBcbiAgICogZm9yIHRoZSBjbGFzcyB3aGljaCB3aWxsIGluc3RhbmNpYXRlIGl0XG4gICAqXG4gICAqIEB0eXBlIHtzdHJpbmd9XG4gICAqIEBtZW1iZXJvZiBTaGFyZGVkUmVxdWVzdFxuICAgKi9cbiAgcHVibGljIGV2ZW50TmFtZTogc3RyaW5nXG5cbiAgLyoqXG4gICAqIFRhcmdldCBNQUdFIG5vZGVcbiAgICpcbiAgICogQHR5cGUge3N0cmluZ1tdfVxuICAgKiBAbWVtYmVyb2YgU2hhcmRlZFJlcXVlc3RcbiAgICovXG4gIHB1YmxpYyB0YXJnZXQ6IHN0cmluZ1tdXG5cbiAgLyoqXG4gICAqIE1ldGhvZCB0byBiZSBleGVjdXRlZFxuICAgKlxuICAgKiBAdHlwZSB7c3RyaW5nfVxuICAgKiBAbWVtYmVyb2YgU2hhcmRlZFJlcXVlc3RcbiAgICovXG4gIHB1YmxpYyBtZXRob2Q6IHN0cmluZ1xuXG4gIC8qKlxuICAgKiBBcmd1bWVudHMgdG8gZmVlZCB0byB0aGUgbWV0aG9kXG4gICAqXG4gICAqIEB0eXBlIHthbnlbXX1cbiAgICogQG1lbWJlcm9mIFNoYXJkZWRSZXF1ZXN0XG4gICAqL1xuICBwdWJsaWMgYXJncz86IGFueVtdXG5cbiAgLyoqXG4gICAqIFJlc29sdmUgZnVuY3Rpb24gKHNlZSBgc2VuZGApXG4gICAqXG4gICAqIEBtZW1iZXJvZiBTaGFyZGVkUmVxdWVzdFxuICAgKi9cbiAgcHVibGljIHJlc29sdmU6IChkYXRhOiBhbnkpID0+IHZvaWRcblxuICAvKipcbiAgICogUmVqZWN0IGZ1bmN0aW9uIChzZWUgYHNlbmRgKVxuICAgKlxuICAgKiBAbWVtYmVyb2YgU2hhcmRlZFJlcXVlc3RcbiAgICovXG4gIHB1YmxpYyByZWplY3Q6IChlcnJvcjogRXJyb3IpID0+IHZvaWRcblxuICBjb25zdHJ1Y3RvcihpZDogc3RyaW5nLCBtbXJwTm9kZTogYW55LCBldmVudE5hbWU6IHN0cmluZywgdGFyZ2V0OiBzdHJpbmdbXSwgbWV0aG9kOiBzdHJpbmcsIGFyZ3M/OiBhbnlbXSkge1xuICAgIHRoaXMuaWQgPSBpZFxuICAgIHRoaXMubW1ycE5vZGUgPSBtbXJwTm9kZVxuICAgIHRoaXMuZXZlbnROYW1lID0gZXZlbnROYW1lXG4gICAgdGhpcy50YXJnZXQgPSB0YXJnZXRcbiAgICB0aGlzLm1ldGhvZCA9IG1ldGhvZFxuICAgIHRoaXMuYXJncyA9IGFyZ3NcbiAgfVxuXG4gIC8qKlxuICAgKiBTZW5kIHRoZSByZXF1ZXN0XG4gICAqXG4gICAqIE5vdGUgdGhhdCB0aGUgc2VuZGluZyBtb2R1bGUgaXMgcmVzcG9uc2libGUgZm9yIGxpc3RlbmluZyBmb3IgdGhlXG4gICAqIHJlc3BvbnNlLCBhbmQgcGFzc2luZyBpdCB0byB0aGlzIHJlcXVlc3QgdGhyb3VnaCBgcmVzb2x2ZWAgb3JcbiAgICogYHJlamVjdGAuXG4gICAqXG4gICAqIFRoZSBzZW5kaW5nIG1vZHVsZSBtYXkgYWxzbyBjYWxsIGByZWplY3RgIHNob3VsZCBpdCBqdWRnZSBhIHJlcXVlc3RcbiAgICogaGFzIHRpbWVkIG91dC5cbiAgICpcbiAgICogQHJldHVybnNcbiAgICogQG1lbWJlcm9mIFNoYXJkZWRSZXF1ZXN0XG4gICAqL1xuICBwdWJsaWMgYXN5bmMgc2VuZCgpIHtcbiAgICBjb25zdCBFbnZlbG9wZSA9IG1zZ1NlcnZlci5tbXJwLkVudmVsb3BlXG4gICAgY29uc3QgbWVzc2FnZXMgPSBbdGhpcy5pZCwgdGhpcy5tZXRob2QsIEpTT04uc3RyaW5naWZ5KHRoaXMuYXJncyldXG4gICAgY29uc3Qgc291cmNlID0gW3RoaXMubW1ycE5vZGUuY2x1c3RlcklkLCB0aGlzLm1tcnBOb2RlLmlkZW50aXR5XVxuICAgIGNvbnN0IHJlcXVlc3RFbnZlbG9wZSA9IG5ldyBFbnZlbG9wZSh0aGlzLmV2ZW50TmFtZSwgbWVzc2FnZXMsIHRoaXMudGFyZ2V0LCBzb3VyY2UsICdUUkFDS19ST1VURScpXG5cbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgdGhpcy5yZXNvbHZlID0gcmVzb2x2ZVxuICAgICAgdGhpcy5yZWplY3QgPSByZWplY3RcblxuICAgICAgdGhpcy5tbXJwTm9kZS5zZW5kKHJlcXVlc3RFbnZlbG9wZSwgMSwgKGVycm9yPzogRXJyb3IpID0+IHtcbiAgICAgICAgaWYgKGVycm9yKSB7XG4gICAgICAgICAgcmV0dXJuIHJlamVjdChlcnJvcilcbiAgICAgICAgfVxuICAgICAgfSlcbiAgICB9KVxuICB9XG59XG5cbi8qKlxuICogSVNoYXJkIGludGVyZmFjZVxuICpcbiAqIFRoaXMgcmVwcmVzZW50cyB0aGUgZGF0YSBzdHJ1Y3R1cmUgb2ZcbiAqIGEgc2hhcmQuXG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgaW50ZXJmYWNlIElTaGFyZCB7XG4gIGlkOiBzdHJpbmdcbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplRXJyb3IoZGF0YTogYW55KSB7XG4gIHJldHVybiBKU09OLnN0cmluZ2lmeShlcnJvclRvT2JqZWN0KGRhdGEpKVxufVxuXG4vKipcbiAqIEFic3RyYWN0U2hhcmRlZE1vZHVsZVxuICpcbiAqIFRoaXMgYWJzdHJhY3QgbW9kdWxlIHdpbGwgdGFrZSBjYXJlIG9mIG1haW50YWluaW5nXG4gKiBhIGNvbnNpc3RlbnQgdmlldyBvZiB0aGUgY2x1c3RlciwgYW5kIG9mIGNyZWF0aW5nXG4gKiBwcm94eSBvYmplY3RzIChvciBzaGFyZHMpIHdoaWNoIGNhbiB0aGVuIGJlIHVzZWQgdG9cbiAqIGNvbnNpc3RlbnRseSBmb3J3YXJkIHJlcXVlc3RzIHRvIGEgc2VydmVyLlxuICpcbiAqIFNlZSBSZWFkbWUubWQgZm9yIG1vcmUgZGV0YWlscyBvbiBob3cgdG8gdXNlIHRoaXMgY2xhc3MuXG4gKlxuICogQGV4cG9ydFxuICogQGNsYXNzIEFic3RyYWN0U2hhcmRlZE1vZHVsZVxuICovXG5leHBvcnQgZGVmYXVsdCBhYnN0cmFjdCBjbGFzcyBBYnN0cmFjdFNoYXJkZWRNb2R1bGUge1xuICAvKipcbiAgICogU2VydmljZSBuYW1lXG4gICAqXG4gICAqIEJ5IGRlZmF1bHQsIHRoZSBzZXJ2aWNlIG5hbWUgd2lsbCBiZSB0aGUgbmFtZSBvZiB0aGUgY2xhc3NcbiAgICpcbiAgICogQHR5cGUge3N0cmluZ31cbiAgICogQG1lbWJlcm9mIEFic3RyYWN0U2hhcmRlZE1vZHVsZVxuICAgKi9cbiAgcHVibGljIG5hbWU6IHN0cmluZ1xuXG4gIC8qKlxuICAgKiBIYXNoaW5nIGFsZ29yaXRobSB0byBmb3Igc2hhcmRpbmdcbiAgICovXG4gIHByaXZhdGUgaGFzaGluZ0FsZ29yaXRobTogc3RyaW5nID0gJ21kNSdcblxuICAvKipcbiAgICogTW9kdWxlIGxvZ2dlciBpbnN0YW5jZVxuICAgKi9cbiAgcHJpdmF0ZSBsb2dnZXI6IG1hZ2UuY29yZS5JTG9nZ2VyXG5cbiAgLyoqXG4gICAqIFJlcXVlc3QgZXZlbnQgbGFiZWxcbiAgICpcbiAgICogUmVxdWVzdCBtZXNzYWdlcyBmb2xsb3cgdGhlIGZvbGxvd2luZyBmb3JtYXQ6XG4gICAqXG4gICAqIFtyZXF1ZXN0SWQsIG1ldGhvZCwgLi4uYXJnc11cbiAgICpcbiAgICogQHR5cGUge3N0cmluZ31cbiAgICogQG1lbWJlcm9mIEFic3RyYWN0U2hhcmRlZE1vZHVsZVxuICAgKi9cbiAgcHJpdmF0ZSBSRVFVRVNUX0VWRU5UX05BTUU6IHN0cmluZ1xuXG4gIC8qKlxuICAgKiBSZXNwb25zZSBldmVudCBsYWJlbFxuICAgKlxuICAgKiBSZXNwb25zZSBtZXNzYWdlcyBmb2xsb3cgdGhlIGZvbGxvd2luZyBmb3JtYXQ6XG4gICAqXG4gICAqIFtyZXF1ZXN0SWQsIHJlc3BvbnNlLCBlcnJvcl1cbiAgICpcbiAgICogQHR5cGUge3N0cmluZ31cbiAgICogQG1lbWJlcm9mIEFic3RyYWN0U2hhcmRlZE1vZHVsZVxuICAgKi9cbiAgcHJpdmF0ZSBSRVNQT05TRV9FVkVOVF9OQU1FOiBzdHJpbmdcblxuICAvKipcbiAgICogU2VydmljZSBpbnN0YW5jZVxuICAgKlxuICAgKiBAdHlwZSB7bWFnZS5jb3JlLklTZXJ2aWNlfVxuICAgKiBAbWVtYmVyb2YgQWJzdHJhY3RTaGFyZGVkTW9kdWxlXG4gICAqL1xuICBwcml2YXRlIHNlcnZpY2U6IG1hZ2UuY29yZS5JU2VydmljZVxuXG4gIC8qKlxuICAgKiBIYXNoZXMgb2YgYXZhaWxhYmxlIHdvcmtlcnMgaW4gdGhlIGNsdXN0ZXJzXG4gICAqXG4gICAqIEEgZ2l2ZW4gbm9kZSBpbiB0aGUgY2x1c3RlciBtYXkgYmUgcnVubmluZyBtb3JlIHRoYW5cbiAgICogb25lIHdvcmtlcjsgZWFjaCB3b3JrZXIgd2lsbCBoYXZlIGl0cyBvd24gYWNjZXNzaWJsZSBhZGRyZXNzLlxuICAgKlxuICAgKiBAdHlwZSB7c3RyaW5nW119XG4gICAqIEBtZW1iZXJvZiBBYnN0cmFjdFNoYXJkZWRNb2R1bGVcbiAgICovXG4gIHByaXZhdGUgYWRkcmVzc0hhc2hlczogc3RyaW5nW10gPSBbXVxuXG4gIC8qKlxuICAgKiBUaGUgYWRkcmVzcyBoYXNoIGZvciB0aGlzIGxvY2FsIHNlcnZlclxuICAgKlxuICAgKiBUaGlzIGlzIHVzZWQgZm9yIH