Skip to content

Commit 48c25c3

Browse files
committed
Removing blocking code async HTTP request
The HTTP Client has a state pool. It blocks when we've exceeded max_concurrency. This can block processing forever. A simple way to reproduce this is to go into the demo cdp.js, and execute the XHR request 5 times (loading json/product.json) To some degree, I think this is a result of weird / non-intuitive execution flow. If you exec a JS with 100 XHR requests, it'll call our XHR _send function but none of these will execute until the loop is run (after the script is done being executed). This can result in poor utilization of our connection and state pool. For an async request, getting the *Request object is itself now asynchronous. If no state is available, we use the Loop's timeout (at 20ms) to keep checking for an available state.
1 parent 3a5aa87 commit 48c25c3

File tree

4 files changed

+207
-49
lines changed

4 files changed

+207
-49
lines changed

src/app.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ pub const App = struct {
5252
.telemetry = undefined,
5353
.app_dir_path = app_dir_path,
5454
.notification = notification,
55-
.http_client = try HttpClient.init(allocator, 5, .{
55+
.http_client = try HttpClient.init(allocator, .{
56+
.max_concurrent = 3,
5657
.http_proxy = config.http_proxy,
5758
.tls_verify_host = config.tls_verify_host,
5859
}),

src/browser/page.zig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,9 @@ pub const Page = struct {
113113
.cookie_jar = &session.cookie_jar,
114114
.microtask_node = .{ .func = microtaskCallback },
115115
.window_clicked_event_node = .{ .func = windowClicked },
116-
.request_factory = browser.http_client.requestFactory(browser.notification),
116+
.request_factory = browser.http_client.requestFactory(.{
117+
.notification = browser.notification,
118+
}),
117119
.scope = undefined,
118120
.module_map = .empty,
119121
};

src/browser/xhr/xhr.zig

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const Mime = @import("../mime.zig").Mime;
3030
const parser = @import("../netsurf.zig");
3131
const http = @import("../../http/client.zig");
3232
const Page = @import("../page.zig").Page;
33+
const Loop = @import("../../runtime/loop.zig").Loop;
3334
const CookieJar = @import("../storage/storage.zig").CookieJar;
3435

3536
// XHR interfaces
@@ -78,6 +79,7 @@ const XMLHttpRequestBodyInit = union(enum) {
7879

7980
pub const XMLHttpRequest = struct {
8081
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
82+
loop: *Loop,
8183
arena: Allocator,
8284
request: ?*http.Request = null,
8385

@@ -91,6 +93,7 @@ pub const XMLHttpRequest = struct {
9193
sync: bool = true,
9294
err: ?anyerror = null,
9395
last_dispatch: i64 = 0,
96+
request_body: ?[]const u8 = null,
9497

9598
cookie_jar: *CookieJar,
9699
// the URI of the page where this request is originating from
@@ -241,12 +244,13 @@ pub const XMLHttpRequest = struct {
241244
pub fn constructor(page: *Page) !XMLHttpRequest {
242245
const arena = page.arena;
243246
return .{
247+
.url = null,
244248
.arena = arena,
249+
.loop = page.loop,
245250
.headers = Headers.init(arena),
246251
.response_headers = Headers.init(arena),
247252
.method = undefined,
248253
.state = .unsent,
249-
.url = null,
250254
.origin_url = &page.url,
251255
.cookie_jar = page.cookie_jar,
252256
};
@@ -422,18 +426,24 @@ pub const XMLHttpRequest = struct {
422426
log.debug(.http, "request", .{ .method = self.method, .url = self.url, .source = "xhr" });
423427

424428
self.send_flag = true;
429+
if (body) |b| {
430+
self.request_body = try self.arena.dupe(u8, b);
431+
}
425432

426-
self.request = try page.request_factory.create(self.method, &self.url.?.uri);
427-
var request = self.request.?;
428-
errdefer request.deinit();
433+
try page.request_factory.initAsync(page.arena, self.method, &self.url.?.uri, self, onHttpRequestReady, self.loop,);
434+
}
435+
436+
fn onHttpRequestReady(ctx: *anyopaque, request: *http.Request) !void {
437+
// on error, our caller will cleanup request
438+
const self: *XMLHttpRequest = @alignCast(@ptrCast(ctx));
429439

430440
for (self.headers.list.items) |hdr| {
431441
try request.addHeader(hdr.name, hdr.value, .{});
432442
}
433443

434444
{
435445
var arr: std.ArrayListUnmanaged(u8) = .{};
436-
try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(page.arena), .{
446+
try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(self.arena), .{
437447
.navigation = false,
438448
.origin_uri = &self.origin_url.uri,
439449
});
@@ -447,14 +457,15 @@ pub const XMLHttpRequest = struct {
447457
// if the request method is GET or HEAD.
448458
// https://xhr.spec.whatwg.org/#the-send()-method
449459
// var used_body: ?XMLHttpRequestBodyInit = null;
450-
if (body) |b| {
460+
if (self.request_body) |b| {
451461
if (self.method != .GET and self.method != .HEAD) {
452-
request.body = try page.arena.dupe(u8, b);
462+
request.body = b;
453463
try request.addHeader("Content-Type", "text/plain; charset=UTF-8", .{});
454464
}
455465
}
456466

457-
try request.sendAsync(page.loop, self, .{});
467+
try request.sendAsync(self.loop, self, .{});
468+
self.request = request;
458469
}
459470

460471
pub fn onHttpResponse(self: *XMLHttpRequest, progress_: anyerror!http.Progress) !void {
@@ -468,6 +479,7 @@ pub const XMLHttpRequest = struct {
468479

469480
if (progress.first) {
470481
const header = progress.header;
482+
471483
log.debug(.http, "request header", .{
472484
.source = "xhr",
473485
.url = self.url,

0 commit comments

Comments
 (0)