Skip to content

Fix analysis-zip corruption on shared-FS (k8s/NFS) deployments (#857) - #863

Merged
brianlball merged 4 commits into
179from
fix/zip-read-only-extract
Jul 30, 2026
Merged

Fix analysis-zip corruption on shared-FS (k8s/NFS) deployments (#857)#863
brianlball merged 4 commits into
179from
fix/zip-read-only-extract

Conversation

@brianlball

Copy link
Copy Markdown
Contributor

Summary

Fixes the analysis-zip corruption behind #857 at its actual sources. Cluster diagnostics (mount options, cross-pod flock test, pod logs) showed flock exclusion works on the k8s/NFS deployment — the corruption chain was:

  1. lock_abandoned? probed LOCK_EX on a read-only fd — legal on local filesystems, Errno::EBADF on NFSv4 (flock is emulated as a write byte-range lock). Every waiter crashed; the deployed EBADF→abandoned runtime override then deleted live locks, letting a second worker re-create the lock on a new inode and initialize the same analysis_dir concurrently.
  2. extract_archive (worker + Analysis) used the Zip::File.open block form with ::Zip.sort_entries = true: the implicit close calls commit, rewriting the archive in place for any unsorted zip — web-background rewrote the live seed zip at analysis start while workers downloaded it; workers rewrote the shared analysis.zip after every extract.
  3. Downloads wrote straight to the shared path unvalidated, so truncated bytes were extracted and escalated to terminal CorruptAnalysisZip failures.
  4. Workers that raced past the outer receipt check re-downloaded/re-extracted even when the receipt appeared before they got the lock (observed: 6 downloads of one analysis zip in 3s).

Changes

  • lock_abandoned?: probe with LOCK_SH (grantable on read-only fds everywhere, still blocked by a live LOCK_EX); indeterminate probe = held, never abandoned.
  • extract_archive (both copies): read-only Zip::File.new, no global sort_entries — archive is byte-identical after extraction.
  • analysis.zip download: pid-suffixed temp file → Analysis.seed_zip_error validation → atomic rename. Transient corruption retries; 3 consecutive validation failures escalate to the existing terminal CorruptAnalysisZip cleanup (Enhance InitializeAnalysis resilience for corrupt seed.zip files #841 fail-fast preserved).
  • Receipt re-check while holding the lock (kills the re-download herd).

Tests

All regression specs written red-first against the unfixed code (md5-identical archive after extract, EBADF probe crash, truncated-transfer retry, receipt re-check skip); existing #841 corrupt-zip spec updated to the new validation seam. 36 examples green across the three spec files.

Deploy notes

  • CI on this branch pushes nrel/openstudio-server:179-flock images for cluster testing (temporary mapping, remove on merge).
  • The helm worker.runtimeOverrides EBADF patch and the lock-janitor should be disabled when this deploys — fix 1 makes them unnecessary, and the EBADF→abandoned override is itself the live-lock deleter.

🤖 Generated with Claude Code

brianlball and others added 2 commits July 29, 2026 18:18
…ct, verified download, receipt re-check, NFS-safe lock probe (#857)

Four defects, each with a red-first regression spec:

1. extract_archive (worker + Analysis) used the Zip::File.open block form with
   ::Zip.sort_entries = true: the implicit close calls commit, which REWRITES
   the archive in place (temp + rename) for any unsorted zip. Web-background
   rewrote the live seed_zip at analysis start while workers downloaded it;
   workers rewrote the shared analysis.zip after every extract. Now uses
   read-only Zip::File.new (same rationale as seed_zip_error) and drops the
   global sort_entries.

2. analysis.zip downloads wrote straight to the final shared path unvalidated;
   truncated bytes were extracted and escalated to terminal CorruptAnalysisZip
   failures. Now: download to pid-suffixed temp, validate via
   Analysis.seed_zip_error, atomic rename. Transient corruption retries; 3
   consecutive validation failures escalate to the existing terminal
   CorruptAnalysisZip cleanup (keeps #841 fail-fast for corrupt-at-rest zips).

3. A worker that raced past the outer receipt check re-downloaded and
   re-extracted even when the receipt appeared before it acquired the lock
   (N-worker serial re-download herd observed as 6 downloads of one analysis
   zip in 3s). Now re-checks the receipt while holding the lock.

4. lock_abandoned? probed with LOCK_EX on a read-only fd: legal on local
   filesystems, Errno::EBADF on NFSv4 (flock emulated as write byte-range
   lock), crashing every waiter on k8s/NFS - and the deployed EBADF->abandoned
   runtime patch turned that into live-lock deletion + concurrent re-init on a
   new inode. Probe now uses LOCK_SH (grantable on read-only fds everywhere,
   still blocked by a live LOCK_EX) and treats an indeterminate probe as held,
   never abandoned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…luster testing

Temporary mapping so the k8s cluster can pull the issue-857 fix candidate as
nrel/openstudio-server:179-flock; remove once merged into 179.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
keepalive_timeout 0 (copy-paste from 2016 stock sample, not a tuning
choice) forced a new TCP conn per asset/API request; gzip off served
HTML/JSON uncompressed. Matches NatLabRockies/openstudio-server-helm
c3c91ea ConfigMap override so that helm-side fork can be dropped once
images rebuild.

Refs #864

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@brianlball
brianlball force-pushed the fix/zip-read-only-extract branch from 22ba379 to ee89e42 Compare July 30, 2026 11:51
@brianlball

Copy link
Copy Markdown
Contributor Author

Added ee89e42: nginx keepalive_timeout 065, gzip off → on in the baked image config (refs #864; matches the openstudio-server-helm c3c91ea ConfigMap override so that fork can be dropped once images rebuild). develop twin of this PR incl. the nginx fix: #865.

overwrite was declared but never checked - existing files were always
skipped, so a worker killed mid-extract left stale measure.xml files
the next worker kept (BCLMeasure "could not be read as XML data").
Red-first regression specs for both copies; overwrite=false skip path
pinned too.

Refs #858

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@brianlball

Copy link
Copy Markdown
Contributor Author

Added b1caeb0: honor overwrite param in both extract_archive copies (refs #858). Red-first: new specs fail on the old skip-always behavior, green after (zf.extract(f, f_path) { true }; overwrite=false skip path pinned too). 29/29 locally across the hardening + analysis_init spec files. Same commit cherry-picked to #865 (which now carries "Closes #858").

@brianlball
brianlball merged commit d63c8eb into 179 Jul 30, 2026
5 checks passed
@brianlball
brianlball deleted the fix/zip-read-only-extract branch July 30, 2026 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant