Skip to content

Commit 2dfb906

Browse files
committed
libstore: fix data race in getFileTransfer() singleton replacement
Multiple threads could simultaneously observe that the singleton FileTransfer instance has quit and attempt to replace it without synchronization. This caused undefined behavior as destroying the old object while other threads might still be accessing its mutex is not allowed. Fixed by implementing in-place restart of the FileTransfer object instead of replacing it. This avoids destroying mutexes that other threads might be waiting on or holding.
1 parent b5f765b commit 2dfb906

File tree

1 file changed

+52
-7
lines changed

1 file changed

+52
-7
lines changed

src/libstore/filetransfer.cc

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -611,11 +611,10 @@ struct curlFileTransfer : public FileTransfer
611611

612612
std::thread workerThread;
613613

614-
curlFileTransfer()
615-
: mt19937(rd())
614+
void resetCurl()
616615
{
617-
static std::once_flag globalInit;
618-
std::call_once(globalInit, curl_global_init, CURL_GLOBAL_ALL);
616+
if (curlm)
617+
curl_multi_cleanup(curlm);
619618

620619
curlm = curl_multi_init();
621620

@@ -625,13 +624,27 @@ struct curlFileTransfer : public FileTransfer
625624
#if LIBCURL_VERSION_NUM >= 0x071e00 // Max connections requires >= 7.30.0
626625
curl_multi_setopt(curlm, CURLMOPT_MAX_TOTAL_CONNECTIONS, fileTransferSettings.httpConnections.get());
627626
#endif
627+
}
628+
629+
void startWorkerThread()
630+
{
631+
workerThread = std::thread([&]() { workerThreadEntry(); });
632+
}
633+
634+
curlFileTransfer()
635+
: mt19937(rd())
636+
{
637+
static std::once_flag globalInit;
638+
std::call_once(globalInit, curl_global_init, CURL_GLOBAL_ALL);
639+
640+
resetCurl();
628641

629642
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
630643
wakeupPipe.create();
631644
fcntl(wakeupPipe.readSide.get(), F_SETFL, O_NONBLOCK);
632645
#endif
633646

634-
workerThread = std::thread([&]() { workerThreadEntry(); });
647+
startWorkerThread();
635648
}
636649

637650
~curlFileTransfer()
@@ -644,6 +657,37 @@ struct curlFileTransfer : public FileTransfer
644657
curl_multi_cleanup(curlm);
645658
}
646659

660+
bool restarting = false;
661+
662+
void restart()
663+
{
664+
{
665+
auto state(state_.lock());
666+
667+
// Check if we need to restart or if another thread is already doing it
668+
if (!state->quit || restarting) {
669+
return;
670+
}
671+
672+
// Mark that we're restarting to prevent other threads from doing it
673+
restarting = true;
674+
} // state lock released here
675+
676+
// The worker thread has already exited (it set quit = true when it exited)
677+
workerThread.join();
678+
679+
resetCurl();
680+
681+
// Reset the flags BEFORE starting the new thread
682+
{
683+
auto state(state_.lock());
684+
state->quit = false;
685+
restarting = false;
686+
} // state lock released here
687+
688+
startWorkerThread();
689+
}
690+
647691
void stopWorkerThread()
648692
{
649693
/* Signal the worker thread to exit. */
@@ -845,8 +889,9 @@ ref<FileTransfer> getFileTransfer()
845889
{
846890
static ref<curlFileTransfer> fileTransfer = makeCurlFileTransfer();
847891

848-
if (fileTransfer->state_.lock()->quit)
849-
fileTransfer = makeCurlFileTransfer();
892+
if (fileTransfer->state_.lock()->quit) {
893+
fileTransfer->restart();
894+
}
850895

851896
return fileTransfer;
852897
}

0 commit comments

Comments
 (0)