Commit Graph
3 Commits
Author SHA1 Message Date
samiandClaude Sonnet 5 6163898c14 core: stop allocating an empty deque on every worker-loop iteration
HttpClient::Impl::drain_commands() default-constructed a std::deque<Command>
every call, on every iteration of the worker thread's event loop (once per
curl_multi_poll wake -- i.e. once per socket-readiness event on the transfer
hot path), then swapped the (usually empty) command queue into it. In
libstdc++, an empty std::deque still allocates its map array on construction,
so this was a real allocation on the hot path regardless of whether any
command (add/pause/resume/cancel) was actually pending -- which is the
common case, since those are rare next to data arriving.

Found via tools/bench's alloc-check, which is built in this change and
exists specifically to catch this class of bug (AGENT-CORE.md: "no
allocation in the curl write callback... checked in review and by a bench
assertion"): before this fix it reported thousands of allocations/sec under
a sustained transfer; after, single digits.

Fixed by checking `w.queue.empty()` under the lock before touching `local`
at all, so the deque is only constructed when there's actually something to
swap into it.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Q3QrF7rCt21bkAjt9BCDFQ
2026-09-11 13:12:57 +04:00
samiandClaude Sonnet 5 afaded85f8 core: carry credentials through the probe and its auth handshake
libcurl with CURLAUTH_ANY answers a 401/407 by resending the request with
an Authorization header. Two spots in net/ cut that short:

- http_client's header callback delivered the response head exactly once
  and latched `head_delivered`, so after an auth challenge the caller only
  ever saw the 401 — never the 2xx of the authenticated resend. Reset the
  latch when a fresh status line follows a delivered 401/407 (redirects
  never reach that path — their head is suppressed).

- the prober's head callbacks return DataAction::abort to skip the body,
  which also aborts the transfer mid-handshake. Return `proceed` for a
  401/407 when credentials were supplied, so curl's resend can run; the
  real status lands on the next header block.

Also give ProbeRequest an `auth` field (default scheme == none) and pass
it through base_request(), so a re-probe after a 401 can present the
credentials the user just entered. No behaviour change when no auth is
configured.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
2026-09-10 20:19:05 +04:00
samiandClaude Sonnet 5 bd1bc029f3 core: net/http_client — libcurl multi wrapper (stage 2)
One HttpClient owns a small pool of workers, each with its own CURLM; an
easy handle lives on one worker for its life. Public start/pause/resume/
cancel enqueue a command + curl_multi_wakeup(); callbacks (on_head /
on_data / on_finished) run on the worker thread and return a DataAction
(proceed / pause / abort). Covers redirects (final-response head only),
ranges (inclusive ByteRange -> CURLOPT_RANGE), proxy/SOCKS5, basic/digest
auth, cookies, verbatim headers, stall detection, a coarse recv-rate cap,
and a curl_share DNS/TLS cache across workers. CURLcode + HTTP status ->
vdm::Error in net/curl_error. A probe is on_head returning abort: it
finishes successfully (head_complete), not canceled.

Tests drive tools/testserver: full GET, ranged 206, redirect chain, 404
-> not_found, connection refused -> connect_failed, HEAD probe + ranged
0-0 probe (no body), cancel mid-transfer, pause/resume completes. Skip
cleanly if testserver isn't in the tree.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
2026-09-09 23:31:09 +04:00