snow-flow
Version:
Snow-Flow v3.2.0: Complete ServiceNow Enterprise Suite with 180+ MCP Tools. ATF Testing, Knowledge Management, Service Catalog, Change Management with CAB scheduling, Virtual Agent chatbots with NLU, Performance Analytics KPIs, Flow Designer automation, A
352 lines • 12.8 kB
JavaScript
;
/**
* Timeout Manager for Snow-Flow
* Provides intelligent timeout configuration and retry logic
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.OperationType = void 0;
exports.getTimeoutConfig = getTimeoutConfig;
exports.calculateTimeout = calculateTimeout;
exports.withRetry = withRetry;
exports.detectOperationType = detectOperationType;
exports.getTimeoutDescription = getTimeoutDescription;
const logger_js_1 = require("./logger.js");
const logger = new logger_js_1.Logger('TimeoutManager');
/**
* Operation types with their specific timeout requirements
*/
var OperationType;
(function (OperationType) {
// Quick operations (30s)
OperationType["SIMPLE_QUERY"] = "simple_query";
OperationType["SINGLE_RECORD"] = "single_record";
OperationType["HEALTH_CHECK"] = "health_check";
// Standard operations (2 min)
OperationType["TABLE_QUERY"] = "table_query";
OperationType["CREATE_RECORD"] = "create_record";
OperationType["UPDATE_RECORD"] = "update_record";
OperationType["DELETE_RECORD"] = "delete_record";
// Complex operations (5 min)
OperationType["BATCH_OPERATION"] = "batch_operation";
OperationType["BULK_QUERY"] = "bulk_query";
OperationType["DEPLOYMENT"] = "deployment";
OperationType["WORKFLOW_EXECUTION"] = "workflow_execution";
// ML operations (10 min)
OperationType["ML_TRAINING"] = "ml_training";
OperationType["ML_BATCH_FETCH"] = "ml_batch_fetch";
OperationType["ML_PREDICTION"] = "ml_prediction";
// Long running operations (15 min)
OperationType["LARGE_EXPORT"] = "large_export";
OperationType["MIGRATION"] = "migration";
OperationType["FULL_SYNC"] = "full_sync";
})(OperationType || (exports.OperationType = OperationType = {}));
/**
* Get timeout configuration for operation type
*/
function getTimeoutConfig(operationType) {
// Allow environment variable overrides
const envTimeout = process.env.SNOW_TIMEOUT_OVERRIDE ?
parseInt(process.env.SNOW_TIMEOUT_OVERRIDE) : null;
// Default timeout configurations per operation type
const configs = {
// Quick operations - 30 seconds
[OperationType.SIMPLE_QUERY]: {
baseTimeout: envTimeout || 30000,
maxTimeout: 60000,
retryCount: 2,
backoffMultiplier: 1.5,
jitterRange: 1000
},
[OperationType.SINGLE_RECORD]: {
baseTimeout: envTimeout || 30000,
maxTimeout: 60000,
retryCount: 2,
backoffMultiplier: 1.5,
jitterRange: 1000
},
[OperationType.HEALTH_CHECK]: {
baseTimeout: envTimeout || 30000,
maxTimeout: 45000,
retryCount: 1,
backoffMultiplier: 1.2,
jitterRange: 500
},
// Standard operations - 2 minutes
[OperationType.TABLE_QUERY]: {
baseTimeout: envTimeout || 120000,
maxTimeout: 300000,
retryCount: 3,
backoffMultiplier: 2,
jitterRange: 2000
},
[OperationType.CREATE_RECORD]: {
baseTimeout: envTimeout || 120000,
maxTimeout: 240000,
retryCount: 2,
backoffMultiplier: 1.5,
jitterRange: 1500
},
[OperationType.UPDATE_RECORD]: {
baseTimeout: envTimeout || 120000,
maxTimeout: 240000,
retryCount: 2,
backoffMultiplier: 1.5,
jitterRange: 1500
},
[OperationType.DELETE_RECORD]: {
baseTimeout: envTimeout || 120000,
maxTimeout: 180000,
retryCount: 2,
backoffMultiplier: 1.5,
jitterRange: 1000
},
// Complex operations - 5 minutes
[OperationType.BATCH_OPERATION]: {
baseTimeout: envTimeout || 300000,
maxTimeout: 600000,
retryCount: 3,
backoffMultiplier: 2,
jitterRange: 5000
},
[OperationType.BULK_QUERY]: {
baseTimeout: envTimeout || 300000,
maxTimeout: 600000,
retryCount: 3,
backoffMultiplier: 2,
jitterRange: 5000
},
[OperationType.DEPLOYMENT]: {
baseTimeout: envTimeout || 300000,
maxTimeout: 600000,
retryCount: 2,
backoffMultiplier: 1.5,
jitterRange: 3000
},
[OperationType.WORKFLOW_EXECUTION]: {
baseTimeout: envTimeout || 300000,
maxTimeout: 600000,
retryCount: 2,
backoffMultiplier: 1.5,
jitterRange: 3000
},
// ML operations - 10 minutes
[OperationType.ML_TRAINING]: {
baseTimeout: envTimeout || 600000,
maxTimeout: 900000,
retryCount: 2,
backoffMultiplier: 1.5,
jitterRange: 10000
},
[OperationType.ML_BATCH_FETCH]: {
baseTimeout: envTimeout || 600000,
maxTimeout: 900000,
retryCount: 3,
backoffMultiplier: 2,
jitterRange: 10000
},
[OperationType.ML_PREDICTION]: {
baseTimeout: envTimeout || 300000,
maxTimeout: 600000,
retryCount: 2,
backoffMultiplier: 1.5,
jitterRange: 5000
},
// Long running operations - 15 minutes
[OperationType.LARGE_EXPORT]: {
baseTimeout: envTimeout || 900000,
maxTimeout: 1800000,
retryCount: 1,
backoffMultiplier: 1.2,
jitterRange: 15000
},
[OperationType.MIGRATION]: {
baseTimeout: envTimeout || 900000,
maxTimeout: 1800000,
retryCount: 1,
backoffMultiplier: 1.2,
jitterRange: 15000
},
[OperationType.FULL_SYNC]: {
baseTimeout: envTimeout || 900000,
maxTimeout: 1800000,
retryCount: 2,
backoffMultiplier: 1.5,
jitterRange: 15000
}
};
const config = configs[operationType];
// Log timeout configuration
logger.debug(`Timeout config for ${operationType}:`, {
baseTimeout: `${config.baseTimeout / 1000}s`,
maxTimeout: `${config.maxTimeout / 1000}s`,
retries: config.retryCount
});
return config;
}
/**
* Calculate timeout with exponential backoff
*/
function calculateTimeout(config, attemptNumber) {
// Base calculation with exponential backoff
let timeout = config.baseTimeout * Math.pow(config.backoffMultiplier, attemptNumber);
// Apply max timeout cap
timeout = Math.min(timeout, config.maxTimeout);
// Add jitter to prevent thundering herd
const jitter = Math.random() * config.jitterRange - (config.jitterRange / 2);
timeout += jitter;
// Ensure minimum timeout
timeout = Math.max(timeout, 5000); // At least 5 seconds
logger.debug(`Calculated timeout for attempt ${attemptNumber + 1}: ${timeout / 1000}s`);
return Math.floor(timeout);
}
/**
* Retry wrapper with exponential backoff
*/
async function withRetry(operation, operationType, operationName) {
const config = getTimeoutConfig(operationType);
let lastError = null;
for (let attempt = 0; attempt <= config.retryCount; attempt++) {
try {
logger.info(`${operationName || operationType}: Attempt ${attempt + 1}/${config.retryCount + 1}`);
// Create timeout promise
const timeout = calculateTimeout(config, attempt);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Operation timed out after ${timeout / 1000} seconds`));
}, timeout);
});
// Race operation against timeout
const result = await Promise.race([
operation(),
timeoutPromise
]);
logger.info(`${operationName || operationType}: Success on attempt ${attempt + 1}`);
return result;
}
catch (error) {
lastError = error;
logger.warn(`${operationName || operationType}: Attempt ${attempt + 1} failed:`, error.message);
// Check if we should retry
if (attempt < config.retryCount) {
// Check if error is retryable
if (isRetryableError(error)) {
const backoffDelay = calculateBackoffDelay(config, attempt);
logger.info(`Retrying in ${backoffDelay / 1000} seconds...`);
await delay(backoffDelay);
}
else {
logger.error('Non-retryable error encountered, stopping retries');
throw error;
}
}
}
}
// All retries exhausted
logger.error(`${operationName || operationType}: All retries exhausted`);
throw lastError || new Error('All retry attempts failed');
}
/**
* Check if an error is retryable
*/
function isRetryableError(error) {
// Network errors are retryable
if (error.code === 'ECONNRESET' ||
error.code === 'ETIMEDOUT' ||
error.code === 'ECONNREFUSED' ||
error.code === 'ENOTFOUND') {
return true;
}
// Timeout errors are retryable
if (error.message?.toLowerCase().includes('timeout')) {
return true;
}
// HTTP status codes that are retryable
const retryableStatusCodes = [408, 429, 502, 503, 504];
if (error.response?.status && retryableStatusCodes.includes(error.response.status)) {
return true;
}
// ServiceNow specific retryable errors
if (error.message?.includes('rate limit') ||
error.message?.includes('too many requests') ||
error.message?.includes('service unavailable')) {
return true;
}
// Non-retryable errors
if (error.response?.status >= 400 && error.response?.status < 500) {
// Client errors (except those listed above) are not retryable
return false;
}
// Default to retryable for unknown errors
return true;
}
/**
* Calculate backoff delay between retries
*/
function calculateBackoffDelay(config, attemptNumber) {
const baseDelay = 2000; // 2 seconds base
const delay = baseDelay * Math.pow(config.backoffMultiplier, attemptNumber);
const jitter = Math.random() * config.jitterRange;
return Math.min(delay + jitter, 30000); // Max 30 seconds between retries
}
/**
* Simple delay utility
*/
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Detect operation type from context
*/
function detectOperationType(context) {
const { tool, table, action, limit } = context;
// ML operations
if (tool?.includes('ml_') || action?.includes('train') || action?.includes('predict')) {
if (action?.includes('train'))
return OperationType.ML_TRAINING;
if (action?.includes('batch'))
return OperationType.ML_BATCH_FETCH;
return OperationType.ML_PREDICTION;
}
// Deployment operations
if (tool?.includes('deploy') || action?.includes('deploy')) {
return OperationType.DEPLOYMENT;
}
// Workflow operations
if (tool?.includes('workflow') || table === 'wf_workflow') {
return OperationType.WORKFLOW_EXECUTION;
}
// Batch/bulk operations
if (tool?.includes('batch') || (limit && limit > 100)) {
return OperationType.BATCH_OPERATION;
}
// Query operations
if (tool?.includes('query') || action === 'query') {
if (limit && limit > 500)
return OperationType.BULK_QUERY;
if (limit && limit > 50)
return OperationType.TABLE_QUERY;
return OperationType.SIMPLE_QUERY;
}
// CRUD operations
if (action === 'create' || tool?.includes('create')) {
return OperationType.CREATE_RECORD;
}
if (action === 'update' || tool?.includes('update')) {
return OperationType.UPDATE_RECORD;
}
if (action === 'delete' || tool?.includes('delete')) {
return OperationType.DELETE_RECORD;
}
// Default to standard query
return OperationType.TABLE_QUERY;
}
/**
* Get human-readable timeout description
*/
function getTimeoutDescription(operationType) {
const config = getTimeoutConfig(operationType);
const baseMinutes = Math.ceil(config.baseTimeout / 60000);
const maxMinutes = Math.ceil(config.maxTimeout / 60000);
return `Base timeout: ${baseMinutes} min, Max: ${maxMinutes} min, Retries: ${config.retryCount}`;
}
//# sourceMappingURL=timeout-manager.js.map