From 560a596de7d48b3705c7260347b1170abe29cbfd Mon Sep 17 00:00:00 2001 From: Bernardo Meurer Costa Date: Wed, 29 Oct 2025 18:08:17 +0000 Subject: [PATCH] fix(libstore/filetransfer): prevent double callback invocation on interrupt during retry Fix a race condition where interrupting a download (via Ctrl-C) during a retry attempt could cause a crash. When `enqueueItem()` throws because the download thread is shutting down, the exception would propagate without setting `done=true`, causing the `TransferItem` destructor to invoke the callback a second time. This triggered an assertion failure in `Callback::rethrow()` with: `Assertion '!prev' failed` and the error message `cannot enqueue download request because the download thread is shutting down`. The fix catches the exception from `enqueueItem()` and calls `fail()` to properly complete the transfer, ensuring the callback is invoked exactly once. --- src/libstore/filetransfer.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index 304984d99..3ee1fb302 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -598,7 +598,14 @@ struct curlFileTransfer : public FileTransfer decompressionSink.reset(); errorSink.reset(); embargo = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms); - fileTransfer.enqueueItem(shared_from_this()); + try { + fileTransfer.enqueueItem(shared_from_this()); + } catch (const nix::Error & e) { + // If enqueue fails (e.g., during shutdown), fail the transfer properly + // instead of letting the exception propagate, which would leave done=false + // and cause the destructor to attempt a second callback invocation + fail(std::move(exc)); + } } else fail(std::move(exc)); }