refactor: use setTimeout chain instead of setInterval for workspace sync

- Next sync schedules AFTER current completes (natural backpressure)
- No more syncInProgress mutex needed
- Cleaner, more predictable timing
This commit is contained in:
Ash Brener 2026-01-28 15:13:43 +02:00
parent 65f135e1a3
commit a3eaec5f41

View File

@ -25,28 +25,50 @@ type SyncManagerLogger = {
};
type SyncManagerState = {
intervalId: ReturnType<typeof setInterval> | null;
timeoutId: ReturnType<typeof setTimeout> | null;
lastSyncAt: Date | null;
lastSyncOk: boolean | null;
syncCount: number;
errorCount: number;
hasSuccessfulSync: boolean;
syncInProgress: boolean;
running: boolean;
intervalMs: number;
};
const state: SyncManagerState = {
intervalId: null,
timeoutId: null,
lastSyncAt: null,
lastSyncOk: null,
syncCount: 0,
errorCount: 0,
hasSuccessfulSync: false,
syncInProgress: false,
running: false,
intervalMs: 0,
};
let currentConfig: MoltbotConfig | null = null;
let currentLogger: SyncManagerLogger | null = null;
/**
* Schedule the next sync after the configured interval.
* Uses setTimeout chain pattern - next sync schedules AFTER current completes.
*/
function scheduleNextSync(): void {
if (!state.running || state.intervalMs <= 0) return;
state.timeoutId = setTimeout(() => {
runSyncLoop();
}, state.intervalMs);
}
/**
* Run sync and schedule next. This is the main loop.
*/
async function runSyncLoop(): Promise<void> {
await runSync();
scheduleNextSync();
}
/**
* Run a single sync operation.
* This is a pure rclone operation - no agent/LLM involvement.
@ -57,14 +79,7 @@ async function runSync(): Promise<void> {
const syncConfig = currentConfig.workspace?.sync;
if (!syncConfig?.provider || syncConfig.provider === "off") return;
// Skip if a sync is already in progress
if (state.syncInProgress) {
currentLogger.info("[workspace-sync] Sync already in progress, skipping");
return;
}
const logger = currentLogger;
state.syncInProgress = true;
try {
// Check if rclone is available
@ -146,8 +161,6 @@ async function runSync(): Promise<void> {
logger.error(
`[workspace-sync] Periodic sync error: ${err instanceof Error ? err.message : String(err)}`,
);
} finally {
state.syncInProgress = false;
}
}
@ -214,15 +227,14 @@ export function startWorkspaceSyncManager(cfg: MoltbotConfig, logger: SyncManage
`[workspace-sync] Starting periodic sync every ${effectiveInterval}s (pure file sync, zero LLM cost)`,
);
// Run initial sync after a short delay (let gateway fully start)
setTimeout(() => {
runSync().catch(() => {});
}, 5000);
state.running = true;
state.intervalMs = effectiveInterval * 1000;
// Set up periodic sync
state.intervalId = setInterval(() => {
runSync().catch(() => {});
}, effectiveInterval * 1000);
// Run initial sync after a short delay (let gateway fully start)
// Then schedule subsequent syncs via setTimeout chain
state.timeoutId = setTimeout(() => {
runSyncLoop();
}, 5000);
}
/**
@ -230,9 +242,10 @@ export function startWorkspaceSyncManager(cfg: MoltbotConfig, logger: SyncManage
* Called when the gateway stops.
*/
export function stopWorkspaceSyncManager(): void {
if (state.intervalId) {
clearInterval(state.intervalId);
state.intervalId = null;
state.running = false;
if (state.timeoutId) {
clearTimeout(state.timeoutId);
state.timeoutId = null;
}
currentConfig = null;
currentLogger = null;
@ -249,7 +262,7 @@ export function getWorkspaceSyncStatus(): {
errorCount: number;
} {
return {
running: state.intervalId !== null,
running: state.running,
lastSyncAt: state.lastSyncAt,
lastSyncOk: state.lastSyncOk,
syncCount: state.syncCount,