Implement resumable staging of foreign files from HTTP sources - #7494
Open
scwatts wants to merge 2 commits into
Open
Implement resumable staging of foreign files from HTTP sources#7494scwatts wants to merge 2 commits into
scwatts wants to merge 2 commits into
Conversation
Assisted-by: Claude Code Signed-off-by: Stephen Watts <hello@stephencharleswatts.com>
Assisted-by: Claude Code Signed-off-by: Stephen Watts <hello@stephencharleswatts.com>
✅ Deploy Preview for nextflow-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
pditommaso
force-pushed
the
master
branch
2 times, most recently
from
August 20, 2026 12:03
5f935c2 to
d1eae20
Compare
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
Hello, I work on the nf-core/oncoanalyser pipeline and I'm opening this PR as an extension of the (great!) work done in response to #5214
The motivation for this small PR is that during normal operation of oncoanalyser, the pipeline may retrieve a large amount of data (sometimes hundreds of GBs) from Cloudflare R2 via HTTP. This service works well for us and our users with respect to their egress-free model, however as mentioned in the linked PR, connections to R2 during download of the largest files are often interrupted
The Nextflow file staging retry functionality for HTTP does handle this in a reasonable way by deleting the partially retrieved file and then starting the download again, but often is the case for us that each successive download attempt experiences the same connection interruption, leading to complete failure of the Nextflow / oncoanalyser run. Ideally, the retry would resume the partially downloaded file so that progress is made during each retry
There was some consideration for such a feature here, but it (fairly) hadn't been picked up since
In this PR I implement an example of the retry via resume functionality for HTTP connections, and extend the mechanism to also include S3 multipart upload destinations. I would be grateful if you were to consider merging, or otherwise implementing this resume-retry feature. It would make a real difference for UX in oncoanalyser and for the use of Cloudflare R2 in general
Disclaimer: I have used Claude Code to help create this PR, and while each line of code has been reviewed and the design carefully considered, I am still largely naive to the Nextflow codebase
Details of Pull Request
Click to show
Resume HTTP staging downloads with a byte range
When Nextflow stages a foreign file from an HTTP(S) URL and the transfer fails partway through, the retry discards the partially-downloaded file and starts over from byte 0. For large files on unstable connections this throws away every byte already transferred when the connection is interrupted
These changes add a mechanism where on retry after connection interruption, an attempt is made to resume from the last downloaded byte using an HTTP
Rangeheader. If the server ignores the range (returning200instead of206), it falls back to re-downloading from the beginning matching existing behaviour. When a file is resume-retried, it still consumes amaxRetriescount. Resume applies to HTTP/HTTPS only; FTP and other foreign sources keep the existing delete-and-restart behaviourResume S3 multipart upload destinations
When the staging target is an S3 object, the same interruption leaves an in-progress multipart upload rather than a local partial file. The S3 provider now exposes that upload as a
ResumableUpload, and the HTTP source resumes the upload from the first uncommitted part instead of aborting the multipart upload and restarting from the first byteChanges
Click to show
Top-to-bottom along the staging call path:
nextflowFilePorter: the retry loop now retries on recoverableIOExceptions (fail-fast on a missing source and on interruption) and detects a resumable partial file (HTTP(S) source + partial target), passing the newHttpCopyOption.RESUMEflag down; otherwise it deletes the partial file and restarts. A resume consumes amaxRetriescount, and a terminal failure aborts any in-progress resumable uploadnf-commons: newResumableFileSystemandResumableUploadinterfaces plus theHttpCopyOption.RESUMEflag.FileHelper.copyPathis unchanged;RESUMEflows through its existingFileSystemTransferAwaredispatch todownload()nf-httpfsXFileSystemProvider.download: a plain copy honours copy options (replace, or throw if the target exists). ARESUMEcopy sends aRangeheader, validates theContent-Range, appends on206, and falls back to a full re-download on200/416/mismatch; for a resumable target it resumes the in-progress upload instead of appending to a local filenf-amazonS3FileSystemProvider: implementsResumableFileSystem.newUploadstarts an S3 multipart upload;resumeUploadlists and recovers the in-progress multipart upload (with pagination) so a retry continues from the last committed byte instead of re-uploading from byte 0nf-amazonS3OutputStream: resumes an existing multipart upload from a set of recovered parts, andabandon()leaves the upload in-progress (flushing only a full minimum part) so a later attempt can resume itUnit tests cover resume/restart and
Content-Range/416edge cases (XFileSystemProviderTest), the resume decision (FilePorterTest), the S3 multipart resume and error propagation (S3FileSystemProviderTest), and the resumable stream (S3OutputStreamTest)../gradlew :nf-httpfs:test :nextflow:test :plugins:nf-amazon:testpassesTesting
Click to show
A Python server simulates the mid-transfer failure: it advertises the full
Content-Lengthbut drops the connection mid-body, leaving a partial file. For files larger than--part-size(default 10 MiB, matching the fixed 10 MiB buffer of Nextflow'sS3OutputStream) it truncates after one full multipart part plus half of the next, so an S3 multipart upload has at least one committed part to resumeThe new code in this PR then makes a ranged request to which the server honours and returns the remaining bytes (or the full body with
--ignore-range, to exercise the restart fallback)Given requirements around size of served file, I generate a ~50 MiB payload for testing, noting its MD5 hash:
Standard resume (HTTP server honours range request)
Click to show
Start the server:
Run:
./nextflow-src/launch.sh run scripts/main.nf -ansi-log false --url http://localhost:8000/input.txt --md5 5b169a50e13cee1f0604e9edd9c7747bThe logs shows a byte-range resume rather than a byte-0 restart:
Restart retry (HTTP server ignores range request)
Click to show
Start the server:
Run:
./nextflow-src/launch.sh run scripts/main.nf -ansi-log false --url http://localhost:8000/input.txt --md5 5b169a50e13cee1f0604e9edd9c7747bThe logs shows a successful byte-0 restart rather than a resume:
AWS S3 destination
Click to show
To exercise the S3 multipart resume, serve a payload of at least two multipart parts (≥ 20 MiB) and stage it into an S3 work dir with
-w s3://bucket-name/workFirst create a Docker image with typical coreutils, libz (
awsdep), and AWS CLIv2:docker build \ --platform linux/amd64 \ --push \ --file scripts/Dockerfile \ --tag docker.io/scwatts/awscliv2:20260817--0 \ .Then place AWS credentials into BASH environment:
Re-launch the file server to reset drop count:
Then run Nextflow script, supplying the AWS Batch conifguration:
./nextflow-src/launch.sh run scripts/main.nf -ansi-log false -config scripts/aws_batch.config --url http://localhost:8000/input.txt --md5 5b169a50e13cee1f0604e9edd9c7747bLog shows good resume in multipart upload context:
Files
File: scripts/file_server.py (click to show)
File: scripts/main.nf (click to show)
File: scripts/Dockerfile (click to show)
File: scripts/aws_batch.config (click to show)
process { executor = 'awsbatch' queue = 'batch-queue-name' container = 'docker.io/scwatts/awscliv2:20260817--0' } aws { region = 'aws-region-name' } workDir = 's3://bucket-name/object-prefix/'