feat(nextflow): self-heal torn-shard/IO failures on per-position steps + default to non-preemptible cpu partition#285
Open
tayllatheodoro wants to merge 5 commits into
Open
Conversation
…steps A per-position task killed mid-write (SLURM preemption/timeout/OOM — the 130..145 signal exits the global errorStrategy retries) can leave a torn zarr v3 shard in the output. flat-field/deskew/apply-inv-tf/virtual-stain write partial chunks via read-modify-write, so the next attempt (a Nextflow retry or a later -resume) reads the torn shard back and zarr raises `RuntimeError: the checksum is invalid` — a non-signal exit (1) that the errorStrategy terminates on, so retries and -resume both keep dying on the same position until the chunk data is manually deleted. Add a `checksum_heal(output_zarr, position, cmd)` helper in common.nf that wraps each per-position command: on failure it inspects the log and ONLY if it sees "checksum is invalid" removes that position's chunk data (zarr v3 `c/` dirs, preserving every zarr.json scaffold from --init) and re-runs the command once. Any other failure is re-raised unchanged for the existing errorStrategy. No-op on success; scoped to a single position group so concurrent tasks never collide. Heals both the retry and -resume cases. Wired into run_flat_field, run_deskew, run_apply_inv_tf, run_virtual_stain. Out of scope: whole-plate single-shot steps (compute_transfer_function, run_concatenate), which have no per-position key. Closes #284 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rror
The same partial-write corruption (a shard left torn/truncated by a killed
mid-write task) surfaces from the zarrs codec pipeline as two distinct
messages, both on the read-modify-write path:
- "the checksum is invalid" (CRC32C mismatch)
- "encoded shard is smaller than the expected size of its index."
(truncated shard)
checksum_heal only matched the first, so a run could still terminate on the
second (observed on a MAP4/ZIKV flat_field resume, position B/3/000001).
Broaden the detection to an extended-regex matching both signatures; the
remedy is identical (clear the position's chunk data, keep the zarr.json
scaffold, retry once). See czbiohub-sf/iohub#415.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The per-position compute steps ran on `preempted`, where SLURM reclaims jobs
mid-write (SIGTERM, exit 143) and leaves partially-written zarr shards. Those
torn shards then fail every subsequent read-modify-write ("checksum is invalid"
/ "encoded shard is smaller than the expected size") and keep aborting on
-resume — the primary driver of the corruption in #286/#287.
Route `withLabel: 'cpu'` to the non-preemptible `cpu` partition instead, which
removes the mid-write-kill source. `gpu` is unchanged. The errorStrategy retry
range still covers genuine time-limit/OOM kills. Switching back to `preempted`
remains a one-line change for cost-sensitive runs.
Refs #286, #287
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hards A third torn-shard symptom from the zarrs write/RMW path: when the corrupt inner chunk is blosc-compressed, decoding the torn shard raises `RuntimeError: blosc encoded value is invalid` (rather than the CRC32C "checksum is invalid" or the "encoded shard is smaller ..." truncation). Observed on B/5/003000 after an interrupted (EIO) write left a torn shard; the heal pattern didn't match it, so the run aborted on -resume. Add it to the torn-shard regex; remedy is identical (clear the position's chunk data, keep the zarr.json scaffold, retry once). Refs #286, #442, iohub#415. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ilure Torn shards surfaced as an open-ended set of zarrs codec messages (checksum is invalid / encoded shard is smaller / blosc encoded value is invalid / ...), each requiring a new string match. Since a per-position task always fully recomputes its position from the input, clearing that position's chunk data and retrying once is safe for ANY failure. Heal on any non-zero exit instead of matching error text: recovers every torn-shard flavour (present and future) and gives transient storage errors (EIO) a second chance, while a genuine bug just fails the retry and propagates as before. Refs #286, #442, iohub#415. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collaborator
Author
Errors encountered & decision logFull record of the errors that drove this PR and why each change was made. Root-cause tracker: #286. Errors encountered (in the order they surfaced)
Key decisions (and why)
Process missteps & corrections (for transparency)
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the recurrent zarr torn-shard corruption that kept aborting mantis-v2 per-position steps and looping on
-resume(root-cause writeup: #286). Two complementary changes:1. Generalized per-position self-heal
checksum_heal(output_zarr, position, cmd)innextflow/modules/common.nf, wired intorun_flat_field,run_deskew,run_apply_inv_tf,run_virtual_stain.A per-position task killed mid-write (SLURM preemption / timeout / OOM, or a transient storage EIO) leaves a torn/partial zarr v3 shard. These steps write partial chunks via read-modify-write, so the next attempt (Nextflow retry or
-resume) reads the torn shard back and thezarrscodec aborts with a non-signal exit (1) that the globalerrorStrategywouldterminateon. The same root cause surfaces as several different messages depending on the codec:the checksum is invalid/encoded shard is smaller than the expected size/blosc encoded value is invalid/ …Rather than enumerate every message, the wrapper heals on any non-zero exit: because a per-position task always fully recomputes its position from the input, it is always safe to clear that position's chunk data (the
c/dirs, preserving thezarr.jsonscaffold from--init) and run the command once more. If the retry also fails, its exit status propagates (genuine bugs are not masked — they just cost one extra attempt). No-op on success; scoped to a single position group so concurrent tasks never collide. This also gives transient EIO a second chance (the EIO-retry idea from #286).2. Route cpu steps off the
preemptedpartitionnextflow/nextflow.config:withLabel: 'cpu'now queues on the non-preemptiblecpupartition instead ofpreempted. Preemptive reclamation mid-write was the primary source of torn shards; running oncpuremoves it.gpuis unchanged. Switching back topreemptedremains a one-line change for cost-sensitive runs.Commit history (evolution)
feat: self-healchecksum is invalidfix: alsoencoded shard is smaller than the expected sizefix: default cpu steps to non-preemptiblecpupartitionfix: alsoblosc encoded value is invalidrefactor: generalize to clean-and-retry on any failure (supersedes the string matching)Verification
nextflow lintclean on all changed files.bash -ue(Nextflow's shell): success is a no-op; a torn shard (incl. blosc) is cleaned + retried → success; a genuine bug is cleaned + retried once then propagates; transient EIO gets a retry.2026_07_14_A549_MAP4_ZIKV: healed checksum / encoded-shard / blosc positions across flat_field & deskew; oncputhere are zero preemption (exit-143) kills, and runs pass flat_field → deskew → reconstruct.Refs #286, #287, czbiohub-sf/iohub#415, czbiohub-sf/iohub#442, zarrs/zarrs#420.