fix(upload): prevent false 422s and EMFILE crash during bulk uploads

Four related issues made uploading 271+ activities unreliable:

1. merge_all/write_combined_feed were inside the extraction try/except —
   any merge race returned 422 even though the file was on disk, causing
   the mobile app to permanently mark the upload as failed.  Fixed by
   moving them to a separate best-effort try/except after the extraction
   block.  Switch to merge_one (single-activity symlink) instead of
   merge_all (full rebuild) so each upload is O(1) FS ops, not O(N).

2. The dev watcher fired merge_all for every activity .json write AND the
   upload endpoint also ran merge_all — O(N²) symlink operations during
   bulk uploads.  Watcher now skips activities/*.json changes (upload
   endpoint handles those directly).

3. Vite/Chokidar followed the public/data symlink and opened a handle per
   activity file; constant merge rebuilds exhausted file descriptors and
   crashed the Astro dev server.  Fixed with watch.ignored on public/data.

4. _write_year_shards and write_combined_feed used f.unlink() without
   missing_ok=True — concurrent callers racing the same file threw
   FileNotFoundError which propagated as a false extraction failure.
This commit is contained in:
Davide Scaini
2026-04-27 14:33:05 +02:00
parent 7a65ed2078
commit be772bd3df
4 changed files with 34 additions and 9 deletions
+5
View File
@@ -141,6 +141,11 @@ def _watch_data(data: Path) -> None:
continue
for prefix, user_dir in prefix_to_user.items():
if path.startswith(prefix):
# Skip new-activity .json writes in activities/ — the upload
# endpoint calls merge_one inline, so firing merge_all here
# too would cause O(N²) full rebuilds during bulk uploads.
if path.startswith(str(user_dir / "activities")) and path.endswith(".json"):
break
affected.add(user_dir)
break