CRITICAL: Fix fetch() timeout not working in Node.js + add job-level timeout + extensive debug logging

This commit is contained in:
Wizznu 2026-02-11 12:23:10 +07:00
parent 1fc1d7ecbc
commit 935e1059f6
1 changed files with 56 additions and 23 deletions

View File

@ -180,9 +180,11 @@ const wateringWorker = new Worker(
} }
console.log(' 🔴 Turning OFF:', Object.keys(offUpdates).join(', ')); console.log(' 🔴 Turning OFF:', Object.keys(offUpdates).join(', '));
await updateFirebaseSmart('aktuator', offUpdates); await updateFirebaseSmart('aktuator', offUpdates);
console.log(' ✅ Turn OFF completed, now logging history...');
// Log history // Log history
await logHistory(type, potNumbers, duration); await logHistory(type, potNumbers, duration);
console.log(' ✅ History logged successfully');
// Update last watering time // Update last watering time
for (const pot of potNumbers) { for (const pot of potNumbers) {
@ -217,6 +219,7 @@ const wateringWorker = new Worker(
{ {
connection: redis, connection: redis,
concurrency: config.worker.concurrency, concurrency: config.worker.concurrency,
lockDuration: 900000, // 15 minutes max job time (duration 600s + 5min buffer)
removeOnComplete: { count: 100 }, // Keep last 100 completed jobs removeOnComplete: { count: 100 }, // Keep last 100 completed jobs
removeOnFail: { count: 50 }, // Keep last 50 failed jobs removeOnFail: { count: 50 }, // Keep last 50 failed jobs
} }
@ -248,16 +251,36 @@ async function fetchWithTimeout(ref, timeoutMs = 10000) {
]); ]);
} }
// Helper: Fetch with timeout wrapper (for REST API calls)
async function fetchWithTimeout2(url, options = {}, timeoutMs = 10000) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeout);
return response;
} catch (error) {
clearTimeout(timeout);
if (error.name === 'AbortError') {
throw new Error('REST API timeout');
}
throw error;
}
}
// Fallback: Fetch via Firebase REST API (lebih reliable) // Fallback: Fetch via Firebase REST API (lebih reliable)
async function fetchKontrolViaREST() { async function fetchKontrolViaREST() {
const url = `${config.firebase.databaseURL}/kontrol.json`; const url = `${config.firebase.databaseURL}/kontrol.json`;
console.log(` [DEBUG] Trying REST API: ${url}`); console.log(` [DEBUG] Trying REST API: ${url}`);
const response = await fetch(url, { const response = await fetchWithTimeout2(url, {
method: 'GET', method: 'GET',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' }
timeout: 10000 }, 10000);
});
if (!response.ok) { if (!response.ok) {
throw new Error(`REST API failed: ${response.status} ${response.statusText}`); throw new Error(`REST API failed: ${response.status} ${response.statusText}`);
@ -294,12 +317,11 @@ async function updateFirebaseViaREST(path, updates) {
const url = `${config.firebase.databaseURL}/${path}.json`; const url = `${config.firebase.databaseURL}/${path}.json`;
console.log(` [DEBUG] REST API PATCH: ${url}`); console.log(` [DEBUG] REST API PATCH: ${url}`);
const response = await fetch(url, { const response = await fetchWithTimeout2(url, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates), body: JSON.stringify(updates)
timeout: 10000 }, 10000);
});
if (!response.ok) { if (!response.ok) {
throw new Error(`REST PATCH failed: ${response.status}`); throw new Error(`REST PATCH failed: ${response.status}`);
@ -322,20 +344,24 @@ async function updateWithTimeout(ref, updates, timeoutMs = 10000) {
// Smart update: Try SDK first, fallback to REST if timeout // Smart update: Try SDK first, fallback to REST if timeout
async function updateFirebaseSmart(path, updates) { async function updateFirebaseSmart(path, updates) {
const updateStr = JSON.stringify(updates);
console.log(` [UPDATE START] Path: ${path}, Data: ${updateStr}`);
try { try {
console.log(` [DEBUG] Attempting SDK update to ${path}...`); console.log(` [UPDATE] Step 1: Attempting SDK update...`);
await updateWithTimeout(db.ref(path), updates, 10000); await updateWithTimeout(db.ref(path), updates, 10000);
console.log(' ✅ Firebase SDK update successful'); console.log(` ✅ [UPDATE] Step 2: SDK update successful!`);
return true; return true;
} catch (sdkError) { } catch (sdkError) {
console.warn(` ⚠️ SDK update failed, trying REST API...`); console.warn(` ⚠️ [UPDATE] Step 2: SDK failed (${sdkError.message}), trying REST API...`);
try { try {
console.log(` [UPDATE] Step 3: Attempting REST API...`);
await updateFirebaseViaREST(path, updates); await updateFirebaseViaREST(path, updates);
console.log(' ✅ Firebase REST update successful'); console.log(` ✅ [UPDATE] Step 4: REST API successful!`);
return true; return true;
} catch (restError) { } catch (restError) {
console.error(' ❌ REST update also failed:', restError.message); console.error(` ❌ [UPDATE] Step 4: REST failed (${restError.message}) - BOTH METHODS FAILED!`);
throw new Error('Both SDK and REST update failed'); throw new Error('Both SDK and REST update failed');
} }
} }
@ -346,12 +372,11 @@ async function setFirebaseViaREST(path, data) {
const url = `${config.firebase.databaseURL}/${path}.json`; const url = `${config.firebase.databaseURL}/${path}.json`;
const response = await fetch(url, { const response = await fetch(url, {
method: 'PUT',WithTimeout2(url, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data), body: JSON.stringify(data)
timeout: 10000 }, 10000
});
if (!response.ok) { if (!response.ok) {
throw new Error(`REST PUT failed: ${response.status}`); throw new Error(`REST PUT failed: ${response.status}`);
} }
@ -371,14 +396,23 @@ async function setWithTimeout(ref, data, timeoutMs = 10000) {
// Smart set: Try SDK first, fallback to REST if timeout // Smart set: Try SDK first, fallback to REST if timeout
async function setFirebaseSmart(path, data) { async function setFirebaseSmart(path, data) {
const dataStr = JSON.stringify(data);
console.log(` [SET START] Path: ${path}, Data: ${dataStr.substring(0,100)}...`);
try { try {
console.log(` [SET] Step 1: Attempting SDK set...`);
await setWithTimeout(db.ref(path), data, 10000); await setWithTimeout(db.ref(path), data, 10000);
console.log(` ✅ [SET] Step 2: SDK set successful!`);
return true; return true;
} catch (sdkError) { } catch (sdkError) {
console.warn(` ⚠️ [SET] Step 2: SDK failed (${sdkError.message}), trying REST API...`);
try { try {
console.log(` [SET] Step 3: Attempting REST API...`);
await setFirebaseViaREST(path, data); await setFirebaseViaREST(path, data);
console.log(` ✅ [SET] Step 4: REST API successful!`);
return true; return true;
} catch (restError) { } catch (restError) {
console.error(` ❌ [SET] Step 4: REST failed - BOTH METHODS FAILED!`);
throw new Error('Both SDK and REST set failed'); throw new Error('Both SDK and REST set failed');
} }
} }
@ -392,11 +426,10 @@ async function readFirebaseSmart(path) {
} catch (sdkError) { } catch (sdkError) {
const url = `${config.firebase.databaseURL}/${path}.json`; const url = `${config.firebase.databaseURL}/${path}.json`;
const response = await fetch(url, { const response = await fetch(url, {
method: 'GET',WithTimeout2(url, {
method: 'GET', method: 'GET',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' }
timeout: 10000 }, 10000
});
if (!response.ok) { if (!response.ok) {
throw new Error(`REST GET failed: ${response.status}`); throw new Error(`REST GET failed: ${response.status}`);
} }
@ -729,9 +762,9 @@ const cleanupJob = new cron.CronJob('0 2 * * *', async () => {
const date = new Date(year, month - 1, day); const date = new Date(year, month - 1, day);
if (date < cutoffDate) { if (date < cutoffDate) {
// Use REST API DELETE // Use REST API DELETE with timeout wrapper
const url = `${config.firebase.databaseURL}/history/${dateKey}.json`; const url = `${config.firebase.databaseURL}/history/${dateKey}.json`;
await fetch(url, { method: 'DELETE', timeout: 10000 }); await fetchWithTimeout2(url, { method: 'DELETE' }, 10000);
deletedCount++; deletedCount++;
console.log(` 🗑️ Deleted: ${dateKey}`); console.log(` 🗑️ Deleted: ${dateKey}`);
} }