forked from SmartDropLabs/smartdrop-backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcircuitBreaker.js
More file actions
75 lines (66 loc) · 2.39 KB
/
Copy pathcircuitBreaker.js
File metadata and controls
75 lines (66 loc) · 2.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
"use strict";
const logger = require("../../logger");
/**
* A per-source circuit breaker for permanent (nonRetryable) failures like an
* invalid/revoked API key. Distinct from ordinary transient failures (network
* blips, rate limits): those already self-heal on the next fetch cycle and
* are intentionally left untouched by this module.
*
* State is process-local (module-level, one instance per source per
* process) — acceptable because it only affects retry cadence, not
* correctness; each horizontally-scaled replica independently rate-limits
* its own calls to a known-broken source rather than sharing a single
* circuit (see #98 for the analogous cross-replica coordination gap in
* scheduled jobs).
*/
function createCircuitBreaker({ sourceName, cooldownMs, reminderIntervalMs }) {
let openUntil = 0;
let lastReminderLoggedAt = 0;
let lastSuccessAt = null;
function isOpen() {
return Date.now() < openUntil;
}
/** Call when a fetch is skipped because the circuit is open. Logs at most once per reminderIntervalMs, not once per skipped attempt. */
function noteSkipped(context = {}) {
const now = Date.now();
if (now - lastReminderLoggedAt >= reminderIntervalMs) {
logger.warn("Price source circuit open, skipping fetch", {
source: sourceName,
openUntil: new Date(openUntil).toISOString(),
...context,
});
lastReminderLoggedAt = now;
}
}
/** Call on a nonRetryable failure. Logs distinctly (error level) only the first time the circuit transitions from closed to open. */
function open(context = {}) {
const wasOpen = isOpen();
openUntil = Date.now() + cooldownMs;
if (!wasOpen) {
logger.error("Price source permanently misconfigured", {
source: sourceName,
cooldownMs,
...context,
});
lastReminderLoggedAt = Date.now();
}
}
/** Call on a successful fetch. No-op if the circuit was already closed. */
function close() {
openUntil = 0;
lastReminderLoggedAt = 0;
lastSuccessAt = Date.now();
}
function getState() {
return {
source: sourceName,
open: isOpen(),
openUntil: openUntil ? new Date(openUntil).toISOString() : null,
last_success_at: lastSuccessAt
? new Date(lastSuccessAt).toISOString()
: null,
};
}
return { isOpen, noteSkipped, open, close, getState };
}
module.exports = { createCircuitBreaker };