deno
deno copied to clipboard
feat(core): Add support for modules in realms.
Part of #13239.
With these changes, deno bench is now hanging forever after the benchmarks are run. This seems to be because the code that drives the benchmarks waits on a mpsc receiver until all senders have been dropped, and one of them is being kept alive:
https://github.com/denoland/deno/blob/16dbf4adc390c9fb7656372b42811c1929e755dd/cli/tools/bench.rs#L407
That one sender is being kept alive because now a realm's module map is stored in the context slot (rather than in the isolate slot), and because of denoland/rusty_v8#1066 it ends up not getting dropped even after the isolate is dropped.
Edit (October 8th): This is fixed now.
#16286 changed the module map, that was in a slot in the isolate backed by the IsolateAnnex, to use a raw V8 slot in the isolate. But this PR changes the module map to be a slot in the context's annex, so some of the perf gains from that PR will regress. But I expect #16215 to land before this PR, after which the module map could be stored in a raw slot in the context.
While starting to look into writing tests for this, I realized that in this PR I'm using a single ModuleLoader instance for all of an isolate's realms. There module maps are still separate per realm, even if they share a loader, but since ModuleLoader implementations might have state, we might need to consider the implications. If we add an Option<Fn() -> Rc<dyn ModuleLoader>> field to RuntimeOptions (plus the current module_loader field for the main realm), we would also let embedders disallow module imports in ShadowRealms, which might be worth considering. And the returned module loaders might be references to the main module loader, of course.
This PR got stuck for a while while most of the realm code got reverted because it was blocking performance work. All of the PRs that were preconditions for modules have now been relanded, so I have rewritten this PR from scratch and will continue to work on it now.
PTAL @bartlomieju @littledivy. Although this is probably not ready for merging, and the comments https://github.com/denoland/deno/pull/15760#issuecomment-1279953186 and https://github.com/denoland/deno/pull/15760#issuecomment-1281613105 are still things to fix, it would be good to get some initial reviews.
PTAL @bartlomieju @littledivy. Although this is probably not ready for merging, and the comments #15760 (comment) and #15760 (comment) are still things to fix, it would be good to get some initial reviews.
Sorry Andreu, I was out for most of the week. I'll put it into my TODO list for the next week and provide initial review.
The question about
ModuleLoaderis an interesting one and I don't have a good answer right now. Does spec say anything about it?
The HTML spec requires a level of "caching" in module fetches between a page's main and shadow realms, such that if a module is imported in the main realm it should not be re-fetched when imported from the shadow realm, and vice versa. I haven't looked at this code in a long time, but last time I checked Deno did some caching for module sources that would already take care of this.
But as I was looking at the spec, I realized that import maps were recently merged into the HTML spec, and it's not clear if anyone has looked in any depth at how they interact with ShadowRealm. Merging the HTML PR as is would mean that import maps don't apply in ShadowRealms, which seems reasonable, but I asked in a comment just in case. In this case, shadow realms would need a different module loader from the main realm, since they'd have to have a flag to not use import maps.
So I guess we should probably change RuntimeOptions to have a module loader for the main realm, and one for all shadow realms. Since they're Option<Rc<dyn ModuleLoader>>, both can be references to the same module loader object, for embedders that don't support import maps.
Also we need to figure out what we're gonna do with snapshotting. I think for the first pass we should not care about additional realms and just make sure that snapshotting still works with a "global realm"
WFM
Since JsRuntime::create_realm can be used to create "regular" (non-shadow) realms, for example if we ever support the Node.js vm module, I thought it was better to require the realm creator to provide a module loader. Then in #16211 (when I find time to rebase that PR) we can have a module loader that gets used for every shadow reallm.
Let's wait on https://github.com/denoland/deno/pull/17648 before we merge this PR.
I think this PR is now ready for review. The commit message contains a somewhat detailed list of changes.
I thought changing the module map to be stored on a context slot would make things slower, but the basic exec_time benchmarks don't show a difference. @littledivy, are there benchmarks specific to module loading that I should be running instead?
Main branch:
Benchmark 1: /home/abotella/Projects/forks/denoland/deno/target/release/deno run --reload cli/tests/testdata/run/002_hello.ts
Time (mean ± σ): 18.1 ms ± 0.9 ms [User: 11.2 ms, System: 7.9 ms]
Range (min … max): 16.9 ms … 23.3 ms 157 runs
Benchmark 2: /home/abotella/Projects/forks/denoland/deno/target/release/deno run --reload cli/tests/testdata/run/003_relative_import.ts
Time (mean ± σ): 17.8 ms ± 0.6 ms [User: 11.2 ms, System: 7.7 ms]
Range (min … max): 17.1 ms … 21.0 ms 156 runs
Benchmark 3: /home/abotella/Projects/forks/denoland/deno/target/release/deno run cli/tests/testdata/run/002_hello.ts
Time (mean ± σ): 18.0 ms ± 1.3 ms [User: 11.1 ms, System: 8.0 ms]
Range (min … max): 16.9 ms … 27.8 ms 156 runs
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
Benchmark 4: /home/abotella/Projects/forks/denoland/deno/target/release/deno run cli/tests/testdata/run/003_relative_import.ts
Time (mean ± σ): 18.1 ms ± 1.0 ms [User: 11.1 ms, System: 8.1 ms]
Range (min … max): 17.1 ms … 25.8 ms 159 runs
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
Benchmark 5: /home/abotella/Projects/forks/denoland/deno/target/release/deno run cli/tests/testdata/run/error_001.ts ; test $? -eq 1
Time (mean ± σ): 18.3 ms ± 0.7 ms [User: 11.1 ms, System: 8.2 ms]
Range (min … max): 17.3 ms … 20.7 ms 156 runs
Benchmark 6: /home/abotella/Projects/forks/denoland/deno/target/release/deno run --reload --no-check cli/tests/testdata/run/002_hello.ts
Time (mean ± σ): 18.7 ms ± 1.2 ms [User: 11.7 ms, System: 8.1 ms]
Range (min … max): 17.0 ms … 28.6 ms 152 runs
Benchmark 7: /home/abotella/Projects/forks/denoland/deno/target/release/deno run --allow-read cli/tests/testdata/workers/bench_startup.ts
Time (mean ± σ): 732.8 ms ± 10.0 ms [User: 600.6 ms, System: 182.6 ms]
Range (min … max): 720.6 ms … 754.7 ms 10 runs
Benchmark 8: /home/abotella/Projects/forks/denoland/deno/target/release/deno run --allow-read cli/tests/testdata/workers/bench_round_robin.ts
Time (mean ± σ): 173.4 ms ± 8.8 ms [User: 367.1 ms, System: 51.8 ms]
Range (min … max): 146.8 ms … 183.6 ms 16 runs
This PR:
Benchmark 1: /home/abotella/Projects/forks/denoland/deno/target/release/deno run --reload cli/tests/testdata/run/002_hello.ts
Time (mean ± σ): 18.5 ms ± 2.2 ms [User: 11.4 ms, System: 8.2 ms]
Range (min … max): 16.8 ms … 34.5 ms 155 runs
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
Benchmark 2: /home/abotella/Projects/forks/denoland/deno/target/release/deno run --reload cli/tests/testdata/run/003_relative_import.ts
Time (mean ± σ): 18.1 ms ± 0.6 ms [User: 11.0 ms, System: 8.2 ms]
Range (min … max): 17.1 ms … 20.7 ms 159 runs
Benchmark 3: /home/abotella/Projects/forks/denoland/deno/target/release/deno run cli/tests/testdata/run/002_hello.ts
Time (mean ± σ): 17.9 ms ± 0.8 ms [User: 11.1 ms, System: 7.9 ms]
Range (min … max): 16.9 ms … 24.4 ms 159 runs
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
Benchmark 4: /home/abotella/Projects/forks/denoland/deno/target/release/deno run cli/tests/testdata/run/003_relative_import.ts
Time (mean ± σ): 18.2 ms ± 1.0 ms [User: 11.0 ms, System: 8.3 ms]
Range (min … max): 17.1 ms … 23.1 ms 160 runs
Benchmark 5: /home/abotella/Projects/forks/denoland/deno/target/release/deno run cli/tests/testdata/run/error_001.ts ; test $? -eq 1
Time (mean ± σ): 18.3 ms ± 0.8 ms [User: 10.9 ms, System: 8.4 ms]
Range (min … max): 17.3 ms … 24.8 ms 149 runs
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
Benchmark 6: /home/abotella/Projects/forks/denoland/deno/target/release/deno run --reload --no-check cli/tests/testdata/run/002_hello.ts
Time (mean ± σ): 17.9 ms ± 0.5 ms [User: 11.1 ms, System: 7.9 ms]
Range (min … max): 17.2 ms … 20.2 ms 153 runs
Benchmark 7: /home/abotella/Projects/forks/denoland/deno/target/release/deno run --allow-read cli/tests/testdata/workers/bench_startup.ts
Time (mean ± σ): 731.0 ms ± 5.8 ms [User: 604.0 ms, System: 179.8 ms]
Range (min … max): 719.9 ms … 739.5 ms 10 runs
Benchmark 8: /home/abotella/Projects/forks/denoland/deno/target/release/deno run --allow-read cli/tests/testdata/workers/bench_round_robin.ts
Time (mean ± σ): 174.2 ms ± 5.7 ms [User: 375.9 ms, System: 53.5 ms]
Range (min … max): 162.1 ms … 185.3 ms 16 runs
@andreubotella looking at the benchmarks you provided there's about 0.3ms regression in the startup benchmark - it's something we'll need to address before landing. I'm gonna give a thorough review this week. Thanks for factoring out parts of core/runtime.rs to core/realm.rs. It makes it much easier to review.
Could you remind me of the reason why we need
global_realmto be anOption? If we removed theOptionwe could get rid of a lot of clones.
Only because state is created before the isolate, so you can't populate that field at that point.
Could you remind me of the reason why we need
global_realmto be anOption? If we removed theOptionwe could get rid of a lot of clones.Only because
stateis created before the isolate, so you can't populate that field at that point.
Maybe we could put an empty v8::Global there?
Could you remind me of the reason why we need
global_realmto be anOption? If we removed theOptionwe could get rid of a lot of clones.Only because
stateis created before the isolate, so you can't populate that field at that point.Maybe we could put an empty
v8::Globalthere?
Appears that's currently not possible, because v8::Global APIs still require a valid Isolate handle. I'll think about it more.
Appears that's currently not possible, because
v8::GlobalAPIs still require a validIsolatehandle. I'll think about it more.
I think I'd be fine with initializing global_realm to MaybeUninit::zeroed().assume_init(). This would need care when setting it, since we don't want to call the Drop impl of v8::Global.
Appears that's currently not possible, because
v8::GlobalAPIs still require a validIsolatehandle. I'll think about it more.I think I'd be fine with initializing
global_realmtoMaybeUninit::zeroed().assume_init(). This would need care when setting it, since we don't want to call theDropimpl ofv8::Global.
Oh, never mind, that by itself would be undefined behavior since v8::Global contains a NonNull field.
@piscisaureus suggested other solution. I will test it first on current main branch to see if it's feasible to use and let you know.
This has now landed on the deno_core repo. Closing.