mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-30 15:41:48 +00:00 
			
		
		
		
	Merge pull request #1091 from lightpanda-io/concurrent_blocking_imports
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				e2e-test / zig build release (push) Has been cancelled
				
			
		
			
				
	
				e2e-test / demo-scripts (push) Has been cancelled
				
			
		
			
				
	
				e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
				
			
		
			
				
	
				e2e-test / perf-fmt (push) Has been cancelled
				
			
		
			
				
	
				zig-test / zig build dev (push) Has been cancelled
				
			
		
			
				
	
				zig-test / browser fetch (push) Has been cancelled
				
			
		
			
				
	
				zig-test / zig test (push) Has been cancelled
				
			
		
			
				
	
				zig-test / perf-fmt (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	e2e-test / zig build release (push) Has been cancelled
				
			e2e-test / demo-scripts (push) Has been cancelled
				
			e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
				
			e2e-test / perf-fmt (push) Has been cancelled
				
			zig-test / zig build dev (push) Has been cancelled
				
			zig-test / browser fetch (push) Has been cancelled
				
			zig-test / zig test (push) Has been cancelled
				
			zig-test / perf-fmt (push) Has been cancelled
				
			Concurrent blocking imports
This commit is contained in:
		| @@ -67,8 +67,18 @@ client: *Http.Client, | |||||||
| allocator: Allocator, | allocator: Allocator, | ||||||
| buffer_pool: BufferPool, | buffer_pool: BufferPool, | ||||||
| script_pool: std.heap.MemoryPool(PendingScript), | script_pool: std.heap.MemoryPool(PendingScript), | ||||||
|  | sync_module_pool: std.heap.MemoryPool(SyncModule), | ||||||
| async_module_pool: std.heap.MemoryPool(AsyncModule), | async_module_pool: std.heap.MemoryPool(AsyncModule), | ||||||
|  |  | ||||||
|  | // We can download multiple sync modules in parallel, but we want to process | ||||||
|  | // then in order. We can't use an OrderList, like the other script types, | ||||||
|  | // because the order we load them might not be the order we want to process | ||||||
|  | // them in (I'm not sure this is true, but as far as I can tell, v8 doesn't | ||||||
|  | // make any guarantees about the list of sub-module dependencies it gives us | ||||||
|  | // So this is more like a cache. When a SyncModule is complete, it's put here | ||||||
|  | // and can be requested as needed. | ||||||
|  | sync_modules: std.StringHashMapUnmanaged(*SyncModule), | ||||||
|  |  | ||||||
| const OrderList = std.DoublyLinkedList; | const OrderList = std.DoublyLinkedList; | ||||||
|  |  | ||||||
| pub fn init(browser: *Browser, page: *Page) ScriptManager { | pub fn init(browser: *Browser, page: *Page) ScriptManager { | ||||||
| @@ -80,24 +90,42 @@ pub fn init(browser: *Browser, page: *Page) ScriptManager { | |||||||
|         .scripts = .{}, |         .scripts = .{}, | ||||||
|         .deferreds = .{}, |         .deferreds = .{}, | ||||||
|         .asyncs_ready = .{}, |         .asyncs_ready = .{}, | ||||||
|  |         .sync_modules = .empty, | ||||||
|         .is_evaluating = false, |         .is_evaluating = false, | ||||||
|         .allocator = allocator, |         .allocator = allocator, | ||||||
|         .client = browser.http_client, |         .client = browser.http_client, | ||||||
|         .static_scripts_done = false, |         .static_scripts_done = false, | ||||||
|         .buffer_pool = BufferPool.init(allocator, 5), |         .buffer_pool = BufferPool.init(allocator, 5), | ||||||
|         .script_pool = std.heap.MemoryPool(PendingScript).init(allocator), |         .script_pool = std.heap.MemoryPool(PendingScript).init(allocator), | ||||||
|  |         .sync_module_pool = std.heap.MemoryPool(SyncModule).init(allocator), | ||||||
|         .async_module_pool = std.heap.MemoryPool(AsyncModule).init(allocator), |         .async_module_pool = std.heap.MemoryPool(AsyncModule).init(allocator), | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn deinit(self: *ScriptManager) void { | pub fn deinit(self: *ScriptManager) void { | ||||||
|     self.reset(); |     self.reset(); | ||||||
|  |     var it = self.sync_modules.valueIterator(); | ||||||
|  |     while (it.next()) |value_ptr| { | ||||||
|  |         value_ptr.*.buffer.deinit(self.allocator); | ||||||
|  |         self.sync_module_pool.destroy(value_ptr.*); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     self.buffer_pool.deinit(); |     self.buffer_pool.deinit(); | ||||||
|     self.script_pool.deinit(); |     self.script_pool.deinit(); | ||||||
|  |     self.sync_module_pool.deinit(); | ||||||
|     self.async_module_pool.deinit(); |     self.async_module_pool.deinit(); | ||||||
|  |  | ||||||
|  |     self.sync_modules.deinit(self.allocator); | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn reset(self: *ScriptManager) void { | pub fn reset(self: *ScriptManager) void { | ||||||
|  |     var it = self.sync_modules.valueIterator(); | ||||||
|  |     while (it.next()) |value_ptr| { | ||||||
|  |         value_ptr.*.buffer.deinit(self.allocator); | ||||||
|  |         self.sync_module_pool.destroy(value_ptr.*); | ||||||
|  |     } | ||||||
|  |     self.sync_modules.clearRetainingCapacity(); | ||||||
|  |  | ||||||
|     self.clearList(&self.asyncs); |     self.clearList(&self.asyncs); | ||||||
|     self.clearList(&self.scripts); |     self.clearList(&self.scripts); | ||||||
|     self.clearList(&self.deferreds); |     self.clearList(&self.deferreds); | ||||||
| @@ -260,49 +288,70 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void { | |||||||
| // Unlike external modules which can only ever be executed after releasing an | // Unlike external modules which can only ever be executed after releasing an | ||||||
| // http handle, these are executed without there necessarily being a free handle. | // http handle, these are executed without there necessarily being a free handle. | ||||||
| // Thus, Http/Client.zig maintains a dedicated handle for these calls. | // Thus, Http/Client.zig maintains a dedicated handle for these calls. | ||||||
| pub fn blockingGet(self: *ScriptManager, url: [:0]const u8) !GetResult { | pub fn getModule(self: *ScriptManager, url: [:0]const u8) !void { | ||||||
|     std.debug.assert(self.is_blocking == false); |     const gop = try self.sync_modules.getOrPut(self.allocator, url); | ||||||
|  |     if (gop.found_existing) { | ||||||
|     self.is_blocking = true; |         // already requested | ||||||
|     defer { |         return; | ||||||
|         self.is_blocking = false; |  | ||||||
|  |  | ||||||
|         // we blocked evaluation while loading this script, there could be |  | ||||||
|         // scripts ready to process. |  | ||||||
|         self.evaluate(); |  | ||||||
|     } |     } | ||||||
|  |     errdefer _ = self.sync_modules.remove(url); | ||||||
|  |  | ||||||
|     var blocking = Blocking{ |     const sync = try self.sync_module_pool.create(); | ||||||
|         .allocator = self.allocator, |     errdefer self.sync_module_pool.destroy(sync); | ||||||
|         .buffer_pool = &self.buffer_pool, |  | ||||||
|     }; |     sync.* = .{ .manager = self }; | ||||||
|  |     gop.value_ptr.* = sync; | ||||||
|  |  | ||||||
|     var headers = try self.client.newHeaders(); |     var headers = try self.client.newHeaders(); | ||||||
|     try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers); |     try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers); | ||||||
|  |  | ||||||
|     var client = self.client; |     try self.client.request(.{ | ||||||
|     try client.blockingRequest(.{ |  | ||||||
|         .url = url, |         .url = url, | ||||||
|  |         .ctx = sync, | ||||||
|         .method = .GET, |         .method = .GET, | ||||||
|         .headers = headers, |         .headers = headers, | ||||||
|         .cookie_jar = self.page.cookie_jar, |         .cookie_jar = self.page.cookie_jar, | ||||||
|         .ctx = &blocking, |  | ||||||
|         .resource_type = .script, |         .resource_type = .script, | ||||||
|         .start_callback = if (log.enabled(.http, .debug)) Blocking.startCallback else null, |         .start_callback = if (log.enabled(.http, .debug)) SyncModule.startCallback else null, | ||||||
|         .header_callback = Blocking.headerCallback, |         .header_callback = SyncModule.headerCallback, | ||||||
|         .data_callback = Blocking.dataCallback, |         .data_callback = SyncModule.dataCallback, | ||||||
|         .done_callback = Blocking.doneCallback, |         .done_callback = SyncModule.doneCallback, | ||||||
|         .error_callback = Blocking.errorCallback, |         .error_callback = SyncModule.errorCallback, | ||||||
|     }); |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|     // rely on http's timeout settings to avoid an endless/long loop. | pub fn waitForModule(self: *ScriptManager, url: [:0]const u8) !GetResult { | ||||||
|  |     std.debug.assert(self.is_blocking == false); | ||||||
|  |     self.is_blocking = true; | ||||||
|  |     defer self.is_blocking = false; | ||||||
|  |  | ||||||
|  |     // Normally it's dangerous to hold on to map pointers. But here, the map | ||||||
|  |     // can't change. It's possible that by calling `tick`, other entries within | ||||||
|  |     // the map will have their value change, but the map itself is immutable | ||||||
|  |     // during this tick. | ||||||
|  |     const entry = self.sync_modules.getEntry(url) orelse { | ||||||
|  |         return error.UnknownModule; | ||||||
|  |     }; | ||||||
|  |     const sync = entry.value_ptr.*; | ||||||
|  |  | ||||||
|  |     var client = self.client; | ||||||
|     while (true) { |     while (true) { | ||||||
|         _ = try client.tick(200); |         switch (sync.state) { | ||||||
|         switch (blocking.state) { |             .loading => {}, | ||||||
|             .running => {}, |             .done => { | ||||||
|             .done => |result| return result, |                 // Our caller has its own higher level cache (caching the | ||||||
|  |                 // actual compiled module). There's no reason for us to keep this | ||||||
|  |                 defer self.sync_module_pool.destroy(sync); | ||||||
|  |                 defer self.sync_modules.removeByPtr(entry.key_ptr); | ||||||
|  |                 return .{ | ||||||
|  |                     .buffer = sync.buffer, | ||||||
|  |                     .buffer_pool = &self.buffer_pool, | ||||||
|  |                 }; | ||||||
|  |             }, | ||||||
|             .err => |err| return err, |             .err => |err| return err, | ||||||
|         } |         } | ||||||
|  |         // rely on http's timeout settings to avoid an endless/long loop. | ||||||
|  |         _ = try client.tick(200); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -333,7 +382,6 @@ pub fn getAsyncModule(self: *ScriptManager, url: [:0]const u8, cb: AsyncModule.C | |||||||
|         .error_callback = AsyncModule.errorCallback, |         .error_callback = AsyncModule.errorCallback, | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn staticScriptsDone(self: *ScriptManager) void { | pub fn staticScriptsDone(self: *ScriptManager) void { | ||||||
|     std.debug.assert(self.static_scripts_done == false); |     std.debug.assert(self.static_scripts_done == false); | ||||||
|     self.static_scripts_done = true; |     self.static_scripts_done = true; | ||||||
| @@ -783,16 +831,15 @@ const BufferPool = struct { | |||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const Blocking = struct { | const SyncModule = struct { | ||||||
|     allocator: Allocator, |     manager: *ScriptManager, | ||||||
|     buffer_pool: *BufferPool, |  | ||||||
|     state: State = .{ .running = {} }, |  | ||||||
|     buffer: std.ArrayListUnmanaged(u8) = .{}, |     buffer: std.ArrayListUnmanaged(u8) = .{}, | ||||||
|  |     state: State = .loading, | ||||||
|  |  | ||||||
|     const State = union(enum) { |     const State = union(enum) { | ||||||
|         running: void, |         done, | ||||||
|  |         loading, | ||||||
|         err: anyerror, |         err: anyerror, | ||||||
|         done: GetResult, |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     fn startCallback(transfer: *Http.Transfer) !void { |     fn startCallback(transfer: *Http.Transfer) !void { | ||||||
| @@ -808,12 +855,13 @@ const Blocking = struct { | |||||||
|             .content_type = header.contentType(), |             .content_type = header.contentType(), | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         var self: *SyncModule = @ptrCast(@alignCast(transfer.ctx)); | ||||||
|         if (header.status != 200) { |         if (header.status != 200) { | ||||||
|  |             self.finished(.{ .err = error.InvalidStatusCode }); | ||||||
|             return error.InvalidStatusCode; |             return error.InvalidStatusCode; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var self: *Blocking = @ptrCast(@alignCast(transfer.ctx)); |         self.buffer = self.manager.buffer_pool.get(); | ||||||
|         self.buffer = self.buffer_pool.get(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn dataCallback(transfer: *Http.Transfer, data: []const u8) !void { |     fn dataCallback(transfer: *Http.Transfer, data: []const u8) !void { | ||||||
| @@ -823,8 +871,8 @@ const Blocking = struct { | |||||||
|         //     .blocking = true, |         //     .blocking = true, | ||||||
|         // }); |         // }); | ||||||
|  |  | ||||||
|         var self: *Blocking = @ptrCast(@alignCast(transfer.ctx)); |         var self: *SyncModule = @ptrCast(@alignCast(transfer.ctx)); | ||||||
|         self.buffer.appendSlice(self.allocator, data) catch |err| { |         self.buffer.appendSlice(self.manager.allocator, data) catch |err| { | ||||||
|             log.err(.http, "SM.dataCallback", .{ |             log.err(.http, "SM.dataCallback", .{ | ||||||
|                 .err = err, |                 .err = err, | ||||||
|                 .len = data.len, |                 .len = data.len, | ||||||
| @@ -836,19 +884,17 @@ const Blocking = struct { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn doneCallback(ctx: *anyopaque) !void { |     fn doneCallback(ctx: *anyopaque) !void { | ||||||
|         var self: *Blocking = @ptrCast(@alignCast(ctx)); |         var self: *SyncModule = @ptrCast(@alignCast(ctx)); | ||||||
|         self.state = .{ .done = .{ |         self.finished(.done); | ||||||
|             .buffer = self.buffer, |  | ||||||
|             .buffer_pool = self.buffer_pool, |  | ||||||
|         } }; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn errorCallback(ctx: *anyopaque, err: anyerror) void { |     fn errorCallback(ctx: *anyopaque, err: anyerror) void { | ||||||
|         var self: *Blocking = @ptrCast(@alignCast(ctx)); |         var self: *SyncModule = @ptrCast(@alignCast(ctx)); | ||||||
|         self.state = .{ .err = err }; |         self.finished(.{ .err = err }); | ||||||
|         if (self.buffer.items.len > 0) { |  | ||||||
|             self.buffer_pool.release(self.buffer); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn finished(self: *SyncModule, state: State) void { | ||||||
|  |         self.state = state; | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -143,7 +143,7 @@ pub const Page = struct { | |||||||
|             .main_context = undefined, |             .main_context = undefined, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         self.main_context = try session.executor.createJsContext(&self.window, self, self, true, Env.GlobalMissingCallback.init(&self.polyfill_loader)); |         self.main_context = try session.executor.createJsContext(&self.window, self, &self.script_manager, true, Env.GlobalMissingCallback.init(&self.polyfill_loader)); | ||||||
|         try polyfill.preload(self.arena, self.main_context); |         try polyfill.preload(self.arena, self.main_context); | ||||||
|  |  | ||||||
|         try self.scheduler.add(self, runMicrotasks, 5, .{ .name = "page.microtasks" }); |         try self.scheduler.add(self, runMicrotasks, 5, .{ .name = "page.microtasks" }); | ||||||
| @@ -255,16 +255,6 @@ pub const Page = struct { | |||||||
|         try Node.prepend(head, &[_]Node.NodeOrText{.{ .node = parser.elementToNode(base) }}); |         try Node.prepend(head, &[_]Node.NodeOrText{.{ .node = parser.elementToNode(base) }}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn fetchModuleSource(ctx: *anyopaque, url: [:0]const u8) !ScriptManager.GetResult { |  | ||||||
|         const self: *Page = @ptrCast(@alignCast(ctx)); |  | ||||||
|         return self.script_manager.blockingGet(url); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn fetchAsyncModuleSource(ctx: *anyopaque, url: [:0]const u8, cb: ScriptManager.AsyncModule.Callback, cb_data: *anyopaque) !void { |  | ||||||
|         const self: *Page = @ptrCast(@alignCast(ctx)); |  | ||||||
|         return self.script_manager.getAsyncModule(url, cb, cb_data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn wait(self: *Page, wait_ms: i32) Session.WaitResult { |     pub fn wait(self: *Page, wait_ms: i32) Session.WaitResult { | ||||||
|         return self._wait(wait_ms) catch |err| { |         return self._wait(wait_ms) catch |err| { | ||||||
|             switch (err) { |             switch (err) { | ||||||
|   | |||||||
| @@ -693,7 +693,7 @@ const IsolatedWorld = struct { | |||||||
|         _ = try self.executor.createJsContext( |         _ = try self.executor.createJsContext( | ||||||
|             &page.window, |             &page.window, | ||||||
|             page, |             page, | ||||||
|             {}, |             null, | ||||||
|             false, |             false, | ||||||
|             Env.GlobalMissingCallback.init(&self.polyfill_loader), |             Env.GlobalMissingCallback.init(&self.polyfill_loader), | ||||||
|         ); |         ); | ||||||
|   | |||||||
| @@ -86,9 +86,6 @@ allocator: Allocator, | |||||||
| // request. These wil come and go with each request. | // request. These wil come and go with each request. | ||||||
| transfer_pool: std.heap.MemoryPool(Transfer), | transfer_pool: std.heap.MemoryPool(Transfer), | ||||||
|  |  | ||||||
| // see ScriptManager.blockingGet |  | ||||||
| blocking: Handle, |  | ||||||
|  |  | ||||||
| // To notify registered subscribers of events, the browser sets/nulls this for us. | // To notify registered subscribers of events, the browser sets/nulls this for us. | ||||||
| notification: ?*Notification = null, | notification: ?*Notification = null, | ||||||
|  |  | ||||||
| @@ -121,16 +118,12 @@ pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, opts: Http.Opts) !*Clie | |||||||
|     var handles = try Handles.init(allocator, client, ca_blob, &opts); |     var handles = try Handles.init(allocator, client, ca_blob, &opts); | ||||||
|     errdefer handles.deinit(allocator); |     errdefer handles.deinit(allocator); | ||||||
|  |  | ||||||
|     var blocking = try Handle.init(client, ca_blob, &opts); |  | ||||||
|     errdefer blocking.deinit(); |  | ||||||
|  |  | ||||||
|     client.* = .{ |     client.* = .{ | ||||||
|         .queue = .{}, |         .queue = .{}, | ||||||
|         .active = 0, |         .active = 0, | ||||||
|         .intercepted = 0, |         .intercepted = 0, | ||||||
|         .multi = multi, |         .multi = multi, | ||||||
|         .handles = handles, |         .handles = handles, | ||||||
|         .blocking = blocking, |  | ||||||
|         .allocator = allocator, |         .allocator = allocator, | ||||||
|         .http_proxy = opts.http_proxy, |         .http_proxy = opts.http_proxy, | ||||||
|         .user_agent = opts.user_agent, |         .user_agent = opts.user_agent, | ||||||
| @@ -142,7 +135,6 @@ pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, opts: Http.Opts) !*Clie | |||||||
|  |  | ||||||
| pub fn deinit(self: *Client) void { | pub fn deinit(self: *Client) void { | ||||||
|     self.abort(); |     self.abort(); | ||||||
|     self.blocking.deinit(); |  | ||||||
|     self.handles.deinit(self.allocator); |     self.handles.deinit(self.allocator); | ||||||
|  |  | ||||||
|     _ = c.curl_multi_cleanup(self.multi); |     _ = c.curl_multi_cleanup(self.multi); | ||||||
| @@ -263,12 +255,6 @@ pub fn fulfillTransfer(self: *Client, transfer: *Transfer, status: u16, headers: | |||||||
|     return transfer.fulfill(status, headers, body); |     return transfer.fulfill(status, headers, body); | ||||||
| } | } | ||||||
|  |  | ||||||
| // See ScriptManager.blockingGet |  | ||||||
| pub fn blockingRequest(self: *Client, req: Request) !void { |  | ||||||
|     const transfer = try self.makeTransfer(req); |  | ||||||
|     return self.makeRequest(&self.blocking, transfer); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn makeTransfer(self: *Client, req: Request) !*Transfer { | fn makeTransfer(self: *Client, req: Request) !*Transfer { | ||||||
|     errdefer req.headers.deinit(); |     errdefer req.headers.deinit(); | ||||||
|  |  | ||||||
| @@ -329,7 +315,6 @@ pub fn changeProxy(self: *Client, proxy: [:0]const u8) !void { | |||||||
|     for (self.handles.handles) |*h| { |     for (self.handles.handles) |*h| { | ||||||
|         try errorCheck(c.curl_easy_setopt(h.conn.easy, c.CURLOPT_PROXY, proxy.ptr)); |         try errorCheck(c.curl_easy_setopt(h.conn.easy, c.CURLOPT_PROXY, proxy.ptr)); | ||||||
|     } |     } | ||||||
|     try errorCheck(c.curl_easy_setopt(self.blocking.conn.easy, c.CURLOPT_PROXY, proxy.ptr)); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Same restriction as changeProxy. Should be ok since this is only called on | // Same restriction as changeProxy. Should be ok since this is only called on | ||||||
| @@ -341,7 +326,6 @@ pub fn restoreOriginalProxy(self: *Client) !void { | |||||||
|     for (self.handles.handles) |*h| { |     for (self.handles.handles) |*h| { | ||||||
|         try errorCheck(c.curl_easy_setopt(h.conn.easy, c.CURLOPT_PROXY, proxy)); |         try errorCheck(c.curl_easy_setopt(h.conn.easy, c.CURLOPT_PROXY, proxy)); | ||||||
|     } |     } | ||||||
|     try errorCheck(c.curl_easy_setopt(self.blocking.conn.easy, c.CURLOPT_PROXY, proxy)); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| fn makeRequest(self: *Client, handle: *Handle, transfer: *Transfer) !void { | fn makeRequest(self: *Client, handle: *Handle, transfer: *Transfer) !void { | ||||||
| @@ -504,7 +488,7 @@ fn endTransfer(self: *Client, transfer: *Transfer) void { | |||||||
|         log.fatal(.http, "Failed to remove handle", .{ .err = err }); |         log.fatal(.http, "Failed to remove handle", .{ .err = err }); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     self.handles.release(self, handle); |     self.handles.release(handle); | ||||||
|     transfer._handle = null; |     transfer._handle = null; | ||||||
|     self.active -= 1; |     self.active -= 1; | ||||||
| } | } | ||||||
| @@ -563,13 +547,7 @@ const Handles = struct { | |||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn release(self: *Handles, client: *Client, handle: *Handle) void { |     fn release(self: *Handles, handle: *Handle) void { | ||||||
|         if (handle == &client.blocking) { |  | ||||||
|             // the handle we've reserved for blocking request doesn't participate |  | ||||||
|             // int he in_use/available pools |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         var node = &handle.node; |         var node = &handle.node; | ||||||
|         self.in_use.remove(node); |         self.in_use.remove(node); | ||||||
|         node.prev = null; |         node.prev = null; | ||||||
| @@ -747,7 +725,7 @@ pub const Transfer = struct { | |||||||
|     fn deinit(self: *Transfer) void { |     fn deinit(self: *Transfer) void { | ||||||
|         self.req.headers.deinit(); |         self.req.headers.deinit(); | ||||||
|         if (self._handle) |handle| { |         if (self._handle) |handle| { | ||||||
|             self.client.handles.release(self.client, handle); |             self.client.handles.release(handle); | ||||||
|         } |         } | ||||||
|         self.arena.deinit(); |         self.arena.deinit(); | ||||||
|         self.client.transfer_pool.destroy(self); |         self.client.transfer_pool.destroy(self); | ||||||
|   | |||||||
| @@ -410,19 +410,9 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | |||||||
|             // when the handle_scope is freed. |             // when the handle_scope is freed. | ||||||
|             // We also maintain our own "context_arena" which allows us to have |             // We also maintain our own "context_arena" which allows us to have | ||||||
|             // all page related memory easily managed. |             // all page related memory easily managed. | ||||||
|             pub fn createJsContext(self: *ExecutionWorld, global: anytype, state: State, module_loader: anytype, enter: bool, global_callback: ?GlobalMissingCallback) !*JsContext { |             pub fn createJsContext(self: *ExecutionWorld, global: anytype, state: State, script_manager: ?*ScriptManager, enter: bool, global_callback: ?GlobalMissingCallback) !*JsContext { | ||||||
|                 std.debug.assert(self.js_context == null); |                 std.debug.assert(self.js_context == null); | ||||||
|  |  | ||||||
|                 const ModuleLoader = switch (@typeInfo(@TypeOf(module_loader))) { |  | ||||||
|                     .@"struct" => @TypeOf(module_loader), |  | ||||||
|                     .pointer => |ptr| ptr.child, |  | ||||||
|                     .void => ErrorModuleLoader, |  | ||||||
|                     else => @compileError("invalid module_loader"), |  | ||||||
|                 }; |  | ||||||
|  |  | ||||||
|                 // If necessary, turn a void context into something we can safely ptrCast |  | ||||||
|                 const safe_module_loader: *anyopaque = if (ModuleLoader == ErrorModuleLoader) @ptrCast(@constCast(&{})) else module_loader; |  | ||||||
|  |  | ||||||
|                 const env = self.env; |                 const env = self.env; | ||||||
|                 const isolate = env.isolate; |                 const isolate = env.isolate; | ||||||
|                 const Global = @TypeOf(global.*); |                 const Global = @TypeOf(global.*); | ||||||
| @@ -542,13 +532,9 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | |||||||
|                     .templates = &env.templates, |                     .templates = &env.templates, | ||||||
|                     .meta_lookup = &env.meta_lookup, |                     .meta_lookup = &env.meta_lookup, | ||||||
|                     .handle_scope = handle_scope, |                     .handle_scope = handle_scope, | ||||||
|  |                     .script_manager = script_manager, | ||||||
|                     .call_arena = self.call_arena.allocator(), |                     .call_arena = self.call_arena.allocator(), | ||||||
|                     .context_arena = self.context_arena.allocator(), |                     .context_arena = self.context_arena.allocator(), | ||||||
|                     .module_loader = .{ |  | ||||||
|                         .ptr = safe_module_loader, |  | ||||||
|                         .func = ModuleLoader.fetchModuleSource, |  | ||||||
|                         .async = ModuleLoader.fetchAsyncModuleSource, |  | ||||||
|                     }, |  | ||||||
|                     .global_callback = global_callback, |                     .global_callback = global_callback, | ||||||
|                 }; |                 }; | ||||||
|  |  | ||||||
| @@ -692,12 +678,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | |||||||
|             // the function that resolves/rejects them. |             // the function that resolves/rejects them. | ||||||
|             persisted_promise_resolvers: std.ArrayListUnmanaged(v8.Persistent(v8.PromiseResolver)) = .empty, |             persisted_promise_resolvers: std.ArrayListUnmanaged(v8.Persistent(v8.PromiseResolver)) = .empty, | ||||||
|  |  | ||||||
|             // When we need to load a resource (i.e. an external script), we call |  | ||||||
|             // this function to get the source. This is always a reference to the |  | ||||||
|             // Page's fetchModuleSource, but we use a function pointer |  | ||||||
|             // since this js module is decoupled from the browser implementation. |  | ||||||
|             module_loader: ModuleLoader, |  | ||||||
|  |  | ||||||
|             // Some Zig types have code to execute to cleanup |             // Some Zig types have code to execute to cleanup | ||||||
|             destructor_callbacks: std.ArrayListUnmanaged(DestructorCallback) = .empty, |             destructor_callbacks: std.ArrayListUnmanaged(DestructorCallback) = .empty, | ||||||
|  |  | ||||||
| @@ -711,15 +691,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | |||||||
|             // necessary to lookup/store the dependent module in the module_cache. |             // necessary to lookup/store the dependent module in the module_cache. | ||||||
|             module_identifier: std.AutoHashMapUnmanaged(u32, []const u8) = .empty, |             module_identifier: std.AutoHashMapUnmanaged(u32, []const u8) = .empty, | ||||||
|  |  | ||||||
|  |             // the page's script manager | ||||||
|  |             script_manager: ?*ScriptManager, | ||||||
|  |  | ||||||
|             // Global callback is called on missing property. |             // Global callback is called on missing property. | ||||||
|             global_callback: ?GlobalMissingCallback = null, |             global_callback: ?GlobalMissingCallback = null, | ||||||
|  |  | ||||||
|             const ModuleLoader = struct { |  | ||||||
|                 ptr: *anyopaque, |  | ||||||
|                 func: *const fn (ptr: *anyopaque, url: [:0]const u8) anyerror!ScriptManager.GetResult, |  | ||||||
|                 async: *const fn (ptr: *anyopaque, url: [:0]const u8, cb: ScriptManager.AsyncModule.Callback, cb_state: *anyopaque) anyerror!void, |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|             const ModuleEntry = struct { |             const ModuleEntry = struct { | ||||||
|                 // Can be null if we're asynchrously loading the module, in |                 // Can be null if we're asynchrously loading the module, in | ||||||
|                 // which case resolver_promise cannot be null. |                 // which case resolver_promise cannot be null. | ||||||
| @@ -861,8 +838,33 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | |||||||
|                 try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url); |                 try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url); | ||||||
|                 errdefer _ = self.module_identifier.remove(m.getIdentityHash()); |                 errdefer _ = self.module_identifier.remove(m.getIdentityHash()); | ||||||
|  |  | ||||||
|                 // resolveModuleCallback loads module's dependencies. |  | ||||||
|                 const v8_context = self.v8_context; |                 const v8_context = self.v8_context; | ||||||
|  |                 { | ||||||
|  |                     // Non-async modules are blocking. We can download them in | ||||||
|  |                     // parallel, but they need to be processed serially. So we | ||||||
|  |                     // want to get the list of dependent modules this module has | ||||||
|  |                     // and start downloading them asap. | ||||||
|  |                     const requests = m.getModuleRequests(); | ||||||
|  |                     const isolate = self.isolate; | ||||||
|  |                     for (0..requests.length()) |i| { | ||||||
|  |                         const req = requests.get(v8_context, @intCast(i)).castTo(v8.ModuleRequest); | ||||||
|  |                         const specifier = try jsStringToZig(self.call_arena, req.getSpecifier(), isolate); | ||||||
|  |                         const normalized_specifier = try @import("../url.zig").stitch( | ||||||
|  |                             self.call_arena, | ||||||
|  |                             specifier, | ||||||
|  |                             owned_url, | ||||||
|  |                             .{ .alloc = .if_needed, .null_terminated = true }, | ||||||
|  |                         ); | ||||||
|  |                         const gop = try self.module_cache.getOrPut(self.context_arena, normalized_specifier); | ||||||
|  |                         if (!gop.found_existing) { | ||||||
|  |                             const owned_specifier = try self.context_arena.dupeZ(u8, normalized_specifier); | ||||||
|  |                             gop.key_ptr.* = owned_specifier; | ||||||
|  |                             gop.value_ptr.* = .{}; | ||||||
|  |                             try self.script_manager.?.getModule(owned_specifier); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 if (try m.instantiate(v8_context, resolveModuleCallback) == false) { |                 if (try m.instantiate(v8_context, resolveModuleCallback) == false) { | ||||||
|                     return error.ModuleInstantiationError; |                     return error.ModuleInstantiationError; | ||||||
|                 } |                 } | ||||||
| @@ -891,14 +893,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | |||||||
|  |  | ||||||
|                 var gop = try self.module_cache.getOrPut(arena, owned_url); |                 var gop = try self.module_cache.getOrPut(arena, owned_url); | ||||||
|                 if (gop.found_existing) { |                 if (gop.found_existing) { | ||||||
|                     // only way for us to have found an existing entry, is if |                     // If we're here, it's because we had a cache entry, but no | ||||||
|                     // we're asynchronously loading this module |                     // module. This happens because both our synch and async | ||||||
|  |                     // module loaders create the entry to prevent concurrent | ||||||
|  |                     // loads of the same resource (like Go's Singleflight). | ||||||
|                     std.debug.assert(gop.value_ptr.module == null); |                     std.debug.assert(gop.value_ptr.module == null); | ||||||
|                     std.debug.assert(gop.value_ptr.module_promise == null); |                     std.debug.assert(gop.value_ptr.module_promise == null); | ||||||
|                     std.debug.assert(gop.value_ptr.resolver_promise != null); |  | ||||||
|  |  | ||||||
|                     // keep the resolver promise, it's doing the heavy lifting |  | ||||||
|                     // and any other async loads will be chained to it. |  | ||||||
|                     gop.value_ptr.module = persisted_module; |                     gop.value_ptr.module = persisted_module; | ||||||
|                     gop.value_ptr.module_promise = persisted_promise; |                     gop.value_ptr.module_promise = persisted_promise; | ||||||
|                 } else { |                 } else { | ||||||
| @@ -1639,8 +1640,29 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | |||||||
|                     .{ .alloc = .if_needed, .null_terminated = true }, |                     .{ .alloc = .if_needed, .null_terminated = true }, | ||||||
|                 ); |                 ); | ||||||
|  |  | ||||||
|                 const module_loader = self.module_loader; |                 const gop = try self.module_cache.getOrPut(self.context_arena, normalized_specifier); | ||||||
|                 var fetch_result = try module_loader.func(module_loader.ptr, normalized_specifier); |                 if (gop.found_existing) { | ||||||
|  |                     if (gop.value_ptr.module) |m| { | ||||||
|  |                         return m.handle; | ||||||
|  |                     } | ||||||
|  |                     // We don't have a module, but we do have a cache entry for it | ||||||
|  |                     // That means we're already trying to load it. We just have | ||||||
|  |                     // to wait for it to be done. | ||||||
|  |                 } else { | ||||||
|  |                     // I don't think it's possible for us to be here. This is | ||||||
|  |                     // only ever called by v8 when we evaluate a module. But | ||||||
|  |                     // before evaluating, we should have already started | ||||||
|  |                     // downloading all of the module's nested modules. So it | ||||||
|  |                     // should be impossible that this is the first time we've | ||||||
|  |                     // heard about this module. | ||||||
|  |                     // But, I'm not confident enough in that, and ther's little | ||||||
|  |                     // harm in handling this case. | ||||||
|  |                     @branchHint(.unlikely); | ||||||
|  |                     gop.value_ptr.* = .{}; | ||||||
|  |                     try self.script_manager.?.getModule(normalized_specifier); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var fetch_result = try self.script_manager.?.waitForModule(normalized_specifier); | ||||||
|                 defer fetch_result.deinit(); |                 defer fetch_result.deinit(); | ||||||
|  |  | ||||||
|                 var try_catch: TryCatch = undefined; |                 var try_catch: TryCatch = undefined; | ||||||
| @@ -1756,8 +1778,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | |||||||
|                     }; |                     }; | ||||||
|  |  | ||||||
|                     // Next, we need to actually load it. |                     // Next, we need to actually load it. | ||||||
|                     const module_loader = self.module_loader; |                     self.script_manager.?.getAsyncModule(specifier, dynamicModuleSourceCallback, state) catch |err| { | ||||||
|                     module_loader.async(module_loader.ptr, specifier, dynamicModuleSourceCallback, state) catch |err| { |  | ||||||
|                         const error_msg = v8.String.initUtf8(isolate, @errorName(err)); |                         const error_msg = v8.String.initUtf8(isolate, @errorName(err)); | ||||||
|                         _ = resolver.reject(self.v8_context, error_msg.toValue()); |                         _ = resolver.reject(self.v8_context, error_msg.toValue()); | ||||||
|                     }; |                     }; | ||||||
| @@ -4185,16 +4206,6 @@ const NoopInspector = struct { | |||||||
|     pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {} |     pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const ErrorModuleLoader = struct { |  | ||||||
|     pub fn fetchModuleSource(_: *anyopaque, _: [:0]const u8) !ScriptManager.GetResult { |  | ||||||
|         return error.NoModuleLoadConfigured; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn fetchAsyncModuleSource(_: *anyopaque, _: [:0]const u8, _: ScriptManager.AsyncModule.Callback, _: *anyopaque) !void { |  | ||||||
|         return error.NoModuleLoadConfigured; |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // If we have a struct: | // If we have a struct: | ||||||
| // const Cat = struct { | // const Cat = struct { | ||||||
| //    pub fn meow(self: *Cat) void { ... } | //    pub fn meow(self: *Cat) void { ... } | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ pub fn Runner(comptime State: type, comptime Global: type, comptime types: anyty | |||||||
|             self.js_context = try self.executor.createJsContext( |             self.js_context = try self.executor.createJsContext( | ||||||
|                 if (Global == void) &default_global else global, |                 if (Global == void) &default_global else global, | ||||||
|                 state, |                 state, | ||||||
|                 {}, |                 null, | ||||||
|                 true, |                 true, | ||||||
|                 null, |                 null, | ||||||
|             ); |             ); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Karl Seguin
					Karl Seguin