mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 23:23:28 +00:00
Optimize the lifecycle of async requests
Async HTTP request work by emitting a "Progress" object to a callback. This object has a "done" flag which, when `true`, indicates that all data has been emitting and no future "Progress" objects will be sent. Callers like XHR buffer the response and wait for "done = true" to then process the request. The HTTP client relies on two important object pools: the connection and the state (with all the buffers for reading/writing). In its current implementation, the async flow does not release these pooled objects until the final callback has returned. At best, this is inefficient: we're keeping the connection and state objects checked out for longer than they have to be. At worse, it can lead to a deadlock. If the calling code issues a new request when done == true, we'll eventually run out of state objects in the pool. This commit now releases the state objects before emit the final "done" Progress message. For this to work, this final message will always have null data and an empty header object.
This commit is contained in:
@@ -468,7 +468,6 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
if (progress.first) {
|
if (progress.first) {
|
||||||
const header = progress.header;
|
const header = progress.header;
|
||||||
|
|
||||||
log.debug(.http, "request header", .{
|
log.debug(.http, "request header", .{
|
||||||
.source = "xhr",
|
.source = "xhr",
|
||||||
.url = self.url,
|
.url = self.url,
|
||||||
@@ -522,7 +521,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
log.info(.http, "request complete", .{
|
log.info(.http, "request complete", .{
|
||||||
.source = "xhr",
|
.source = "xhr",
|
||||||
.url = self.url,
|
.url = self.url,
|
||||||
.status = progress.header.status,
|
.status = self.response_status,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Not that the request is done, the http/client will free the request
|
// Not that the request is done, the http/client will free the request
|
||||||
|
|||||||
@@ -828,6 +828,7 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
|
|||||||
wait,
|
wait,
|
||||||
done,
|
done,
|
||||||
need_more,
|
need_more,
|
||||||
|
handler_error,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn deinit(self: *Self) void {
|
fn deinit(self: *Self) void {
|
||||||
@@ -966,12 +967,35 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
.wait => {},
|
.wait => {},
|
||||||
.need_more => self.receive(),
|
.need_more => self.receive(),
|
||||||
|
.handler_error => {
|
||||||
|
// handler should never have been called if we're redirecting
|
||||||
|
std.debug.assert(self.redirect == null);
|
||||||
|
self.request.requestCompleted(self.reader.response);
|
||||||
|
self.deinit();
|
||||||
|
return;
|
||||||
|
},
|
||||||
.done => {
|
.done => {
|
||||||
const redirect = self.redirect orelse {
|
const redirect = self.redirect orelse {
|
||||||
|
var handler = self.handler;
|
||||||
self.request.requestCompleted(self.reader.response);
|
self.request.requestCompleted(self.reader.response);
|
||||||
self.deinit();
|
self.deinit();
|
||||||
|
|
||||||
|
// Emit the done chunk. We expect the caller to do
|
||||||
|
// processing once the full request is completed. By
|
||||||
|
// emiting this AFTER we've relreased the connection,
|
||||||
|
// we free the connection and its state for re-use.
|
||||||
|
// If we don't do this this way, we can end up with
|
||||||
|
// _a lot_ of pending request/states.
|
||||||
|
// DO NOT USE `self` here, it's no longer valid.
|
||||||
|
handler.onHttpResponse(.{
|
||||||
|
.data = null,
|
||||||
|
.done = true,
|
||||||
|
.first = false,
|
||||||
|
.header = .{},
|
||||||
|
}) catch {};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.request.redirectAsync(redirect, self.loop, self.handler) catch |err| {
|
self.request.redirectAsync(redirect, self.loop, self.handler) catch |err| {
|
||||||
self.handleError("Setup async redirect", err);
|
self.handleError("Setup async redirect", err);
|
||||||
return;
|
return;
|
||||||
@@ -1116,22 +1140,22 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
|
|||||||
self.handler.onHttpResponse(.{
|
self.handler.onHttpResponse(.{
|
||||||
.data = chunk,
|
.data = chunk,
|
||||||
.first = first,
|
.first = first,
|
||||||
.done = next == null,
|
.done = false,
|
||||||
.header = reader.response,
|
.header = reader.response,
|
||||||
}) catch return .done;
|
}) catch return .handler_error;
|
||||||
|
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (result.data != null or done or would_be_first) {
|
} else if (result.data != null or would_be_first) {
|
||||||
// If we have data. Or if the request is done. Or if this is the
|
// If we have data. Or if the request is done. Or if this is the
|
||||||
// first time we have a complete header. Emit the chunk.
|
// first time we have a complete header. Emit the chunk.
|
||||||
self.handler.onHttpResponse(.{
|
self.handler.onHttpResponse(.{
|
||||||
.done = done,
|
.done = false,
|
||||||
.data = result.data,
|
.data = result.data,
|
||||||
.first = would_be_first,
|
.first = would_be_first,
|
||||||
.header = reader.response,
|
.header = reader.response,
|
||||||
}) catch return .done;
|
}) catch return .handler_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (done == true) {
|
if (done == true) {
|
||||||
@@ -3135,7 +3159,8 @@ const CaptureHandler = struct {
|
|||||||
const progress = try progress_;
|
const progress = try progress_;
|
||||||
const allocator = self.response.arena.allocator();
|
const allocator = self.response.arena.allocator();
|
||||||
try self.response.body.appendSlice(allocator, progress.data orelse "");
|
try self.response.body.appendSlice(allocator, progress.data orelse "");
|
||||||
if (progress.done) {
|
if (progress.first) {
|
||||||
|
std.debug.assert(!progress.done);
|
||||||
self.response.status = progress.header.status;
|
self.response.status = progress.header.status;
|
||||||
try self.response.headers.ensureTotalCapacity(allocator, progress.header.headers.items.len);
|
try self.response.headers.ensureTotalCapacity(allocator, progress.header.headers.items.len);
|
||||||
for (progress.header.headers.items) |header| {
|
for (progress.header.headers.items) |header| {
|
||||||
@@ -3144,6 +3169,9 @@ const CaptureHandler = struct {
|
|||||||
.value = try allocator.dupe(u8, header.value),
|
.value = try allocator.dupe(u8, header.value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress.done) {
|
||||||
self.reset.set();
|
self.reset.set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user