mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-18 21:21:11 +02:00
fix(apps): stop task.yaml rewrite races + blank-load watchdog + loop-lag monitor
Investigating 'apps blank while a bg task runs': measured serving latency through a real scheduled bg-task run — 1-3ms on both loopbacks throughout, so the server is not the bottleneck. Found and fixed the likely renderer-era culprit plus made the failure observable: - agent sync (apps:list, polled every 4s) rewrote each bundled agent's task.yaml unconditionally — racing the bg runner's own patches mid-run and spamming watcher events; now writes only when the definition changed - AppFrame load watchdog: if the iframe hasn't loaded in 6s, show a visible 'taking too long' state with Retry instead of a silent blank pane - main event-loop lag monitor: stalls >300ms are logged so future blank reports can be tied to the offending work
This commit is contained in:
parent
9173097715
commit
7c9fe7214d
3 changed files with 65 additions and 2 deletions
|
|
@ -59,6 +59,9 @@ async function materializeAgent(folder: string, agentFile: string): Promise<stri
|
|||
|
||||
if (await pathExists(taskYaml)) {
|
||||
// Update path (§8.4): overwrite definition fields, preserve the rest.
|
||||
// Write ONLY when the definition actually changed — this sync runs on
|
||||
// every apps:list poll, and an unconditional rewrite races the bg
|
||||
// runner's own task.yaml patches mid-run (and spams watcher events).
|
||||
try {
|
||||
const current = parseYaml(await fs.readFile(taskYaml, 'utf-8')) as BackgroundTask;
|
||||
const next: BackgroundTask = {
|
||||
|
|
@ -69,7 +72,14 @@ async function materializeAgent(folder: string, agentFile: string): Promise<stri
|
|||
sourceApp: folder,
|
||||
};
|
||||
if (!def.triggers) delete next.triggers;
|
||||
await fs.writeFile(taskYaml, stringifyYaml(next), 'utf-8');
|
||||
const unchanged =
|
||||
current.name === next.name &&
|
||||
current.instructions === next.instructions &&
|
||||
current.sourceApp === next.sourceApp &&
|
||||
JSON.stringify(current.triggers ?? null) === JSON.stringify(next.triggers ?? null);
|
||||
if (!unchanged) {
|
||||
await fs.writeFile(taskYaml, stringifyYaml(next), 'utf-8');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[Apps] failed to update agent task ${slug}:`, e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -710,6 +710,27 @@ export function getServerStatus(): { running: boolean; error?: string } {
|
|||
return server ? { running: true } : { running: false, ...(serverError ? { error: serverError } : {}) };
|
||||
}
|
||||
|
||||
let lagMonitor: NodeJS.Timeout | null = null;
|
||||
|
||||
/**
|
||||
* Event-loop lag monitor: the apps server shares the main process with agent
|
||||
* runs, sync pipelines, etc. If the loop stalls, every open app hangs with it —
|
||||
* log stalls >300ms so "app went blank" reports can be tied to a culprit.
|
||||
*/
|
||||
function startLagMonitor(): void {
|
||||
if (lagMonitor) return;
|
||||
let last = Date.now();
|
||||
lagMonitor = setInterval(() => {
|
||||
const now = Date.now();
|
||||
const lag = now - last - 500;
|
||||
if (lag > 300) {
|
||||
console.warn(`[Apps] main event-loop stalled ~${lag}ms (apps server shares this loop — open apps hang during stalls)`);
|
||||
}
|
||||
last = now;
|
||||
}, 500);
|
||||
lagMonitor.unref?.();
|
||||
}
|
||||
|
||||
export async function init(): Promise<void> {
|
||||
if (server) return;
|
||||
if (startPromise) return startPromise;
|
||||
|
|
@ -717,6 +738,7 @@ export async function init(): Promise<void> {
|
|||
startPromise = (async () => {
|
||||
try {
|
||||
await fsp.mkdir(APPS_DIR, { recursive: true });
|
||||
startLagMonitor();
|
||||
await startWatcher();
|
||||
const expressApp = createApp();
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue