diff --git a/core/src/net/http_client.cpp b/core/src/net/http_client.cpp index a83b206..f265971 100644 --- a/core/src/net/http_client.cpp +++ b/core/src/net/http_client.cpp @@ -290,11 +290,20 @@ struct HttpClient::Impl { } void drain_commands(Worker &w) { + // Called every worker-loop iteration (run(), below) -- once per curl_multi_poll + // wake, so once per socket-readiness event on the transfer hot path -- but commands + // (add/pause/resume/cancel) are rare next to that. Check empty under the lock + // *before* touching `local`: libstdc++'s std::deque allocates its map array on + // default construction even with nothing pushed to it, so constructing one every + // iteration just to usually swap nothing into it was an allocation on every poll + // wake, not just on an actual command -- exactly what the curl-write-callback path + // must never do (AGENT-CORE.md; caught by tools/bench's alloc-check). + std::unique_lock lk(w.mu); + if (w.queue.empty()) + return; std::deque local; - { - std::lock_guard lk(w.mu); - local.swap(w.queue); - } + local.swap(w.queue); + lk.unlock(); for (auto &cmd : local) { auto &st = cmd.state; switch (cmd.kind) {