@@ -441,13 +441,18 @@ pub fn _request(_: *anyopaque, transfer: *Transfer) !void {
441441 return transfer .client .process (transfer );
442442}
443443
444- // Ownership contract: from the moment this function is entered, the
445- // HttpClient owns `req` — specifically `req.headers` (a curl_slist).
446- // On success, transfer.deinit eventually frees it. On any failure path
447- // inside this function, we free it before returning the error. Callers
448- // must NOT pair `request()` with their own `errdefer headers.deinit()`
449- // — that's a double-free.
444+ // HttpClient takes ownership of req.headers; do not pair with
445+ // `errdefer headers.deinit()`
450446pub fn request (self : * Client , req : Request , owner : ? * Owner ) ! void {
447+ _ = try self .requestT (req , owner );
448+ }
449+
450+ // Like `request`, but returns the created `*Transfer`. The caller does not own
451+ // the returned `*Transfer` and must thus use it with care. From the moment this
452+ // function is entered, the HttpClient owns `req` — specifically `req.headers`
453+ // On success, transfer.deinit eventually frees it. On any failure path inside
454+ // this function, we free it before returning the error.
455+ fn requestT (self : * Client , req : Request , owner : ? * Owner ) ! * Transfer {
451456 const arena = self .arena_pool .acquire (.small , "Request.arena" ) catch | err | {
452457 req .headers .deinit ();
453458 return err ;
@@ -502,6 +507,8 @@ pub fn request(self: *Client, req: Request, owner: ?*Owner) !void {
502507 }
503508 return err ;
504509 };
510+
511+ return transfer ;
505512}
506513
507514const SyncContext = struct {
@@ -565,10 +572,18 @@ pub fn syncRequest(self: *Client, allocator: Allocator, req: Request) !SyncRespo
565572 r .done_callback = SyncContext .doneCallback ;
566573 r .error_callback = SyncContext .errorCallback ;
567574 r .shutdown_callback = SyncContext .shutdownCallback ;
568- try self .request (r , null );
575+ const transfer = try self .requestT (r , null );
569576
570577 while (sync_ctx .completion == .in_progress ) {
571- try self .tick (200 , .sync_wait );
578+ self .tick (200 , .sync_wait ) catch | err | {
579+ if (sync_ctx .completion == .in_progress ) {
580+ // tick failed for a reason unrelated to our transfer (likely OOM or
581+ // client disconnect). transfer.req.ctx points at &sync_ctx on this
582+ // stack — abort to sever that reference before we return
583+ transfer .abort (err );
584+ }
585+ return err ;
586+ };
572587 }
573588
574589 switch (sync_ctx .completion ) {
0 commit comments