diff --git a/src/lock-publisher.mjs b/src/lock-publisher.mjs index 08d0fb5..938d155 100644 --- a/src/lock-publisher.mjs +++ b/src/lock-publisher.mjs @@ -187,6 +187,17 @@ export class LockPublisher { * @throws {Error} If publication quorum is not met after all attempts. */ async publish() { + this._initialize(); + + try { + this.healthManager.maybeLogSnapshot(this.allRelays, this.healthConfig, this.healthLogger, 'publish:periodic'); + return await this._executeRetryLoop(Date.now()); + } finally { + this.pool.close(this.allRelays); + } + } + + _initialize() { const { poolFactory = () => new SimplePool(), retryAttempts = 4, @@ -222,49 +233,49 @@ export class LockPublisher { this.attemptId = diagnostics.attemptId || randomUUID(); this.allRelays = mergeRelayList(this.relays, this.fallbackRelays); + } - try { - this.healthManager.maybeLogSnapshot(this.allRelays, this.healthConfig, this.healthLogger, 'publish:periodic'); - let lastAttemptState = null; - const retryTimeline = []; - const overallStartedAt = Date.now(); - let terminalFailureCategory = PUBLISH_FAILURE_CATEGORIES.NON_RETRYABLE; + async _executeRetryLoop(overallStartedAt) { + let lastAttemptState = null; + const retryTimeline = []; + let terminalFailureCategory = PUBLISH_FAILURE_CATEGORIES.NON_RETRYABLE; - // Retry loop: attempts publication until success or max attempts reached - for (let attemptNumber = 1; attemptNumber <= this.maxAttempts; attemptNumber += 1) { - const startedAtMs = Date.now(); - lastAttemptState = await this.executePublishCycle(); - const elapsedMs = Date.now() - startedAtMs; - retryTimeline.push({ - publishAttempt: attemptNumber, - successCount: lastAttemptState.successCount, - relayAttemptedCount: lastAttemptState.attempted.size, - elapsedMs, - }); + // Retry loop: attempts publication until success or max attempts reached + for (let attemptNumber = 1; attemptNumber <= this.maxAttempts; attemptNumber += 1) { + const startedAtMs = Date.now(); + lastAttemptState = await this.executePublishCycle(); + const elapsedMs = Date.now() - startedAtMs; + retryTimeline.push({ + publishAttempt: attemptNumber, + successCount: lastAttemptState.successCount, + relayAttemptedCount: lastAttemptState.attempted.size, + elapsedMs, + }); - if (lastAttemptState.successCount >= this.minSuccesses) { - this.logSuccess(attemptNumber, lastAttemptState, retryTimeline, overallStartedAt); - return this.event; - } - - const { hasTransientFailure, hasPermanentFailure } = this.analyzeFailures(lastAttemptState.failures); - const canRetry = attemptNumber < this.maxAttempts && hasTransientFailure && !hasPermanentFailure; - - terminalFailureCategory = this.determineFailureCategory(hasTransientFailure, hasPermanentFailure, attemptNumber); - - if (!canRetry) { - break; - } - - const nextDelayMs = calculateBackoffDelayMs(attemptNumber, this.retryBaseDelayMs, this.retryCapDelayMs, this.randomFn); - this.logRetry(attemptNumber, lastAttemptState.failures, elapsedMs, nextDelayMs); - await this.sleepFn(nextDelayMs); + if (lastAttemptState.successCount >= this.minSuccesses) { + this.logSuccess(attemptNumber, lastAttemptState, retryTimeline, overallStartedAt); + return this.event; } - this.handleFinalFailure(lastAttemptState, terminalFailureCategory, retryTimeline, overallStartedAt); - } finally { - this.pool.close(this.allRelays); + const { hasTransientFailure, hasPermanentFailure } = this.analyzeFailures(lastAttemptState.failures); + const canRetry = this._shouldRetry(attemptNumber, hasTransientFailure, hasPermanentFailure); + + terminalFailureCategory = this.determineFailureCategory(hasTransientFailure, hasPermanentFailure, attemptNumber); + + if (!canRetry) { + break; + } + + const nextDelayMs = calculateBackoffDelayMs(attemptNumber, this.retryBaseDelayMs, this.retryCapDelayMs, this.randomFn); + this.logRetry(attemptNumber, lastAttemptState.failures, elapsedMs, nextDelayMs); + await this.sleepFn(nextDelayMs); } + + this.handleFinalFailure(lastAttemptState, terminalFailureCategory, retryTimeline, overallStartedAt); + } + + _shouldRetry(attemptNumber, hasTransientFailure, hasPermanentFailure) { + return attemptNumber < this.maxAttempts && hasTransientFailure && !hasPermanentFailure; } async executePublishCycle() {