Skip to content

Commit a84708e

Browse files
authored
Merge pull request #1359 from lightpanda-io/crash_handler
Improve crash handling
2 parents 6b6c0e9 + 2894bef commit a84708e

38 files changed

+377
-112
lines changed

build.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pub fn build(b: *Build) !void {
4848
.sanitize_c = enable_csan,
4949
.sanitize_thread = enable_tsan,
5050
});
51+
mod.addImport("lightpanda", mod); // allow circular "lightpanda" import
5152

5253
try addDependencies(b, mod, opts, prebuilt_v8_path);
5354

src/Notification.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20+
const lp = @import("lightpanda");
2021

2122
const log = @import("log.zig");
2223
const Page = @import("browser/Page.zig");
@@ -241,7 +242,7 @@ pub fn unregister(self: *Notification, comptime event: EventType, receiver: anyt
241242
if (listeners.items.len == 0) {
242243
listeners.deinit(self.allocator);
243244
const removed = self.listeners.remove(@intFromPtr(receiver));
244-
std.debug.assert(removed == true);
245+
lp.assert(removed == true, "Notification.unregister", .{ .type = event });
245246
}
246247
}
247248

src/Server.zig

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20+
const lp = @import("lightpanda");
2021
const builtin = @import("builtin");
2122

2223
const net = std.net;
@@ -157,7 +158,7 @@ fn readLoop(self: *Server, socket: posix.socket_t, timeout_ms: u32) !void {
157158
});
158159
defer http.removeCDPClient();
159160

