Skip to content

Commit 6372e2a

Browse files
authored
Merge pull request #394 from franciscodes-ops/fix/issues-360-363
fix(#360,#361,#362,#363): retry metrics, cron validation, price cleanup, heartbeat fix
2 parents 64aab80 + 0a7bded commit 6372e2a

3 files changed

Lines changed: 27 additions & 3 deletions

File tree

‎src/jobs/priceRefresh.js‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ const health = {
1818

1919
function start() {
2020
const intervalSeconds = config.price.refreshInterval;
21+
22+
// #361 — Validate that the interval evenly divides 60 so the cron
23+
// expression produces a correct schedule. Non-divisor intervals (e.g.
24+
// 7 seconds) would create an incorrect cron schedule with node-cron's
25+
// second-level syntax.
26+
if (!Number.isInteger(intervalSeconds) || intervalSeconds < 1 || intervalSeconds > 59 || 60 % intervalSeconds !== 0) {
27+
throw new Error(
28+
`price.refreshInterval must be a whole number between 1 and 59 that evenly divides 60 ` +
29+
`(got ${intervalSeconds}). Non-divisor intervals produce incorrect cron schedules. ` +
30+
`Valid values: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60.`
31+
);
32+
}
33+
2134
const cronExpression = `*/${intervalSeconds} * * * * *`;
2235
health.startedAt = Date.now();
2336

‎src/jobs/webhookRetryWorker.js‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ async function tick() {
3636
const attemptStartedAt = Date.now();
3737
try {
3838
await dispatcher.attempt(id);
39+
health.totalRetriesProcessed++;
40+
health.totalRetryLatencyMs += Date.now() - startMs;
3941
} catch (err) {
42+
health.totalRetriesProcessed++;
43+
health.totalRetryLatencyMs += Date.now() - startMs;
4044
logger.error('Retry attempt failed', { delivery_id: id, error: err.message });
4145
}
4246
// Latency is recorded for failed attempts too — a retry that times

‎src/ws/PriceSubscriptionManager.js‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,16 +193,23 @@ class PriceSubscriptionManager {
193193
startHeartbeat() {
194194
if (this._pingTimer) return;
195195
this._pingTimer = setInterval(() => {
196+
// #363 — Collect timed-out clients first, then disconnect after
197+
// iteration. Modifying a Map during for-of iteration is undefined
198+
// behavior in JS; snapshotting the keys avoids the mutation.
199+
const timedOut = [];
196200
for (const [ws, client] of this._clients) {
197201
if (client.missedPings >= MAX_MISSED_PINGS) {
198-
logger.info('WS client timed out, disconnecting');
199-
ws.terminate();
200-
this._remove(ws);
202+
timedOut.push(ws);
201203
continue;
202204
}
203205
client.missedPings += 1;
204206
this._send(ws, { type: 'ping' });
205207
}
208+
for (const ws of timedOut) {
209+
logger.info('WS client timed out, disconnecting');
210+
ws.terminate();
211+
this._remove(ws);
212+
}
206213
}, PING_INTERVAL_MS);
207214
}
208215

0 commit comments

Comments
 (0)