bulltrackers-module 1.0.743 → 1.0.745
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -43,6 +43,35 @@ class StorageManager {
|
|
|
43
43
|
}
|
|
44
44
|
return this._firestore;
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Updates the heartbeat of a zombie to "hide" it from detection
|
|
49
|
+
* while the recovery task is being queued.
|
|
50
|
+
*/
|
|
51
|
+
async claimZombie(checkpointId) {
|
|
52
|
+
if (!checkpointId) return;
|
|
53
|
+
|
|
54
|
+
// FIX: Access projectId and dataset from the config object
|
|
55
|
+
const { projectId, dataset } = this.config.bigquery; //
|
|
56
|
+
|
|
57
|
+
const query = `
|
|
58
|
+
UPDATE \`${projectId}.${dataset}.computation_checkpoints\`
|
|
59
|
+
SET last_updated = CURRENT_TIMESTAMP()
|
|
60
|
+
WHERE checkpoint_id = @checkpointId
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await this.bigquery.query({
|
|
65
|
+
query,
|
|
66
|
+
params: { checkpointId }
|
|
67
|
+
});
|
|
68
|
+
} catch (e) {
|
|
69
|
+
// Ignore errors here, it's an optimization, not critical
|
|
70
|
+
console.warn(`[Storage] Failed to claim zombie ${checkpointId}: ${e.message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
46
75
|
|
|
47
76
|
// =========================================================================
|
|
48
77
|
// RESULT COMMITTING (Batch -> GCS Buffer)
|
|
@@ -696,6 +725,7 @@ class StorageManager {
|
|
|
696
725
|
|
|
697
726
|
this._log('ERROR', `${context}: ${details}`);
|
|
698
727
|
}
|
|
728
|
+
|
|
699
729
|
}
|
|
700
730
|
|
|
701
731
|
module.exports = { StorageManager };
|
|
@@ -10,16 +10,21 @@
|
|
|
10
10
|
* the Scheduler and the Worker.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
const system = require('../index');
|
|
13
|
+
// REMOVED: const system = require('../index');
|
|
14
|
+
// We will lazy-load this inside the handler to prevent circular dependency cycles.
|
|
14
15
|
|
|
15
16
|
exports.dispatcherHandler = async (req, res) => {
|
|
16
17
|
const startTime = Date.now();
|
|
17
18
|
|
|
18
19
|
try {
|
|
20
|
+
// LAZY LOAD: Require index.js here to ensure it is fully initialized
|
|
21
|
+
// This fixes the "Accessing non-existent property inside circular dependency" error
|
|
22
|
+
const system = require('../index');
|
|
23
|
+
|
|
19
24
|
const {
|
|
20
25
|
computationName,
|
|
21
26
|
targetDate,
|
|
22
|
-
source = 'scheduled', // 'scheduled' | 'on-demand'
|
|
27
|
+
source = 'scheduled', // 'scheduled' | 'on-demand' | 'zombie-recovery'
|
|
23
28
|
entityIds, // Optional: specific entities
|
|
24
29
|
dryRun = false,
|
|
25
30
|
force = false // Optional: run even if hash matches
|
|
@@ -36,6 +41,11 @@ exports.dispatcherHandler = async (req, res) => {
|
|
|
36
41
|
|
|
37
42
|
const date = targetDate || new Date().toISOString().split('T')[0];
|
|
38
43
|
console.log(`[Dispatcher] Received ${source} request: ${computationName} for ${date}`);
|
|
44
|
+
|
|
45
|
+
// Safety check to ensure system is loaded correctly
|
|
46
|
+
if (!system || typeof system.runComputation !== 'function') {
|
|
47
|
+
throw new Error('System not fully initialized (runComputation is missing). Check index.js exports.');
|
|
48
|
+
}
|
|
39
49
|
|
|
40
50
|
// 2. DELEGATE TO ORCHESTRATOR
|
|
41
51
|
// The Orchestrator calls RunAnalyzer internally to check:
|
|
@@ -74,7 +84,7 @@ exports.dispatcherHandler = async (req, res) => {
|
|
|
74
84
|
|
|
75
85
|
// Scheduled tasks need 503 to trigger retry
|
|
76
86
|
// On-demand users need 200 to see the error message immediately
|
|
77
|
-
const httpStatus = source === 'scheduled' ? 503 : 200;
|
|
87
|
+
const httpStatus = source === 'scheduled' || source === 'zombie-recovery' ? 503 : 200;
|
|
78
88
|
|
|
79
89
|
return res.status(httpStatus).json({
|
|
80
90
|
status: result.status,
|
|
@@ -51,14 +51,21 @@ async function schedulerHandler(req, res) {
|
|
|
51
51
|
const dueComputations = findDueComputations(now);
|
|
52
52
|
|
|
53
53
|
// 2. ZOMBIE DETECTION
|
|
54
|
-
// Find tasks marked 'running' that haven't heartbeated in X mins
|
|
55
54
|
let zombies = [];
|
|
56
55
|
try {
|
|
57
56
|
zombies = await storageManager.findZombies(ZOMBIE_THRESHOLD_MINUTES);
|
|
58
|
-
|
|
57
|
+
|
|
59
58
|
if (zombies.length > 0) {
|
|
60
59
|
const zombieDetails = zombies.map(z => `${z.name} [${z.date}]`).join(', ');
|
|
61
60
|
console.log(`[Scheduler] DETECTED ${zombies.length} ZOMBIES: ${zombieDetails}`);
|
|
61
|
+
|
|
62
|
+
// --- NEW FIX: CLAIM ZOMBIES ---
|
|
63
|
+
// "Touch" these rows in the DB so they don't look like zombies for another 15 mins.
|
|
64
|
+
// This prevents re-dispatching the same task 15 times if the queue is slow.
|
|
65
|
+
await Promise.all(zombies.map(z =>
|
|
66
|
+
storageManager.claimZombie(z.checkpointId)
|
|
67
|
+
));
|
|
68
|
+
// ------------------------------
|
|
62
69
|
}
|
|
63
70
|
} catch (e) {
|
|
64
71
|
console.error(`[Scheduler] Zombie check failed: ${e.message}`);
|