mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-31 09:46:05 -04:00
Replaces layering with an inline request pipeline, and transfer queue. This is meant to simplify the code, reduce footguns, and make future enhancements easier to implement (e.g. speculative parsing (which requires streaming to fully leverage)). Previously, HttpClient implemented deferring as a layer which required special pumping at various callsites (https://github.com/lightpanda-io/browser/pull/2855, https://github.com/lightpanda-io/browser/pull/2843, ...). In this new approach, deferring is built-into the HttpClient/Transfer's flow. Specifically, Transfers now maintain a queue of events (start, header, data, end, err) which are dispatched in HttpClient.tick. The result is that JS callbacks are never executed in the same stack that initiated the I/O, without needing guards or any external intervention. tTwo other benefits come from this. The first is that reentrant libcurl is eliminated. Instead of "libcurl -> callback", it's now "libcurl -> transfer event queue THEN tick -> callback" (we don't have to wait until the NEXT tick, we can just do it later in the tick). HttpClient still has to guard against libcurl reentrancy, but only because of how WebSocket is implemented, and we should be able to unify WebSockets to use an event queue too in a follow up PR (which will eliminate a bunch of guard code). The transfer queue should also be useful to re-implement streaming, since a data chunk is just an event in the transfer's event queue. For now, I kept it as a single buffered event to minimize the change. But since speculative parsing depends on this, and speculative parsing seems to be the next major performance tweak we can make, we need to re-introduce streaming. The other change is the removal of all other layers in favor of a pipeline. This works well with the existing Transfer.park mechanism, where a parked Transfer can restart the pipeline for a transfer in an arbitrary point (not as fancy as it sounds given how simple the flow is). The fallout from this is that we're no longer creating/wrapping contexts and callbacks: whatever the request was configured with is all we need. Because of this, HttpClient.Response is removed. There are no intermediary responses and no changing context, everything is just the Transfer. A smaller change is the addition of newRequest + transfer.submit(). The one-shot HttpClient.request and HttpClient.requestT still exist, but this explicit create + submit has some advantage. First, callers can use the transfer.arena (e.g. Frame using the transfer's arena to set the Referrer header). Second, callers can holds Transfer immediately, rather than waiting for their startCallback to be fired. An abort on an XMLHttpRequest called before the start of the transfer no longer silently fails.