Depends on: https://github.com/lightpanda-io/zig-v8-fork/pull/152
We previously ran the message loop every 250ms. This commit changes it to run on
every tick (much more frequently). It also runs microtasks after draining the
message loop (since it can generate microtasks).
Also, we use to run microtasks after each script execution. Now we drain the
message Loop + microtasks.
We still only drain the microtasks when executing v8 callbacks.
As part of this change, we also adjust our wait time based on whether or not
there are pending background tasks in v8 in order to try to execute them (in
general) and in a timely manner.
The goal is to ensure that tasks v8 enqueued on the foreground thread are
executed promptly.
This change is particularly useful for calls to webassembly as compilation
happens in the background and eventually requires the message loop to be drained
to continue.
Previously, if a script did `await WebAssembly.instantiate(....)`, there was
a good chance we'd never finish the code - we'd wait too long to run the
message loop AND, after running it, we wouldn't necessarily resolve the promise.
Under some conditions, a microtask would be executed for a context that was
already deinit'd, resulting in various use-after-free.
The culprit appears to be WASM compilation being placed in the microtask queue
(by a user-script) and then resolved at some point in the future. We guard the
microtask queue by a context.shutting_down boolean, but v8 doesn't know anything
about this flag. The fact is that, microtasks are tied to an isolate, not a
context.
This commit introduces a number of changes:
1 - It follows 309f254c2c and stores the zig Context inside of an embedder field. This
ensures v8 doesn't consider this when GC'ing, which _could_ extend the
lifetime of the v8::Context beyond what we expect
2 - Most significantly, it introduces per-context microtasks queues. Each
context gets its own queue. This makes cleanup much simpler and reduces the
chance of microtasks outliving the context
3 - pumpMessageLoop is called on context.deinit, this helps to ensure that any
tasks v8 has for our context are processed (e.g. wasm compilation) before
shtudown
4 - The order of context shutdown is important, we notify the isolate of the
context destruction first, then pump the message loop and finally destroy
the context's message loop.
Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/151
Follow up to https://github.com/lightpanda-io/browser/pull/1495 which introduced
the concept of a fast getter. This commit expands the new behavior to all
comptime-known scalar getters.
It also leverages the new `v8__FunctionTemplate__New__Config` to
1 - flag fast getters as having no side effect
2 - set the length (arity) on all functions
This is the first in a series of changes to make globals explicit. The ultimate
goal of having explicit Globals is to move away from the global HandleScope and
to explicit HandleScopes.
Currently, we treat globals and locals interchangeably. In fact, for Global ->
Local, we just ptrCast. This works because we have 1 global HandleScope, which
effectively disables V8's GC and thus nothing ever gets moved.
If we're going to introduce explicit HandleScopes, then we need to first have
correct Globals. Specifically, when we want to act on the global, we need to
get the local value, and that will eventually mean making sure there's a
HandleScope.
While adding explicit globals, we're keeping the global HandleScope so that we
can minimize the change. So, given that we still have the global HandleScope
the change is largely two things:
1 - js.Function.persit() returns a js.Function.Global. Types that persist global
functions must be updated to js.Function.Global.
2 - To turn js.Function.Global -> js.Function, we need to call .local() on it.
The bridge has been updated to support js.Function.Global for both input and
output parameters. Thus, window.setOnLoad can now directly take a
js.Function.Global, and window.getOnLoad can directly return that
js.Function.Global.
Previously, `MessageEvent('')` would have been allowed, but invalid. This caused
problems as the receiver was the window. All such calls are now rejected.
Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/131
There are two layers here. The first is that, on startup, a v8 SnapshotCreator
is created, and a snapshot-specific isolate/context is setup with our browser
environment. This contains most of what was in Env.init and a good chunk of
what was in ExecutionWorld.createContext. From this, we create a v8.StartupData
which is used for the creation of all subsequent contexts. The snapshot sits
at the application level, above the Env - it's re-used for all envs/isolates, so
this gives a nice performance boost for both 1 connection opening multiple pages
or multiple connections opening 1 page.
The second layer is that the Snapshot data can be embedded into the binary, so
that it doesn't have to be created on startup, but rather created at build-time.
This improves the startup time (though, I'm not really sure how to measure that
accurately...).
The first layer is the big win (and just works as-is without any build / usage
changes).
with snapshot
total runs 1000
total duration (ms) 7527
avg run duration (ms) 7
min run duration (ms) 5
max run duration (ms) 41
without snapshot
total runs 1000
total duration (ms) 9350
avg run duration (ms) 9
min run duration (ms) 8
max run duration (ms) 42
To embed a snapshot into the binary, we first need to create the snapshot file:
zig build -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
And then build using the new snapshot_path argument:
zig build -Dsnapshot_path=../../snapshot.bin -Doptimize=ReleaseFast
The paths are weird, I know...since it's embedded, it needs to be inside the
project path, hence we put it in src/snapshot.bin. And since it's embedded
relative to the embedder (src/browser/js/Snapshot.zig) the path has to be
relative to that, hence ../../snapshot.bin. I'm open to suggestions on
improving this.
First, this exposes the v8 Profiler. Right now it's just a commented-out block
in `fetch` and meant for internal debugging.
Depends on: https://github.com/lightpanda-io/zig-v8-fork/pull/105
Use postAttach on Window to attach "static" properties. This comes from
profiling (lightpanda.io) and seeing window.get_self called tens of thousands
of times.
1 - On `unkown global property`, include the stack trace (this might be WAY too
verbose)
2 - On script get, include stack trace (when available)
3 - On script get, include referrer
4 - Stack traces will now include the script name/src when available
Refactors some of the module loading logic. Both normal modules import and
dynamic module import now share more of the same code - they both go through
the slightly modified `module` function.
Dynamic modules now check the cache first, before loading, and when cached,
resolve the correct promise. This can now happen regardless of the module
loading state.
Also tried to replace some page arenas with call arenas and added some basic
tests for both normal and dynamic module loading.
Previously, the IO loop was doing three things:
1 - Managing timeouts (either from scripts or for our own needs)
2 - Handling browser IO events (page/script/xhr)
3 - Handling CDP events (accept, read, write, timeout)
With the libcurl merge, 1 was moved to an in-process scheduler and 2 was moved
to libcurl's own event loop. That means the entire loop code, including
the dependency on tigerbeetle-io existed for handling a single TCP client.
Not only is that a lot of code, there was also friction between the two loops
(the libcurl one and our IO loop), which would result in latency - while one
loop is waiting for the events, any events on the other loop go un-processed.
This PR removes our IO loop. To accomplish this:
1 - The main accept loop is blocking. This is simpler and works perfectly well,
given we only allow 1 active connection.
2 - The client socket is passed to libcurl - yes, libcurl's loop can take
arbitrary FDs and poll them along with its own.
In addition to having one less dependency, the CDP code is quite a bit simpler,
especially around shutdowns and writes. This also removes _some_ of the latency
caused by the friction between page process and CDP processing. Specifically,
when CDP now blocks for input, http page events (script loading, xhr, ...) will
still be processed.
There's still friction. For one, the reverse isn't true: when the page is
waiting for events, CDP events aren't going to be processed. But the page.wait
already have some sensitivity to this (e.g. the page.request_intercepted flag).
Also, when CDP waits, while we will process network events, page timeouts are
still not processed. Because of both these remaining issues, we still need to
jump between the two loops - but being able to block on CDP (even for a short
time) WITHOUT stopping the page's network I/O, should reduce some latency.
Our build.zig is using a number of deprecated features, which are removed in
0.15.
This updates build.zig so that it still works in 0.14 and [hopefully] will work
in 0.15.
Related: https://github.com/lightpanda-io/zig-v8-fork/pull/87
Don't error on JSON.stringify failure (likely caused by circular reference).
In debug mode, try to print [slightly] more meaningful value representation
when default serialization results in [object Object].