160-
std.debug.assert(client.mode == .http);
161+
lp.assert(client.mode == .http, "Server.readLoop invalid mode", .{});
161162
while (true) {
162163
if (http.poll(timeout_ms) != .cdp_socket) {
163164
log.info(.app, "CDP timeout", .{});
@@ -236,7 +237,7 @@ pub const Client = struct {
236237
const socket_flags = try posix.fcntl(socket, posix.F.GETFL, 0);
237238
const nonblocking = @as(u32, @bitCast(posix.O{ .NONBLOCK = true }));
238239
// we expect the socket to come to us as nonblocking
239-
std.debug.assert(socket_flags & nonblocking == nonblocking);
240+
lp.assert(socket_flags & nonblocking == nonblocking, "Client.init blocking", .{});
240241

241242
var reader = try Reader(true).init(server.allocator);
242243
errdefer reader.deinit();
@@ -311,7 +312,7 @@ pub const Client = struct {
311312
}
312313

313314
fn processHTTPRequest(self: *Client) !bool {
314-
std.debug.assert(self.reader.pos == 0);
315+
lp.assert(self.reader.pos == 0, "Client.HTTP pos", .{ .pos = self.reader.pos });
315316
const request = self.reader.buf[0..self.reader.len];
316317

317318
if (request.len > MAX_HTTP_REQUEST_SIZE) {
@@ -592,8 +593,7 @@ pub const Client = struct {
592593
// blocking and switch it back to non-blocking after the write
593594
// is complete. Doesn't seem particularly efficiently, but
594595
// this should virtually never happen.
595-
std.debug.assert(changed_to_blocking == false);
596-
log.debug(.app, "CDP write would block", .{});
596+
lp.assert(changed_to_blocking == false, "Client.double block", .{});
597597
changed_to_blocking = true;
598598
_ = try posix.fcntl(self.socket, posix.F.SETFL, self.socket_flags & ~@as(u32, @bitCast(posix.O{ .NONBLOCK = true })));
599599
continue :LOOP;
@@ -821,7 +821,7 @@ fn Reader(comptime EXPECT_MASK: bool) type {
821821
const pos = self.pos;
822822
const len = self.len;
823823

824-
std.debug.assert(pos <= len);
824+
lp.assert(pos <= len, "Client.Reader.compact precondition", .{ .pos = pos, .len = len });
825825

826826
// how many (if any) partial bytes do we have
827827
const partial_bytes = len - pos;
@@ -842,7 +842,7 @@ fn Reader(comptime EXPECT_MASK: bool) type {
842842
const next_message_len = length_meta.@"1";
843843
// if this isn't true, then we have a full message and it
844844
// should have been processed.
845-
std.debug.assert(next_message_len > partial_bytes);
845+
lp.assert(pos <= len, "Client.Reader.compact postcondition", .{ .next_len = next_message_len, .partial = partial_bytes });
846846

847847
const missing_bytes = next_message_len - partial_bytes;
848848

@@ -929,7 +929,7 @@ fn fillWebsocketHeader(buf: std.ArrayListUnmanaged(u8)) []const u8 {
929929
// makes the assumption that our caller reserved the first
930930
// 10 bytes for the header
931931
fn websocketHeader(buf: []u8, op_code: OpCode, payload_len: usize) []const u8 {
932-
std.debug.assert(buf.len == 10);
932+
lp.assert(buf.len == 10, "Websocket.Header", .{ .len = buf.len });
933933

934934
const len = payload_len;
935935
buf[0] = 128 | @intFromEnum(op_code); // fin | opcode

src/browser/Factory.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20-
const assert = std.debug.assert;
2120
const builtin = @import("builtin");
2221
const reflect = @import("reflect.zig");
23-
const IS_DEBUG = builtin.mode == .Debug;
2422

2523
const log = @import("../log.zig");
2624
const String = @import("../string.zig").String;
@@ -38,6 +36,9 @@ const XMLHttpRequestEventTarget = @import("webapi/net/XMLHttpRequestEventTarget.
3836
const Blob = @import("webapi/Blob.zig");
3937
const AbstractRange = @import("webapi/AbstractRange.zig");
4038

39+
const IS_DEBUG = builtin.mode == .Debug;
40+
const assert = std.debug.assert;
41+
4142
const Factory = @This();
4243
_page: *Page,
4344
_slab: SlabAllocator,

src/browser/Page.zig

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
const std = @import("std");
2020
const JS = @import("js/js.zig");
21+
const lp = @import("lightpanda");
2122
const builtin = @import("builtin");
2223

2324
const Allocator = std.mem.Allocator;
@@ -786,7 +787,9 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
786787
// an extra socket, so it should not be possibl to
787788
// get an cdp_socket message when exit_when_done
788789
// is true.
789-
std.debug.assert(exit_when_done == false);
790+
if (IS_DEBUG) {
791+
std.debug.assert(exit_when_done == false);
792+
}
790793

791794
// data on a socket we aren't handling, return to caller
792795
return .cdp_socket;
@@ -822,7 +825,9 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
822825
// we don't need to consider http_client.intercepted here
823826
// because exit_when_done is true, and that can only be
824827
// the case when interception isn't possible.
825-
std.debug.assert(http_client.intercepted == 0);
828+
if (comptime IS_DEBUG) {
829+
std.debug.assert(http_client.intercepted == 0);
830+
}
826831

827832
const ms = ms_to_next_task orelse blk: {
828833
if (wait_ms - ms_remaining < 100) {
@@ -1019,7 +1024,9 @@ fn getElementIdMap(page: *Page, node: *Node) ElementIdMaps {
10191024
};
10201025
}
10211026
// Detached nodes should not have IDs registered
1022-
std.debug.assert(false);
1027+
if (IS_DEBUG) {
1028+
std.debug.assert(false);
1029+
}
10231030
return .{
10241031
.lookup = &page.document._elements_by_id,
10251032
.removed_ids = &page.document._removed_ids,
@@ -1265,14 +1272,14 @@ pub fn deliverSlotchangeEvents(self: *Page) void {
12651272
}
12661273

12671274
fn notifyNetworkIdle(self: *Page) void {
1268-
std.debug.assert(self._notified_network_idle == .done);
1275+
lp.assert(self._notified_network_idle == .done, "Page.notifyNetworkIdle", .{});
12691276
self._session.browser.notification.dispatch(.page_network_idle, &.{
12701277
.timestamp = timestamp(.monotonic),
12711278
});
12721279
}
12731280

12741281
fn notifyNetworkAlmostIdle(self: *Page) void {
1275-
std.debug.assert(self._notified_network_almost_idle == .done);
1282+
lp.assert(self._notified_network_almost_idle == .done, "Page.notifyNetworkAlmostIdle", .{});
12761283
self._session.browser.notification.dispatch(.page_network_almost_idle, &.{
12771284
.timestamp = timestamp(.monotonic),
12781285
});
@@ -1298,7 +1305,7 @@ pub fn appendNew(self: *Page, parent: *Node, child: Node.NodeOrText) !void {
12981305
},
12991306
};
13001307

1301-
std.debug.assert(node._parent == null);
1308+
lp.assert(node._parent == null, "Page.appendNew", .{});
13021309
try self._insertNodeRelative(true, parent, node, .append, .{
13031310
// this opts has no meaning since we're passing `true` as the first
13041311
// parameter, which indicates this comes from the parser, and has its
@@ -2213,7 +2220,7 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts
22132220
const children = parent._children.?;
22142221
switch (children.*) {
22152222
.one => |n| {
2216-
std.debug.assert(n == child);
2223+
lp.assert(n == child, "Page.removeNode.one", .{});
22172224
parent._children = null;
22182225
self._factory.destroy(children);
22192226
},
@@ -2340,7 +2347,8 @@ pub fn insertNodeRelative(self: *Page, parent: *Node, child: *Node, relative: In
23402347
}
23412348
pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Node, child: *Node, relative: InsertNodeRelative, opts: InsertNodeOpts) !void {
23422349
// caller should have made sure this was the case
2343-
std.debug.assert(child._parent == null);
2350+
2351+
lp.assert(child._parent == null, "Page.insertNodeRelative parent", .{ .url = self.url });
23442352

23452353
const children = blk: {
23462354
// expand parent._children so that it can take another child
@@ -2369,14 +2377,14 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
23692377
},
23702378
.after => |ref_node| {
23712379
// caller should have made sure this was the case
2372-
std.debug.assert(ref_node._parent.? == parent);
2380+
lp.assert(ref_node._parent.? == parent, "Page.insertNodeRelative after", .{ .url = self.url });
23732381
// if ref_node is in parent, and expanded _children above to
23742382
// accommodate another child, then `children` must be a list
23752383
children.list.insertAfter(&ref_node._child_link, &child._child_link);
23762384
},
23772385
.before => |ref_node| {
23782386
// caller should have made sure this was the case
2379-
std.debug.assert(ref_node._parent.? == parent);
2387+
lp.assert(ref_node._parent.? == parent, "Page.insertNodeRelative before", .{ .url = self.url });
23802388
// if ref_node is in parent, and expanded _children above to
23812389
// accommodate another child, then `children` must be a list
23822390
children.list.insertBefore(&ref_node._child_link, &child._child_link);
@@ -2654,7 +2662,7 @@ pub fn parseHtmlAsChildren(self: *Page, node: *Node, html: []const u8) !void {
26542662
// https://github.com/servo/html5ever/issues/583
26552663
const children = node._children orelse return;
26562664
const first = children.one;
2657-
std.debug.assert(first.is(Element.Html.Html) != null);
2665+
lp.assert(first.is(Element.Html.Html) != null, "Page.parseHtmlAsChildren root", .{ .type = first._type });
26582666
node._children = first._children;
26592667

26602668
if (self.hasMutationObservers()) {

src/browser/Scheduler.zig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ fn runQueue(self: *Scheduler, queue: *Queue) !?u64 {
9595

9696
if (repeat_in_ms) |ms| {
9797
// Task cannot be repeated immediately, and they should know that
98-
std.debug.assert(ms != 0);
98+
if (comptime IS_DEBUG) {
99+
std.debug.assert(ms != 0);
100+
}
99101
task.run_at = now + ms;
100102
try self.low_priority.add(task);
101103
}

src/browser/ScriptManager.zig

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20+
const lp = @import("lightpanda");
2021
const builtin = @import("builtin");
2122

2223
const js = @import("js/js.zig");
@@ -484,7 +485,7 @@ pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.C
484485
// Called from the Page to let us know it's done parsing the HTML. Necessary that
485486
// we know this so that we know that we can start evaluating deferred scripts.
486487
pub fn staticScriptsDone(self: *ScriptManager) void {
487-
std.debug.assert(self.static_scripts_done == false);
488+
lp.assert(self.static_scripts_done == false, "ScriptManager.staticScriptsDone", .{});
488489
self.static_scripts_done = true;
489490
self.evaluate();
490491
}
@@ -675,7 +676,7 @@ pub const Script = struct {
675676
// set `CURLOPT_SUPPRESS_CONNECT_HEADERS` and CONNECT to a proxy, this
676677
// will fail. This assertion exists to catch incorrect assumptions about
677678
// how libcurl works, or about how we've configured it.
678-
std.debug.assert(self.source.remote.capacity == 0);
679+
lp.assert(self.source.remote.capacity == 0, "ScriptManager.HeaderCallback", .{ .capacity = self.source.remote.capacity });
679680
var buffer = self.manager.buffer_pool.get();
680681
if (transfer.getContentLength()) |cl| {
681682
try buffer.ensureTotalCapacity(self.manager.allocator, cl);
@@ -750,10 +751,12 @@ pub const Script = struct {
750751

751752
fn eval(self: *Script, page: *Page) void {
752753
// never evaluated, source is passed back to v8, via callbacks.
753-
std.debug.assert(self.mode != .import_async);
754+
if (comptime IS_DEBUG) {
755+
std.debug.assert(self.mode != .import_async);
754756

755-
// never evaluated, source is passed back to v8 when asked for it.
756-
std.debug.assert(self.mode != .import);
757+
// never evaluated, source is passed back to v8 when asked for it.
758+
std.debug.assert(self.mode != .import);
759+
}
757760

758761
if (page.isGoingAway()) {
759762
// don't evaluate scripts for a dying page.

src/browser/Session.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20+
const lp = @import("lightpanda");
2021

2122
const log = @import("../log.zig");
2223

@@ -92,7 +93,7 @@ pub fn deinit(self: *Session) void {
9293
// NOTE: the caller is not the owner of the returned value,
9394
// the pointer on Page is just returned as a convenience
9495
pub fn createPage(self: *Session) !*Page {
95-
std.debug.assert(self.page == null);
96+
lp.assert(self.page == null, "Session.createPage - page not null", .{});
9697

9798
const page_arena = &self.browser.page_arena;
9899
_ = page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 });
@@ -116,8 +117,7 @@ pub fn createPage(self: *Session) !*Page {
116117
pub fn removePage(self: *Session) void {
117118
// Inform CDP the page is going to be removed, allowing other worlds to remove themselves before the main one
118119
self.browser.notification.dispatch(.page_remove, .{});
119-
120-
std.debug.assert(self.page != null);
120+
lp.assert(self.page != null, "Session.removePage - page is null", .{});
121121

122122
self.page.?.deinit();
123123
self.page = null;

src/browser/URL.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20+
const lp = @import("lightpanda");
2021
const Allocator = std.mem.Allocator;
2122

2223
const ResolveOpts = struct {
@@ -93,7 +94,7 @@ pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime
9394
}
9495

9596
if (std.mem.startsWith(u8, out[in_i..], "../")) {
96-
std.debug.assert(out[out_i - 1] == '/');
97+
lp.assert(out[out_i - 1] == '/', "URL.resolve", .{ .out = out });
9798

9899
if (out_i > path_marker) {
99100
// go back before the /

0 commit comments

Comments
 (0)