Protect against transfer.abort() being called during callback

This was already handled in most cases, but not for a body-less response. It's
safe to call transfer.abort() during a callback, so long as the performing flag
is set to true. This was set during the normal libcurl callbacks, but for a
body-less response, we manually invoke the header_done_callback and were not
setting the performing flag.
This commit is contained in:
Karl Seguin
2026-03-02 11:44:42 +08:00
parent 516335e0ed
commit 82e3f126ff
3 changed files with 59 additions and 19 deletions

View File

@@ -252,3 +252,34 @@
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState); testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
}); });
</script> </script>
<script id=xhr_abort_callback_nobody>
testing.async(async (restore) => {
const req = new XMLHttpRequest();
let abortFired = false;
let errorFired = false;
let loadEndFired = false;
await new Promise((resolve) => {
req.onabort = () => { abortFired = true; };
req.onerror = () => { errorFired = true; };
req.onloadend = () => {
loadEndFired = true;
resolve();
};
req.open('GET', 'http://127.0.0.1:9582/xhr_empty');
req.onreadystatechange = (e) => {
req.abort();
}
req.send();
});
restore();
testing.expectEqual(true, abortFired);
testing.expectEqual(true, errorFired);
testing.expectEqual(true, loadEndFired);
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
});
</script>

View File

@@ -789,25 +789,30 @@ fn processMessages(self: *Client) !bool {
if (msg.err) |err| { if (msg.err) |err| {
requestFailed(transfer, err, true); requestFailed(transfer, err, true);
} else blk: { } else blk: {
// In case of request w/o data, we need to call the header done {
// callback now. self.handles.performing = true;
if (!transfer._header_done_called) { defer self.handles.performing = false;
const proceed = transfer.headerDoneCallback(&msg.conn) catch |err| {
log.err(.http, "header_done_callback", .{ .err = err }); // In case of request w/o data, we need to call the header done
// callback now.
if (!transfer._header_done_called) {
const proceed = transfer.headerDoneCallback(&msg.conn) catch |err| {
log.err(.http, "header_done_callback", .{ .err = err });
requestFailed(transfer, err, true);
continue;
};
if (!proceed) {
requestFailed(transfer, error.Abort, true);
break :blk;
}
}
transfer.req.done_callback(transfer.ctx) catch |err| {
// transfer isn't valid at this point, don't use it.
log.err(.http, "done_callback", .{ .err = err });
requestFailed(transfer, err, true); requestFailed(transfer, err, true);
continue; continue;
}; };
if (!proceed) {
requestFailed(transfer, error.Abort, true);
break :blk;
}
} }
transfer.req.done_callback(transfer.ctx) catch |err| {
// transfer isn't valid at this point, don't use it.
log.err(.http, "done_callback", .{ .err = err });
requestFailed(transfer, err, true);
continue;
};
transfer.req.notification.dispatch(.http_request_done, &.{ transfer.req.notification.dispatch(.http_request_done, &.{
.transfer = transfer, .transfer = transfer,
@@ -1041,10 +1046,6 @@ pub const Transfer = struct {
pub fn abort(self: *Transfer, err: anyerror) void { pub fn abort(self: *Transfer, err: anyerror) void {
requestFailed(self, err, true); requestFailed(self, err, true);
if (self._conn == null) {
self.deinit();
return;
}
const client = self.client; const client = self.client;
if (client.handles.performing) { if (client.handles.performing) {

View File

@@ -561,6 +561,14 @@ fn testHTTPHandler(req: *std.http.Server.Request) !void {
}); });
} }
if (std.mem.eql(u8, path, "/xhr_empty")) {
return req.respond("", .{
.extra_headers = &.{
.{ .name = "Content-Type", .value = "text/html; charset=utf-8" },
},
});
}
if (std.mem.eql(u8, path, "/xhr/json")) { if (std.mem.eql(u8, path, "/xhr/json")) {
return req.respond("{\"over\":\"9000!!!\",\"updated_at\":1765867200000}", .{ return req.respond("{\"over\":\"9000!!!\",\"updated_at\":1765867200000}", .{
.extra_headers = &.{ .extra_headers = &.{