Skip to content

Commit c19e6b8

Browse files
refactor(async): use consistent timestamp throughout circuit breaker execute
1 parent 1cf79c7 commit c19e6b8

File tree

1 file changed

+24
-14
lines changed

1 file changed

+24
-14
lines changed

async/unstable_circuit_breaker.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ export class CircuitBreaker<T = unknown> {
475475
* @returns `true` if requests will be attempted, `false` if rejected.
476476
*/
477477
get isAvailable(): boolean {
478-
const resolved = this.#resolveCurrentState();
478+
const resolved = this.#resolveCurrentState(Date.now());
479479
if (resolved.state === "closed") return true;
480480
if (resolved.state === "open") return false;
481481
// half_open: available if under concurrent limit
@@ -533,7 +533,7 @@ export class CircuitBreaker<T = unknown> {
533533
options?.signal?.throwIfAborted();
534534

535535
const currentTime = Date.now();
536-
const currentState = this.#resolveCurrentState();
536+
const currentState = this.#resolveCurrentState(currentTime);
537537

538538
// Check if we should reject
539539
if (currentState.state === "open") {
@@ -557,7 +557,7 @@ export class CircuitBreaker<T = unknown> {
557557
try {
558558
result = await fn();
559559
} catch (error) {
560-
this.#handleFailure(error, currentState.state);
560+
this.#handleFailure(error, currentState.state, currentTime);
561561
throw error;
562562
} finally {
563563
// Decrement half-open in-flight counter
@@ -572,7 +572,7 @@ export class CircuitBreaker<T = unknown> {
572572
// Check if successful result should count as failure
573573
if (this.#isResultFailure(result)) {
574574
const syntheticError = new Error("Result classified as failure");
575-
this.#handleFailure(syntheticError, currentState.state);
575+
this.#handleFailure(syntheticError, currentState.state, currentTime);
576576
return result; // Still return the result, but record the failure
577577
}
578578

@@ -689,14 +689,16 @@ export class CircuitBreaker<T = unknown> {
689689
/**
690690
* Resolves the current state, handling automatic transitions.
691691
* OPEN → HALF_OPEN after cooldown expires.
692+
*
693+
* @param now Current timestamp in milliseconds.
692694
*/
693-
#resolveCurrentState(): CircuitBreakerState {
695+
#resolveCurrentState(now: number): CircuitBreakerState {
694696
if (this.#state.state !== "open") {
695697
return this.#state;
696698
}
697699

698700
const cooldownEnd = this.#state.openedAt + this.#cooldownMs;
699-
if (Date.now() < cooldownEnd) {
701+
if (now < cooldownEnd) {
700702
return this.#state;
701703
}
702704

@@ -712,22 +714,30 @@ export class CircuitBreaker<T = unknown> {
712714
return this.#state;
713715
}
714716

715-
/** Records a failure and potentially opens the circuit. */
716-
#handleFailure(error: unknown, previousState: CircuitState): void {
717+
/**
718+
* Records a failure and potentially opens the circuit.
719+
*
720+
* @param error The error that occurred.
721+
* @param previousState The state before this failure.
722+
* @param now Current timestamp in milliseconds.
723+
*/
724+
#handleFailure(
725+
error: unknown,
726+
previousState: CircuitState,
727+
now: number,
728+
): void {
717729
// Check if this error should count as a failure
718730
if (!this.#isFailure(error)) {
719731
return;
720732
}
721733

722-
const currentTime = Date.now();
723-
724734
// Prune old failures and add new one
725735
const prunedFailures = pruneOldFailures(
726736
this.#state.failureTimestamps,
727737
this.#failureWindowMs,
728-
currentTime,
738+
now,
729739
);
730-
const newFailures = [...prunedFailures, currentTime];
740+
const newFailures = [...prunedFailures, now];
731741

732742
this.#onFailure?.(error, newFailures.length);
733743

@@ -737,7 +747,7 @@ export class CircuitBreaker<T = unknown> {
737747
...this.#state,
738748
state: "open",
739749
failureTimestamps: newFailures,
740-
openedAt: currentTime,
750+
openedAt: now,
741751
consecutiveSuccesses: 0,
742752
};
743753
this.#onStateChange?.("half_open", "open");
@@ -751,7 +761,7 @@ export class CircuitBreaker<T = unknown> {
751761
...this.#state,
752762
state: "open",
753763
failureTimestamps: newFailures,
754-
openedAt: currentTime,
764+
openedAt: now,
755765
consecutiveSuccesses: 0,
756766
};
757767
this.#onStateChange?.("closed", "open");

0 commit comments

Comments
 (0)