Repository navigation
Description
Summary
extract_archive in dj_jobs/run_simulate_data_point.rb declares overwrite = true as a default parameter but never checks it — the method body always skips existing files, regardless of the argument passed.
Root Cause
def extract_archive(archive_filename, destination, overwrite = true)
Zip::File.open(archive_filename) do |zf|
zf.each do |f|
f_path = File.join(destination, f.name)
FileUtils.mkdir_p(File.dirname(f_path))
if File.exist?(f_path)
@sim_logger.warn "SKIPPED: #{f.name}, already existed." # always skips — overwrite is never checked
else
zf.extract(f, f_path)
end
end
end
endThe overwrite parameter is declared but has no effect.
Impact
If a previous worker died mid-extraction (e.g., killed by a rollout restart), or if an analysis directory was left behind from a prior run, subsequent workers will skip re-extracting files that are already present. This causes measure.xml files from the old run to persist on NFS.
OpenStudio's BCLMeasure loader then rejects the stale/partial measure.xml with errors such as:
could not be read as XML datais not a measure directoryWorkflow is invalid
This resulted in 15 out of 50 datapoints in an analysis failing with datapoint failure status.
Reproduction
- Start a large analysis on a multi-node cluster
- Trigger a worker rollout restart while workers are actively downloading/extracting the analysis zip
- On the next run, workers will find partial analysis directories on NFS and skip extraction of all already-present files
- Datapoints fail with BCLMeasure XML errors
Fix
Honor the overwrite parameter and pass it through to rubyzip's extract block:
def extract_archive(archive_filename, destination, overwrite = true)
::Zip.sort_entries = true
Zip::File.open(archive_filename) do |zf|
zf.each do |f|
@sim_logger.info "Zip: Extracting #{f.name}"
f_path = File.join(destination, f.name)
FileUtils.mkdir_p(File.dirname(f_path))
if File.exist?(f_path) && !overwrite
@sim_logger.warn "SKIPPED: #{f.name}, already existed."
else
zf.extract(f, f_path) { overwrite } # rubyzip block return value controls overwrite
end
end
end
endWith overwrite = true (the default), all files are always extracted fresh from the zip, replacing any stale content. This is safe because the Redis distributed lock fix (#857) ensures only one worker at a time runs extract_archive for a given analysis.
Notes
- Only
dj_jobs/run_simulate_data_point.rbneeds to change; the resque job delegates toDjJobs::RunSimulateDataPointdirectly and has no independent copy of this method. - This fix is a prerequisite companion to initialize_worker zip lock uses flock which is not cross-host safe on NFS, causing zlib corruption race on Kubernetes #857 — without it, the Redis lock prevents the race but stale files from pre-reset runs can still survive and cause failures.
Activity
brianlball commentedon Jul 30, 2026
Still open after #863/#865: those PRs rewrite
extract_archive(read-onlyZip::File.new, no globalsort_entries) but deliberately keep the skip-if-exists behavior — theoverwriteparameter is still declared and never checked. They do shrink this issue's blast radius (validated atomic download + receipt re-check under the lock make partial/stale extractions much rarer), but a worker killed mid-extract can still leave stalemeasure.xmlfiles that the next worker skips. A fix should land on top of #863/#865 once those merge (prior standalone fix #861 was closed).Honor overwrite param in extract_archive (worker + Analysis)
Honor overwrite param in extract_archive (worker + Analysis)
brianlball commentedon Jul 30, 2026
Fix now included in #863 (179) and #865 (develop):
overwriteis honored in bothextract_archivecopies, default true, with red-first regression specs (stalemeasure.xmlreplaced;overwrite=falseskip path pinned). Will auto-close when #865 merges to develop.Fix analysis-zip corruption on shared-FS (k8s/NFS) deployments (#857) (…
Fix analysis-zip corruption on shared-FS (k8s/NFS) deployments (#857)…