Skip to content

Fix analysis-zip corruption on shared-FS (k8s/NFS) deployments (#857); nginx keepalive/gzip (#864) - #865

Merged
brianlball merged 3 commits into
developfrom
fix/zip-read-only-extract-develop
Jul 30, 2026
Merged

Fix analysis-zip corruption on shared-FS (k8s/NFS) deployments (#857); nginx keepalive/gzip (#864)#865
brianlball merged 3 commits into
developfrom
fix/zip-read-only-extract-develop

Conversation

@brianlball

@brianlball brianlball commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

develop twin of #863 (which targets 179 / OS 3.10; develop = OS 3.11). Both branches will be maintained for a while, so the fix lands in both. Cherry-picked from fix/zip-read-only-extract minus the 179-only CI image-push commit.

Fixes #857. Closes #864. Closes #858.

Zip corruption fix (#857)

Corruption chain found on the k8s/NFS cluster (flock exclusion itself works there):

  1. lock_abandoned? probed LOCK_EX on a read-only fd — Errno::EBADF on NFSv4; the deployed EBADF→abandoned override then deleted live locks, letting a second worker initialize the same analysis_dir concurrently.
  2. extract_archive (worker + Analysis) used the Zip::File.open block form with ::Zip.sort_entries = true — implicit close commits, rewriting the archive in place for any unsorted zip (web-background rewrote the live seed zip while workers downloaded it).
  3. Downloads wrote straight to the shared path unvalidated — truncated bytes extracted → terminal CorruptAnalysisZip.
  4. Workers racing past the outer receipt check re-downloaded/re-extracted needlessly (observed 6 downloads in 3s).

Changes:

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

Regression specs written red-first against unfixed code; 36 examples green.

overwrite fix (#858)

extract_archive (worker + Analysis) declared an overwrite parameter but never checked it — existing files were always skipped, so a worker killed mid-extract left stale measure.xml files the next worker kept. Now honored (default true); overwrite=false skip path pinned by spec. Red-first regression specs both copies.

nginx fix (#864)

docker/server/nginx.conf: keepalive_timeout 065, gzip off → on (level 6, standard types, min 1000B). The 0/off values were copy-paste from the 2016 stock sample, not tuning. Matches the NatLabRockies/openstudio-server-helm c3c91ea ConfigMap override so that helm-side config fork (which hard-pins the passenger 6.0.27 path) can be dropped once images rebuild.

🤖 Generated with Claude Code

brianlball and others added 2 commits July 30, 2026 07:50
…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>
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>
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
brianlball merged commit 40d2bfd into develop Jul 30, 2026
5 checks passed
@brianlball
brianlball deleted the fix/zip-read-only-extract-develop branch July 30, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant