libstore: don't hold state lock while unpausing transfers

libcurl may call callbacks during unpause. if libcurl calls these
callbacks often enough they may find that they've exhausted their
allotted buffer space, at which point they will call dataCallback
as provided in enqueueFileTransfer. download() provides callbacks
that have their own state locks and may call unpause on transfers
with *their* state locks they share with curlFileTransfer. it was
possible to cause a deadlock between these two if the curl worker
thread tried to provide some data to a callback with the transfer
state lock held and the user transfer simultaneously unpaused its
associated curl transfer, with its own state lock held. obviously
not a great situation, but avoidable by not holding any lock when
we unpause transfers and execute their callbacks, as is otherwise
the case when a transfer runs normally and is never paused at all

Change-Id: I58556d292adaf7dfb14001d3a6c5c38fa71994da
This commit is contained in:
eldritch horrors 2024-11-04 18:47:02 +01:00
parent 6b7076f81c
commit 1c28270c9d

View file

@ -632,11 +632,14 @@ struct curlFileTransfer : public FileTransfer
timeoutMs = INT64_MAX; timeoutMs = INT64_MAX;
{ {
auto state(state_.lock()); auto unpause = [&] { return std::move(state_.lock()->unpause); }();
for (auto & item : state->unpause) { for (auto & item : unpause) {
curl_easy_pause(item->req, CURLPAUSE_CONT); curl_easy_pause(item->req, CURLPAUSE_CONT);
} }
state->unpause.clear(); }
{
auto state(state_.lock());
while (!state->incoming.empty()) { while (!state->incoming.empty()) {
auto item = state->incoming.top(); auto item = state->incoming.top();
if (item->embargo <= now) { if (item->embargo <= now) {