Merge pull request #1689 from lightpanda-io/protect_xhr_abort_during_callback

Protect against transfer.abort() being called during callback
This commit is contained in:
Karl Seguin
2026-03-02 17:42:07 +08:00
committed by GitHub
4 changed files with 62 additions and 13 deletions

View File

@@ -496,8 +496,12 @@ fn waitForInterceptedResponse(self: *Client, transfer: *Transfer) !bool {
// cases, the interecptor is expected to call resume to continue the transfer
// or transfer.abort() to abort it.
fn process(self: *Client, transfer: *Transfer) !void {
if (self.handles.get()) |conn| {
return self.makeRequest(conn, transfer);
// libcurl doesn't allow recursive calls, if we're in a `perform()` operation
// then we _have_ to queue this.
if (self.handles.performing == false) {
if (self.handles.get()) |conn| {
return self.makeRequest(conn, transfer);
}
}
self.queue.append(&transfer._node);
@@ -789,9 +793,14 @@ fn processMessages(self: *Client) !bool {
if (msg.err) |err| {
requestFailed(transfer, err, true);
} else blk: {
// In case of request w/o data, we need to call the header done
// callback now.
// make sure the transfer can't be immediately aborted from a callback
// since we still need it here.
transfer._performing = true;
defer transfer._performing = false;
if (!transfer._header_done_called) {
// In case of request w/o data, we need to call the header done
// callback now.
const proceed = transfer.headerDoneCallback(&msg.conn) catch |err| {
log.err(.http, "header_done_callback", .{ .err = err });
requestFailed(transfer, err, true);
@@ -937,6 +946,7 @@ pub const Transfer = struct {
// number of times the transfer has been tried.
// incremented by reset func.
_tries: u8 = 0,
_performing: bool = false,
// for when a Transfer is queued in the client.queue
_node: std.DoublyLinkedList.Node = .{},
@@ -1041,13 +1051,9 @@ pub const Transfer = struct {
pub fn abort(self: *Transfer, err: anyerror) void {
requestFailed(self, err, true);
if (self._conn == null) {
self.deinit();
return;
}
const client = self.client;
if (client.handles.performing) {
if (self._performing or client.handles.performing) {
// We're currently in a curl_multi_perform. We cannot call endTransfer
// as that calls curl_multi_remove_handle, and you can't do that
// from a curl callback. Instead, we flag this transfer and all of