UNPKG

tcp-serial-relay

Version:

Modular TCP to Serial relay service with comprehensive logging, monitoring and web dashboard

141 lines (122 loc) 3.79 kB
// src/utils/retry-handler.js const { logger } = require('./logger'); /** * Generic retry handler with exponential backoff */ class RetryHandler { constructor(options = {}) { this.maxRetries = options.maxRetries || 3; this.baseDelay = options.baseDelay || 1000; this.maxDelay = options.maxDelay || 30000; this.backoffFactor = options.backoffFactor || 2; this.jitter = options.jitter || false; } /** * Execute an async operation with retry logic * @param {Function} operation - Async function to execute * @param {string} operationName - Name for logging purposes * @param {object} context - Additional context for logging * @returns {Promise} - Result of the operation */ async execute(operation, operationName = 'operation', context = {}) { let lastError; for (let attempt = 1; attempt <= this.maxRetries; attempt++) { try { logger.debug(`Attempting ${operationName}`, { attempt, maxRetries: this.maxRetries, ...context }); const result = await operation(attempt); if (attempt > 1) { logger.info(`${operationName} succeeded after ${attempt} attempts`, context); } return result; } catch (error) { lastError = error; logger.warn(`${operationName} failed`, { attempt, maxRetries: this.maxRetries, error: error.message, willRetry: attempt < this.maxRetries, ...context }); if (attempt === this.maxRetries) { break; } const delay = this.calculateDelay(attempt); logger.debug(`Retrying ${operationName} in ${delay}ms`); await this.sleep(delay); } } const finalError = new Error( `${operationName} failed after ${this.maxRetries} attempts: ${lastError.message}` ); finalError.originalError = lastError; finalError.attempts = this.maxRetries; logger.error(`${operationName} permanently failed`, { attempts: this.maxRetries, finalError: lastError.message, ...context }); throw finalError; } /** * Calculate delay for next retry with exponential backoff * @param {number} attempt - Current attempt number * @returns {number} Delay in milliseconds */ calculateDelay(attempt) { let delay = this.baseDelay * Math.pow(this.backoffFactor, attempt - 1); delay = Math.min(delay, this.maxDelay); if (this.jitter) { // Add random jitter ±25% const jitterRange = delay * 0.25; delay += (Math.random() - 0.5) * 2 * jitterRange; } return Math.round(delay); } /** * Sleep for specified duration * @param {number} ms - Milliseconds to sleep * @returns {Promise} - Promise that resolves after delay */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } /** * Create a retry handler with common connection settings * @param {object} options - Override options * @returns {RetryHandler} Configured retry handler */ function createConnectionRetryHandler(options = {}) { return new RetryHandler({ maxRetries: 3, baseDelay: 2000, maxDelay: 15000, backoffFactor: 2, jitter: true, ...options }); } /** * Create a retry handler for quick operations * @param {object} options - Override options * @returns {RetryHandler} Configured retry handler */ function createQuickRetryHandler(options = {}) { return new RetryHandler({ maxRetries: 2, baseDelay: 500, maxDelay: 2000, backoffFactor: 1.5, jitter: false, ...options }); } module.exports = { RetryHandler, createConnectionRetryHandler, createQuickRetryHandler };