fix(bench): write config.json atomically to prevent silent 404s#559
fix(bench): write config.json atomically to prevent silent 404s#559regdocs wants to merge 1 commit into
Conversation
set_bench_config wrote its temp file to /tmp and finished with shutil.copy2(), which truncates config.json before streaming into it. A crash in that window left a 0-byte config.json (plus a valid .bak), so Bench.__init__ raised on json.load, Server.benches silently dropped the bench, and every request 404'd with BenchNotExistsException despite the bench existing on disk. - set_bench_config: temp file via mkstemp(dir=self.directory) + fsync + os.replace (atomic same-filesystem rename); drops the copy2/.bak dance. - bench_init: initial config.json write made atomic the same way so an interrupted New Bench job can't leave a 0-byte stub. - Server.benches: log the skipped bench via logger.exception instead of suppress(Exception), so the real cause is diagnosable, not an opaque 404.
|
Tick the box to add this pull request to the merge queue (same as
|
Confidence Score: 4/5The atomic-write fix is correct and removes the truncation window; the only rough edge is log verbosity for broken benches. The core changes are solid. The agent/server.py — specifically the Reviews (1): Last reviewed commit: "fix(bench): write config.json atomically..." | Re-trigger Greptile |
| try: | ||
| benches[directory] = Bench(directory, self) | ||
| except Exception: | ||
| # A bench that fails to construct (e.g. missing/corrupt | ||
| # config.json) is skipped, which surfaces downstream as an | ||
| # opaque BenchNotExistsException -> 404. Log it so the real | ||
| # cause is diagnosable instead of silently swallowed. | ||
| logger.exception("Skipping bench %s: failed to load", directory) |
There was a problem hiding this comment.
Exception logged on every
benches access for a broken bench
self.benches is an uncached @property called from ~15 sites (including hot paths like dump() and get_bench). A permanently corrupt config.json will fire logger.exception on every one of those accesses, flooding the log until the bench is manually repaired.
Problem
A bench that clearly exists on disk was returning
404for every request:On the server,
config.jsonwas 0 bytes while a validconfig.json.baksat right next to it.Root cause
set_bench_configclaimed to avoid partial writes by writing to a temp file first, but its final step usedshutil.copy2(temp, config.json).copy2opens the destination withopen(dst, "wb"), which truncatesconfig.jsonto 0 bytes and then streams into it. If the process dies in that window (OOM kill, worker/supervisor restart, deploy), you're left with a 0-byteconfig.json+ a valid.bak.Bench.__init__then raises onjson.load(atbench.py:61, before the existence guard), andServer.benchesswallowed the exception viasuppress(Exception)— so the bench silently disappeared from the dict andget_benchraisedBenchNotExistsException→404, with no hint of the real cause.The temp file lived in
/tmp(a different filesystem), which is whycopy2was used instead of an atomicos.replace— the latter fails withEXDEVacross filesystems.Fix
set_bench_config: write the temp file in the bench directory viatempfile.mkstemp(dir=self.directory),fsync, thenos.replaceontoconfig.json— an atomic same-filesystem rename with no truncation window. Removes thecopy2/.bakdance.bench_init: the initialconfig.jsonwrite for a new bench usedopen("w")(truncates immediately) — made atomic the same way, so an interrupted New Bench job can't leave a 0-byte stub.Server.benches: replacesuppress(Exception)with atry/exceptthatlogger.exceptions the skipped bench, so a corrupt config is diagnosable from logs instead of an opaque 404.Testing
Recovering affected benches
For any bench already in this state, the good config is in the backup: