forked from SmartDropLabs/smartdrop-backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpriceRefresh.js
More file actions
112 lines (98 loc) · 3.3 KB
/
Copy pathpriceRefresh.js
File metadata and controls
112 lines (98 loc) · 3.3 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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
const cron = require('node-cron');
const priceOracle = require('../services/priceOracle');
const alertsService = require('../services/alerts');
const subscriptionManager = require('../ws/PriceSubscriptionManager');
const config = require('../config');
const logger = require('../logger');
let scheduledTask = null;
let running = false;
let cycleStartedAt = null;
const health = {
startedAt: null,
lastSuccessAt: null,
lastError: null,
running: false,
};
function start() {
const intervalSeconds = config.price.refreshInterval;
const cronExpression = `*/${intervalSeconds} * * * * *`;
health.startedAt = Date.now();
scheduledTask = cron.schedule(cronExpression, async () => {
if (running) {
logger.warn('Skipping price refresh tick, previous cycle still running', {
runningForMs: Date.now() - cycleStartedAt,
});
return;
}
running = true;
cycleStartedAt = Date.now();
health.running = true;
const maxCycleMs = config.price.refreshMaxCycleMs || 90000;
const overrunTimer = setTimeout(() => {
logger.error('Price refresh cycle exceeded max duration', {
durationMs: Date.now() - cycleStartedAt,
maxCycleMs,
});
}, maxCycleMs);
try {
logger.info('Starting scheduled price refresh');
const freshPrices = await priceOracle.refreshAllCachedPrices();
await alertsService.evaluateAll();
if (freshPrices && Object.keys(freshPrices).length > 0) {
subscriptionManager.notifyPriceUpdates(freshPrices);
}
health.lastSuccessAt = Date.now();
health.lastError = null;
} catch (err) {
logger.error('Scheduled price refresh failed', { error: err.message });
health.lastError = err.message;
} finally {
clearTimeout(overrunTimer);
running = false;
cycleStartedAt = null;
health.running = false;
}
}, {
scheduled: true,
});
logger.info('Price refresh job started', { intervalSeconds });
}
function stop() {
if (scheduledTask) {
scheduledTask.stop();
scheduledTask = null;
health.startedAt = null;
logger.info('Price refresh job stopped');
}
}
/**
* Returns the current health state of the price-refresh job.
*
* Grace period: a job that has never run since startup is not considered
* stalled until at least one full interval has elapsed.
*
* @returns {{ healthy: boolean, lastSuccessAt: number|null, lastError: string|null, stalled: boolean }}
*/
function getHealth() {
if (!health.startedAt) {
return { healthy: false, lastSuccessAt: null, lastError: null, stalled: false };
}
const intervalMs = (config.price.refreshInterval || 30) * 1000;
// Grace period: allow 2× the interval before flagging as stalled
const gracePeriodMs = intervalMs * 2;
const age = Date.now() - health.startedAt;
const inGrace = age < gracePeriodMs;
if (health.lastSuccessAt === null) {
// Has not run yet — only healthy while inside the grace window
return { healthy: inGrace, lastSuccessAt: null, lastError: health.lastError, stalled: !inGrace };
}
const timeSinceSuccess = Date.now() - health.lastSuccessAt;
const stalled = timeSinceSuccess > gracePeriodMs;
return {
healthy: !stalled,
lastSuccessAt: health.lastSuccessAt,
lastError: health.lastError,
stalled,
};
}
module.exports = { start, stop, getHealth };