fix(server): hata yönetimini iyileştir

Zamanlayıcı ve qbit istemcisi bileşenlerinde hata işleme yeteneklerini
güçlendirir.

- loop.scheduler: qbit hatalarında sistem durumunu ve sağlık bilgisini
  güncelleme ekler.
- qbit.client: geçici ağ hatalarını (EAI_AGAIN vb.) algılayarak oturum
  durumunu sıfırlar.
- timer.worker: global hata yakalama ekleyerek işleyicinin çökmesini
  engeller ve hataları günlüğe kaydeder.
This commit is contained in:
2026-01-31 11:04:31 +03:00
parent 075780435c
commit 9b495b7bf7
3 changed files with 97 additions and 68 deletions

View File

@@ -1,7 +1,7 @@
import { QbitClient } from "../qbit/qbit.client"
import { tickLoopJobs } from "./loop.engine"
import { getStatusSnapshot, refreshJobsStatus, setTorrentsStatus } from "../status/status.service"
import { emitStatusUpdate } from "../realtime/emitter"
import { getStatusSnapshot, refreshJobsStatus, setQbitStatus, setTorrentsStatus } from "../status/status.service"
import { emitQbitHealth, emitStatusUpdate } from "../realtime/emitter"
import { logger } from "../utils/logger"
export const startLoopScheduler = (qbit: QbitClient, intervalMs: number) => {
@@ -20,7 +20,19 @@ export const startLoopScheduler = (qbit: QbitClient, intervalMs: number) => {
jobs,
});
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
logger.error({ error }, "Loop scheduler tick failed");
setQbitStatus({ ok: false, lastError: message });
emitQbitHealth({ ok: false, lastError: message });
try {
const current = await getStatusSnapshot();
emitStatusUpdate({
...current,
qbit: { ...current.qbit, ok: false, lastError: message },
});
} catch {
// Swallow secondary status errors to keep scheduler alive.
}
}
}, intervalMs);
};

View File

@@ -48,6 +48,18 @@ export class QbitClient {
}
return await fn();
} catch (error) {
if (axios.isAxiosError(error)) {
const code = error.code ?? "";
const transient =
code === "EAI_AGAIN" ||
code === "ENOTFOUND" ||
code === "ECONNREFUSED" ||
code === "ECONNRESET" ||
code === "ETIMEDOUT";
if (transient) {
this.loggedIn = false;
}
}
if (
axios.isAxiosError(error) &&
(error.response?.status === 401 || error.response?.status === 403)

View File

@@ -4,6 +4,7 @@ import { readDb, writeDb } from "../storage/jsondb";
import { TimerLog, TimerSummary } from "../types";
import { emitTimerLog, emitTimerSummary } from "../realtime/emitter";
import { nowIso } from "../utils/time";
import { logger } from "../utils/logger";
const MAX_LOGS = 2000;
@@ -17,6 +18,7 @@ const normalizeTags = (tags?: string, category?: string) => {
export const startTimerWorker = (qbit: QbitClient, intervalMs: number) => {
setInterval(async () => {
try {
const db = await readDb();
const rules = db.timerRules ?? [];
if (rules.length === 0) {
@@ -88,5 +90,8 @@ export const startTimerWorker = (qbit: QbitClient, intervalMs: number) => {
db.timerSummary = summary;
await writeDb(db);
}
} catch (error) {
logger.error({ error }, "Timer worker tick failed");
}
}, intervalMs);
};