watch the dev server, because pm2 cannot tell if it is serving
fofos-dev sat for eighteen hours reporting `online`, holding its port open and answering nothing. pm2 was not wrong — the process was alive and the socket was bound. It was `bun --hot` wedged after a day of edits, sitting in ep_poll and never completing a request. dev.fofosfluffybastards.com was down the whole time and nothing said so. servers.ts already knows those two answers disagree; that is why `reachable` is asked separately from pm2's status, and its own comment says so. This asks the same question on a timer. Not an hourly restart. That bounces a healthy server fifty times to catch one wedge, drops whatever anybody was doing each time, and still leaves the site broken for up to an hour where a probe catches it in two minutes. It acts only when pm2 says online AND the port does not answer, which is a lie worth correcting. A server somebody STOPPED is a decision and is left alone. Two strikes a minute apart, because --hot legitimately goes quiet while it rebuilds. Five minutes of cooldown after acting. Three failed restarts and it gives up and says to go read the logs, because a watchdog that restarts forever is a crash loop with a nicer name. State is on its own route rather than folded into /_config/servers, which answers with a bare array that two screens read positionally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import { listOriginals, originalsDir, reconcile, safeScope } from './derive';
|
||||
import { createDatabases, pushSchema, readDatabases } from './databases';
|
||||
import { readBackups, restoreBackup, runBackup, saveBackups, schedule, testDestination } from './backups';
|
||||
import { watchMailbox } from './messages';
|
||||
import { watchDevServer, watchdogState } from './dev-watchdog';
|
||||
import { watchOriginals } from './originals-watch';
|
||||
import { readServers, savePorts, siteSecrets, startServer, stopServer } from './servers';
|
||||
import { buildSite, readStatic, startStatic, stopStatic } from './static-site';
|
||||
@@ -564,6 +565,19 @@ router.post('/_config/backups/run', async (ctx) => {
|
||||
*/
|
||||
router.get('/_config/servers', async (ctx) => ctx.json(await readServers()));
|
||||
|
||||
/*
|
||||
The watchdog's own state, on its own route.
|
||||
|
||||
NOT folded into `/_config/servers`, which answers with a bare array that SitePreview and
|
||||
ServersBlock both read positionally. Wrapping it to make room here would have broken two screens to
|
||||
save one request.
|
||||
|
||||
`lastCheckedAt` is the point of exposing any of this: a timer that has died looks exactly like a
|
||||
timer with nothing to do, and a stale timestamp is the only thing that tells them apart without
|
||||
going to read a log.
|
||||
*/
|
||||
router.get('/_config/servers/watchdog', (ctx) => ctx.json(watchdogState()));
|
||||
|
||||
router.put('/_config/servers', async (ctx) => {
|
||||
const { development, production } = (ctx.get('body') ?? {}) as { development?: number; production?: number };
|
||||
try {
|
||||
@@ -800,3 +814,13 @@ void watchMailbox().catch(() => {});
|
||||
to know about.
|
||||
*/
|
||||
void watchOriginals().catch(() => {});
|
||||
|
||||
/*
|
||||
Watch the DEVELOPMENT server, because pm2 cannot tell whether it is serving.
|
||||
|
||||
It reported `online` for eighteen hours while holding its port open and answering nothing — a wedged
|
||||
`bun --hot`, and the site was down the whole time with nothing to say so. servers.ts already asks
|
||||
`reachable` separately from pm2's status for exactly this reason; this asks it on a timer and
|
||||
restarts only when the two disagree. See dev-watchdog.ts.
|
||||
*/
|
||||
watchDevServer();
|
||||
|
||||
Reference in New Issue
Block a user