@dollhousemcp/mcp-server
Version:
DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.
187 lines • 23.5 kB
JavaScript
import { logger } from '../utils/logger.js';
import * as fs from 'fs/promises';
import * as path from 'path';
import { randomBytes } from 'crypto';
/**
* FileLockManager - Prevents race conditions in concurrent file operations
*
* Features:
* - Resource-based locking with automatic cleanup
* - Configurable timeouts to prevent deadlocks
* - Atomic file operations with write-rename pattern
* - Lock queueing for concurrent requests
* - Comprehensive error handling and logging
* - Performance metrics tracking
*/
export class FileLockManager {
// Map of resource identifiers to their lock promises
static locks = new Map();
// Lock acquisition metrics for monitoring
static metrics = {
totalLockRequests: 0,
lockWaitTime: new Map(),
lockTimeouts: 0,
concurrentWaits: 0
};
// Default timeout for lock operations (10 seconds)
static DEFAULT_TIMEOUT_MS = 10000;
// Temporary file directory
static TEMP_DIR = '.tmp';
/**
* Execute an operation with exclusive lock on a resource
* @param resource - Unique identifier for the resource (e.g., 'persona:name')
* @param operation - Async function to execute while holding the lock
* @param options - Lock options including timeout
* @returns Result of the operation
*/
static async withLock(resource, operation, options = {}) {
const startTime = Date.now();
this.metrics.totalLockRequests++;
logger.debug(`Lock requested for resource: ${resource}`);
// Wait for any existing operation on this resource
const existingLock = this.locks.get(resource);
if (existingLock) {
this.metrics.concurrentWaits++;
logger.debug(`Waiting for existing lock on: ${resource}`);
try {
await existingLock;
}
catch (error) {
// Previous operation failed, but we can proceed
logger.debug(`Previous operation on ${resource} failed, proceeding`);
}
}
// Create new lock for this operation
const timeout = options.timeout || this.DEFAULT_TIMEOUT_MS;
const lockPromise = this.executeWithTimeout(operation, timeout, resource);
this.locks.set(resource, lockPromise);
try {
const result = await lockPromise;
// Record metrics
const waitTime = Date.now() - startTime;
if (!this.metrics.lockWaitTime.has(resource)) {
this.metrics.lockWaitTime.set(resource, []);
}
this.metrics.lockWaitTime.get(resource).push(waitTime);
logger.debug(`Lock released for resource: ${resource} (${waitTime}ms)`);
return result;
}
finally {
// Clean up lock atomically - compare and delete in one operation
const currentLock = this.locks.get(resource);
if (currentLock === lockPromise) {
this.locks.delete(resource);
logger.debug(`Lock queue cleaned up for resource: ${resource}`);
}
}
}
/**
* Execute operation with timeout protection
*/
static async executeWithTimeout(operation, timeoutMs, resource) {
let timeoutHandle;
const timeoutPromise = new Promise((_, reject) => {
timeoutHandle = setTimeout(() => {
this.metrics.lockTimeouts++;
reject(new Error(`Lock operation timeout for resource: ${resource}`));
}, timeoutMs);
});
try {
const result = await Promise.race([operation(), timeoutPromise]);
if (timeoutHandle)
clearTimeout(timeoutHandle);
return result;
}
catch (error) {
if (timeoutHandle)
clearTimeout(timeoutHandle);
throw error;
}
}
/**
* Perform atomic file write operation
* Writes to temporary file then renames to ensure atomicity
*/
static async atomicWriteFile(filePath, content, options) {
const tempPath = await this.getTempFilePath(filePath);
const dir = path.dirname(tempPath);
try {
// Ensure temp directory exists
await fs.mkdir(dir, { recursive: true });
// Write to temporary file
await fs.writeFile(tempPath, content, options);
// Atomic rename (on same filesystem)
await fs.rename(tempPath, filePath);
logger.debug(`Atomic write completed: ${filePath}`);
}
catch (error) {
// Clean up temp file on error
try {
await fs.unlink(tempPath);
logger.debug(`Cleaned up temp file after error: ${tempPath}`);
}
catch (unlinkError) {
// Log cleanup failure but don't throw - original error is more important
logger.warn(`Failed to clean up temp file ${tempPath}: ${unlinkError}`);
}
throw error;
}
}
/**
* Perform atomic file read with lock
*/
static async atomicReadFile(filePath, options) {
return this.withLock(`file:${filePath}`, async () => {
const content = await fs.readFile(filePath, options);
return content.toString();
});
}
/**
* Generate temporary file path for atomic operations
*/
static async getTempFilePath(originalPath) {
const dir = path.dirname(originalPath);
const basename = path.basename(originalPath);
const random = randomBytes(8).toString('hex');
return path.join(dir, this.TEMP_DIR, `${basename}.${random}.tmp`);
}
/**
* Get lock metrics for monitoring
*/
static getMetrics() {
const avgWaitTimes = new Map();
for (const [resource, times] of this.metrics.lockWaitTime.entries()) {
if (times.length > 0) {
const avg = times.reduce((a, b) => a + b, 0) / times.length;
avgWaitTimes.set(resource, Math.round(avg));
}
}
return {
totalRequests: this.metrics.totalLockRequests,
activeLocksCount: this.locks.size,
timeouts: this.metrics.lockTimeouts,
concurrentWaits: this.metrics.concurrentWaits,
avgWaitTimeByResource: Object.fromEntries(avgWaitTimes),
activeLocks: Array.from(this.locks.keys())
};
}
/**
* Clear all locks (use with caution - mainly for testing)
*/
static clearAllLocks() {
this.locks.clear();
logger.warn('All file locks cleared - use only for testing/recovery');
}
/**
* Reset metrics
*/
static resetMetrics() {
this.metrics = {
totalLockRequests: 0,
lockWaitTime: new Map(),
lockTimeouts: 0,
concurrentWaits: 0
};
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsZUxvY2tNYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlY3VyaXR5L2ZpbGVMb2NrTWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDNUMsT0FBTyxLQUFLLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDbEMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUVyQzs7Ozs7Ozs7OztHQVVHO0FBQ0gsTUFBTSxPQUFPLGVBQWU7SUFDMUIscURBQXFEO0lBQzdDLE1BQU0sQ0FBQyxLQUFLLEdBQUcsSUFBSSxHQUFHLEVBQXdCLENBQUM7SUFFdkQsMENBQTBDO0lBQ2xDLE1BQU0sQ0FBQyxPQUFPLEdBQUc7UUFDdkIsaUJBQWlCLEVBQUUsQ0FBQztRQUNwQixZQUFZLEVBQUUsSUFBSSxHQUFHLEVBQW9CO1FBQ3pDLFlBQVksRUFBRSxDQUFDO1FBQ2YsZUFBZSxFQUFFLENBQUM7S0FDbkIsQ0FBQztJQUVGLG1EQUFtRDtJQUMzQyxNQUFNLENBQVUsa0JBQWtCLEdBQUcsS0FBSyxDQUFDO0lBRW5ELDJCQUEyQjtJQUNuQixNQUFNLENBQVUsUUFBUSxHQUFHLE1BQU0sQ0FBQztJQUUxQzs7Ozs7O09BTUc7SUFDSCxNQUFNLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FDbkIsUUFBZ0IsRUFDaEIsU0FBMkIsRUFDM0IsVUFBZ0MsRUFBRTtRQUVsQyxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDN0IsSUFBSSxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBRWpDLE1BQU0sQ0FBQyxLQUFLLENBQUMsZ0NBQWdDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFFekQsbURBQW1EO1FBQ25ELE1BQU0sWUFBWSxHQUFHLElBQUks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QUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUMsR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDO2dCQUM1RCxZQUFZLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7WUFDOUMsQ0FBQztRQUNILENBQUM7UUFFRCxPQUFPO1lBQ0wsYUFBYSxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsaUJBQWlCO1lBQzdDLGdCQUFnQixFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSTtZQUNqQyxRQUFRLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxZQUFZO1lBQ25DLGVBQWUsRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLGVBQWU7WUFDN0MscUJBQXFCLEVBQUUsTUFBTSxDQUFDLFdBQVcsQ0FBQyxZQUFZLENBQUM7WUFDdkQsV0FBVyxFQUFFLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztTQUMzQyxDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0gsTUFBTSxDQUFDLGFBQWE7UUFDbEIsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNuQixNQUFNLENBQUMsSUFBSSxDQUFDLHdEQUF3RCxDQUFDLENBQUM7SUFDeEUsQ0FBQztJQUVEOztPQUVHO0lBQ0gsTUFBTSxDQUFDLFlBQVk7UUFDakIsSUFBSSxDQUFDLE9BQU8sR0FBRztZQUNiLGlCQUFpQixFQUFFLENBQUM7WUFDcEIsWUFBWSxFQUFFLElBQUksR0FBRyxFQUFvQjtZQUN6QyxZQUFZLEVBQUUsQ0FBQztZQUNmLGVBQWUsRUFBRSxDQUFDO1NBQ25CLENBQUM7SUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCAqIGFzIGZzIGZyb20gJ2ZzL3Byb21pc2VzJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgeyByYW5kb21CeXRlcyB9IGZyb20gJ2NyeXB0byc7XG5cbi8qKlxuICogRmlsZUxvY2tNYW5hZ2VyIC0gUHJldmVudHMgcmFjZSBjb25kaXRpb25zIGluIGNvbmN1cnJlbnQgZmlsZSBvcGVyYXRpb25zXG4gKiBcbiAqIEZlYXR1cmVzOlxuICogLSBSZXNvdXJjZS1iYXNlZCBsb2NraW5nIHdpdGggYXV0b21hdGljIGNsZWFudXBcbiAqIC0gQ29uZmlndXJhYmxlIHRpbWVvdXRzIHRvIHByZXZlbnQgZGVhZGxvY2tzXG4gKiAtIEF0b21pYyBmaWxlIG9wZXJhdGlvbnMgd2l0aCB3cml0ZS1yZW5hbWUgcGF0dGVyblxuICogLSBMb2NrIHF1ZXVlaW5nIGZvciBjb25jdXJyZW50IHJlcXVlc3RzXG4gKiAtIENvbXByZWhlbnNpdmUgZXJyb3IgaGFuZGxpbmcgYW5kIGxvZ2dpbmdcbiAqIC0gUGVyZm9ybWFuY2UgbWV0cmljcyB0cmFja2luZ1xuICovXG5leHBvcnQgY2xhc3MgRmlsZUxvY2tNYW5hZ2VyIHtcbiAgLy8gTWFwIG9mIHJlc291cmNlIGlkZW50aWZpZXJzIHRvIHRoZWlyIGxvY2sgcHJvbWlzZXNcbiAgcHJpdmF0ZSBzdGF0aWMgbG9ja3MgPSBuZXcgTWFwPHN0cmluZywgUHJvbWlzZTxhbnk+PigpO1xuICBcbiAgLy8gTG9jayBhY3F1aXNpdGlvbiBtZXRyaWNzIGZvciBtb25pdG9yaW5nXG4gIHByaXZhdGUgc3RhdGljIG1ldHJpY3MgPSB7XG4gICAgdG90YWxMb2NrUmVxdWVzdHM6IDAsXG4gICAgbG9ja1dhaXRUaW1lOiBuZXcgTWFwPHN0cmluZywgbnVtYmVyW10+KCksXG4gICAgbG9ja1RpbWVvdXRzOiAwLFxuICAgIGNvbmN1cnJlbnRXYWl0czogMFxuICB9O1xuXG4gIC8vIERlZmF1bHQgdGltZW91dCBmb3IgbG9jayBvcGVyYXRpb25zICgxMCBzZWNvbmRzKVxuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBERUZBVUxUX1RJTUVPVVRfTVMgPSAxMDAwMDtcbiAgXG4gIC8vIFRlbXBvcmFyeSBmaWxlIGRpcmVjdG9yeVxuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBURU1QX0RJUiA9ICcudG1wJztcblxuICAvKipcbiAgICogRXhlY3V0ZSBhbiBvcGVyYXRpb24gd2l0aCBleGNsdXNpdmUgbG9jayBvbiBhIHJlc291cmNlXG4gICAqIEBwYXJhbSByZXNvdXJjZSAtIFVuaXF1ZSBpZGVudGlmaWVyIGZvciB0aGUgcmVzb3VyY2UgKGUuZy4sICdwZXJzb25hOm5hbWUnKVxuICAgKiBAcGFyYW0gb3BlcmF0aW9uIC0gQXN5bmMgZnVuY3Rpb24gdG8gZXhlY3V0ZSB3aGlsZSBob2xkaW5nIHRoZSBsb2NrXG4gICAqIEBwYXJhbSBvcHRpb25zIC0gTG9jayBvcHRpb25zIGluY2x1ZGluZyB0aW1lb3V0XG4gICAqIEByZXR1cm5zIFJlc3VsdCBvZiB0aGUgb3BlcmF0aW9uXG4gICAqL1xuICBzdGF0aWMgYXN5bmMgd2l0aExvY2s8VD4oXG4gICAgcmVzb3VyY2U6IHN0cmluZyxcbiAgICBvcGVyYXRpb246ICgpID0+IFByb21pc2U8VD4sXG4gICAgb3B0aW9uczogeyB0aW1lb3V0PzogbnVtYmVyIH0gPSB7fVxuICApOiBQcm9taXNlPFQ+IHtcbiAgICBjb25zdCBzdGFydFRpbWUgPSBEYXRlLm5vdygpO1xuICAgIHRoaXMubWV0cmljcy50b3RhbExvY2tSZXF1ZXN0cysrO1xuICAgIFxuICAgIGxvZ2dlci5kZWJ1ZyhgTG9jayByZXF1ZXN0ZWQgZm9yIHJlc291cmNlOiAke3Jlc291cmNlfWApO1xuICAgIFxuICAgIC8vIFdhaXQgZm9yIGFueSBleGlzdGluZyBvcGVyYXRpb24gb24gdGhpcyByZXNvdXJjZVxuICAgIGNvbnN0IGV4aXN0aW5nTG9jayA9IHRoaXMubG9ja3MuZ2V0KHJlc291cmNlKTtcbiAgICBpZiAoZXhpc3RpbmdMb2NrKSB7XG4gICAgICB0aGlzLm1ldHJpY3MuY29uY3VycmVudFdhaXRzKys7XG4gICAgICBsb2dnZXIuZGVidWcoYFdhaXRpbmcgZm9yIGV4aXN0aW5nIGxvY2sgb246ICR7cmVzb3VyY2V9YCk7XG4gICAgICBcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IGV4aXN0aW5nTG9jaztcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIC8vIFByZXZpb3VzIG9wZXJhdGlvbiBmYWlsZWQsIGJ1dCB3ZSBjYW4gcHJvY2VlZFxuICAgICAgICBsb2dnZXIuZGVidWcoYFByZXZpb3VzIG9wZXJhdGlvbiBvbiAke3Jlc291cmNlfSBmYWlsZWQsIHByb2NlZWRpbmdgKTtcbiAgICAgIH1cbiAgICB9XG4gICAgXG4gICAgLy8gQ3JlYXRlIG5ldyBsb2NrIGZvciB0aGlzIG9wZXJhdGlvblxuICAgIGNvbnN0IHRpbWVvdXQgPSBvcHRpb25zLnRpbWVvdXQgfHwgdGhpcy5ERUZBVUxUX1RJTUVPVVRfTVM7XG4gICAgY29uc3QgbG9ja1Byb21pc2UgPSB0aGlzLmV4ZWN1dGVXaXRoVGltZW91dChvcGVyYXRpb24sIHRpbWVvdXQsIHJlc291cmNlKTtcbiAgICB0aGlzLmxvY2tzLnNldChyZXNvdXJjZSwgbG9ja1Byb21pc2UpO1xuICAgIFxuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBsb2NrUHJvbWlzZTtcbiAgICAgIFxuICAgICAgLy8gUmVjb3JkIG1ldHJpY3NcbiAgICAgIGNvbnN0IHdhaXRUaW1lID0gRGF0ZS5ub3coKSAtIHN0YXJ0VGltZTtcbiAgICAgIGlmICghdGhpcy5tZXRyaWNzLmxvY2tXYWl0VGltZS5oYXMocmVzb3VyY2UpKSB7XG4gICAgICAgIHRoaXMubWV0cmljcy5sb2NrV2FpdFRpbWUuc2V0KHJlc291cmNlLCBbXSk7XG4gICAgICB9XG4gICAgICB0aGlzLm1ldHJpY3MubG9ja1dhaXRUaW1lLmdldChyZXNvdXJjZSkhLnB1c2god2FpdFRpbWUpO1xuICAgICAgXG4gICAgICBsb2dnZXIuZGVidWcoYExvY2sgcmVsZWFzZWQgZm9yIHJlc291cmNlOiAke3Jlc291cmNlfSAoJHt3YWl0VGltZX1tcylgKTtcbiAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIC8vIENsZWFuIHVwIGxvY2sgYXRvbWljYWxseSAtIGNvbXBhcmUgYW5kIGRlbGV0ZSBpbiBvbmUgb3BlcmF0aW9uXG4gICAgICBjb25zdCBjdXJyZW50TG9jayA9IHRoaXMubG9ja3MuZ2V0KHJlc291cmNlKTtcbiAgICAgIGlmIChjdXJyZW50TG9jayA9PT0gbG9ja1Byb21pc2UpIHtcbiAgICAgICAgdGhpcy5sb2Nrcy5kZWxldGUocmVzb3VyY2UpO1xuICAgICAgICBsb2dnZXIuZGVidWcoYExvY2sgcXVldWUgY2xlYW5lZCB1cCBmb3IgcmVzb3VyY2U6ICR7cmVzb3VyY2V9YCk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEV4ZWN1dGUgb3BlcmF0aW9uIHdpdGggdGltZW91dCBwcm90ZWN0aW9uXG4gICAqL1xuICBwcml2YXRlIHN0YXRpYyBhc3luYyBleGVjdXRlV2l0aFRpbWVvdXQ8VD4oXG4gICAgb3BlcmF0aW9uOiAoKSA9PiBQcm9taXNlPFQ+LFxuICAgIHRpbWVvdXRNczogbnVtYmVyLFxuICAgIHJlc291cmNlOiBzdHJpbmdcbiAgKTogUHJvbWlzZTxUPiB7XG4gICAgbGV0IHRpbWVvdXRIYW5kbGU6IE5vZGVKUy5UaW1lb3V0IHwgdW5kZWZpbmVkO1xuICAgIFxuICAgIGNvbnN0IHRpbWVvdXRQcm9taXNlID0gbmV3IFByb21pc2U8bmV2ZXI+KChfLCByZWplY3QpID0+IHtcbiAgICAgIHRpbWVvdXRIYW5kbGUgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgdGhpcy5tZXRyaWNzLmxvY2tUaW1lb3V0cysrO1xuICAgICAgICByZWplY3QobmV3IEVycm9yKGBMb2NrIG9wZXJhdGlvbiB0aW1lb3V0IGZvciByZXNvdXJjZTogJHtyZXNvdXJjZX1gKSk7XG4gICAgICB9LCB0aW1lb3V0TXMpO1xuICAgIH0pO1xuICAgIFxuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBQcm9taXNlLnJhY2UoW29wZXJhdGlvbigpLCB0aW1lb3V0UHJvbWlzZV0pO1xuICAgICAgaWYgKHRpbWVvdXRIYW5kbGUpIGNsZWFyVGltZW91dCh0aW1lb3V0SGFuZGxlKTtcbiAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmICh0aW1lb3V0SGFuZGxlKSBjbGVhclRpbWVvdXQodGltZW91dEhhbmRsZSk7XG4gICAgICB0aHJvdyBlcnJvcjtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUGVyZm9ybSBhdG9taWMgZmlsZSB3cml0ZSBvcGVyYXRpb25cbiAgICogV3JpdGVzIHRvIHRlbXBvcmFyeSBmaWxlIHRoZW4gcmVuYW1lcyB0byBlbnN1cmUgYXRvbWljaXR5XG4gICAqL1xuICBzdGF0aWMgYXN5bmMgYXRvbWljV3JpdGVGaWxlKFxuICAgIGZpbGVQYXRoOiBzdHJpbmcsXG4gICAgY29udGVudDogc3RyaW5nLFxuICAgIG9wdGlvbnM/OiB7IGVuY29kaW5nPzogQnVmZmVyRW5jb2RpbmcgfVxuICApOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCB0ZW1wUGF0aCA9IGF3YWl0IHRoaXMuZ2V0VGVtcEZpbGVQYXRoKGZpbGVQYXRoKTtcbiAgICBjb25zdCBkaXIgPSBwYXRoLmRpcm5hbWUodGVtcFBhdGgpO1xuICAgIFxuICAgIHRyeSB7XG4gICAgICAvLyBFbnN1cmUgdGVtcCBkaXJlY3RvcnkgZXhpc3RzXG4gICAgICBhd2FpdCBmcy5ta2RpcihkaXIsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICAgICAgXG4gICAgICAvLyBXcml0ZSB0byB0ZW1wb3JhcnkgZmlsZVxuICAgICAgYXdhaXQgZnMud3JpdGVGaWxlKHRlbXBQYXRoLCBjb250ZW50LCBvcHRpb25zKTtcbiAgICAgIFxuICAgICAgLy8gQXRvbWljIHJlbmFtZSAob24gc2FtZSBmaWxlc3lzdGVtKVxuICAgICAgYXdhaXQgZnMucmVuYW1lKHRlbXBQYXRoLCBmaWxlUGF0aCk7XG4gICAgICBcbiAgICAgIGxvZ2dlci5kZWJ1ZyhgQXRvbWljIHdyaXRlIGNvbXBsZXRlZDogJHtmaWxlUGF0aH1gKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgLy8gQ2xlYW4gdXAgdGVtcCBmaWxlIG9uIGVycm9yXG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCBmcy51bmxpbmsodGVtcFBhdGgpO1xuICAgICAgICBsb2dnZXIuZGVidWcoYENsZWFuZWQgdXAgdGVtcCBmaWxlIGFmdGVyIGVycm9yOiAke3RlbXBQYXRofWApO1xuICAgICAgfSBjYXRjaCAodW5saW5rRXJyb3IpIHtcbiAgICAgICAgLy8gTG9nIGNsZWFudXAgZmFpbHVyZSBidXQgZG9uJ3QgdGhyb3cgLSBvcmlnaW5hbCBlcnJvciBpcyBtb3JlIGltcG9ydGFudFxuICAgICAgICBsb2dnZXIud2FybihgRmFpbGVkIHRvIGNsZWFuIHVwIHRlbXAgZmlsZSAke3RlbXBQYXRofTogJHt1bmxpbmtFcnJvcn1gKTtcbiAgICAgIH1cbiAgICAgIHRocm93IGVycm9yO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBQZXJmb3JtIGF0b21pYyBmaWxlIHJlYWQgd2l0aCBsb2NrXG4gICAqL1xuICBzdGF0aWMgYXN5bmMgYXRvbWljUmVhZEZpbGUoXG4gICAgZmlsZVBhdGg6IHN0cmluZyxcbiAgICBvcHRpb25zPzogeyBlbmNvZGluZz86IEJ1ZmZlckVuY29kaW5nIH1cbiAgKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICByZXR1cm4gdGhpcy53aXRoTG9jayhgZmlsZToke2ZpbGVQYXRofWAsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGNvbnRlbnQgPSBhd2FpdCBmcy5yZWFkRmlsZShmaWxlUGF0aCwgb3B0aW9ucyk7XG4gICAgICByZXR1cm4gY29udGVudC50b1N0cmluZygpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIEdlbmVyYXRlIHRlbXBvcmFyeSBmaWxlIHBhdGggZm9yIGF0b21pYyBvcGVyYXRpb25zXG4gICAqL1xuICBwcml2YXRlIHN0YXRpYyBhc3luYyBnZXRUZW1wRmlsZVBhdGgob3JpZ2luYWxQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IGRpciA9IHBhdGguZGlybmFtZShvcmlnaW5hbFBhdGgpO1xuICAgIGNvbnN0IGJhc2VuYW1lID0gcGF0aC5iYXNlbmFtZShvcmlnaW5hbFBhdGgpO1xuICAgIGNvbnN0IHJhbmRvbSA9IHJhbmRvbUJ5dGVzKDgpLnRvU3RyaW5nKCdoZXgnKTtcbiAgICByZXR1cm4gcGF0aC5qb2luKGRpciwgdGhpcy5URU1QX0RJUiwgYCR7YmFzZW5hbWV9LiR7cmFuZG9tfS50bXBgKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgbG9jayBtZXRyaWNzIGZvciBtb25pdG9yaW5nXG4gICAqL1xuICBzdGF0aWMgZ2V0TWV0cmljcygpIHtcbiAgICBjb25zdCBhdmdXYWl0VGltZXMgPSBuZXcgTWFwPHN0cmluZywgbnVtYmVyPigpO1xuICAgIGZvciAoY29uc3QgW3Jlc291cmNlLCB0aW1lc10gb2YgdGhpcy5tZXRyaWNzLmxvY2tXYWl0VGltZS5lbnRyaWVzKCkpIHtcbiAgICAgIGlmICh0aW1lcy5sZW5ndGggPiAwKSB7XG4gICAgICAgIGNvbnN0IGF2ZyA9IHRpbWVzLnJlZHVjZSgoYSwgYikgPT4gYSArIGIsIDApIC8gdGltZXMubGVuZ3RoO1xuICAgICAgICBhdmdXYWl0VGltZXMuc2V0KHJlc291cmNlLCBNYXRoLnJvdW5kKGF2ZykpO1xuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICByZXR1cm4ge1xuICAgICAgdG90YWxSZXF1ZXN0czogdGhpcy5tZXRyaWNzLnRvdGFsTG9ja1JlcXVlc3RzLFxuICAgICAgYWN0aXZlTG9ja3NDb3VudDogdGhpcy5sb2Nrcy5zaXplLFxuICAgICAgdGltZW91dHM6IHRoaXMubWV0cmljcy5sb2NrVGltZW91dHMsXG4gICAgICBjb25jdXJyZW50V2FpdHM6IHRoaXMubWV0cmljcy5jb25jdXJyZW50V2FpdHMsXG4gICAgICBhdmdXYWl0VGltZUJ5UmVzb3VyY2U6IE9iamVjdC5mcm9tRW50cmllcyhhdmdXYWl0VGltZXMpLFxuICAgICAgYWN0aXZlTG9ja3M6IEFycmF5LmZyb20odGhpcy5sb2Nrcy5rZXlzKCkpXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDbGVhciBhbGwgbG9ja3MgKHVzZSB3aXRoIGNhdXRpb24gLSBtYWlubHkgZm9yIHRlc3RpbmcpXG4gICAqL1xuICBzdGF0aWMgY2xlYXJBbGxMb2NrcygpOiB2b2lkIHtcbiAgICB0aGlzLmxvY2tzLmNsZWFyKCk7XG4gICAgbG9nZ2VyLndhcm4oJ0FsbCBmaWxlIGxvY2tzIGNsZWFyZWQgLSB1c2Ugb25seSBmb3IgdGVzdGluZy9yZWNvdmVyeScpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlc2V0IG1ldHJpY3NcbiAgICovXG4gIHN0YXRpYyByZXNldE1ldHJpY3MoKTogdm9pZCB7XG4gICAgdGhpcy5tZXRyaWNzID0ge1xuICAgICAgdG90YWxMb2NrUmVxdWVzdHM6IDAsXG4gICAgICBsb2NrV2FpdFRpbWU6IG5ldyBNYXA8c3RyaW5nLCBudW1iZXJbXT4oKSxcbiAgICAgIGxvY2tUaW1lb3V0czogMCxcbiAgICAgIGNvbmN1cnJlbnRXYWl0czogMFxuICAgIH07XG4gIH1cbn0iXX0=