@@ -26,31 +26,14 @@ kj::Maybe<const Module&> checkModule(const ResolveContext& context, const Module
2626};
2727
2828// Ensure that the given module has been instantiated or errored.
29+ // If false is returned, then an exception should have been scheduled
30+ // on the isolate.
2931bool ensureInstantiated (Lock& js,
3032 v8::Local<v8::Module> module ,
3133 const CompilationObserver& observer,
32- const Module* self) {
33- if (module ->GetStatus () == v8::Module::kUninstantiated ) {
34- bool result;
35- if (!self->instantiate (js, module , observer).To (&result)) {
36- // If we got here, instantiation threw an error. Let's return
37- // false and allow that error to propagate.
38- return false ;
39- }
40- // If we got here the instantiation may or may not have succeeded.
41- // Let's check...
42- if (!result) {
43- // Nope, it failed. Let's throw an error.
44- // Note here we are scheduling the exception on the isolate directly
45- // rather than via the lock. This is because throwing via the lock
46- // also throws JsExceptionThrown, which is not what we want. Here
47- // we only want to schedule the exception in the isolate
48- js.v8Isolate ->ThrowError (
49- js.str (kj::str (" Failed to instantiate module: " , self->specifier ())));
50- return false ;
51- }
52- }
53- return true ;
34+ const Module& self) {
35+ return module ->GetStatus () != v8::Module::kUninstantiated ||
36+ self.instantiate (js, module , observer);
5437}
5538
5639constexpr ResolveContext::Type moduleTypeToResolveContextType (Module::Type type) {
@@ -108,54 +91,78 @@ class EsModule final: public Module {
10891 v8::ScriptOrigin origin (js.str (specifier ().getHref ()), resourceLineOffset, resourceColumnOffset,
10992 resourceIsSharedCrossOrigin, scriptId, {}, resourceIsOpaque, isWasm, true );
11093
111- v8::ScriptCompiler::CachedData* data = nullptr ;
11294 auto options = v8::ScriptCompiler::CompileOptions::kNoCompileOptions ;
11395
11496 v8::Local<v8::Module> module ;
11597 {
98+ v8::ScriptCompiler::CachedData* data = nullptr ;
99+
116100 // Check to see if we have cached compilation data for this module.
101+ // Importantly, we want to allow multiple threads to be capable of
102+ // reading and using the cached data without blocking each other
103+ // (which is fine since using the cache does not modify it).
117104 auto lock = cachedData.lockShared ();
118105 KJ_IF_SOME (c, *lock) {
119106 // We new new here because v8 will take ownership of the CachedData instance,
120107 // even tho we are maintaining ownership of the underlying buffer.
121108 data = new v8::ScriptCompiler::CachedData (
122109 c->data , c->length , v8::ScriptCompiler::CachedData::BufferPolicy::BufferNotOwned);
110+ auto check = data->CompatibilityCheck (js.v8Isolate );
111+ if (check != v8::ScriptCompiler::CachedData::kSuccess ) {
112+ // The cached data is not compatible with the current isolate. Let's
113+ // not try using it.
114+ delete data;
115+ } else {
116+ observer.onCompileCacheFound (js.v8Isolate );
117+ }
123118 }
124119
125120 // Note that the Source takes ownership of the CachedData pointer that we pass in.
126- // Do not use data after this point.
121+ // (but not the actual buffer it holds). Do not use data after this point.
127122 v8::ScriptCompiler::Source source (js.strExtern (this ->source ), origin, data);
128123
129124 auto maybeCached = source.GetCachedData ();
130125 if (maybeCached != nullptr ) {
131126 if (!maybeCached->rejected ) {
132- // We found valid cached data and need to set the option to consume it to avoid
127+ // We found valid cached data and set the option to consume it to avoid
133128 // compiling again below...
134129 options = v8::ScriptCompiler::CompileOptions::kConsumeCodeCache ;
135130 } else {
136131 // In this case we'll just log a warning and continue on. This is potentially
137132 // a signal that something with the compile cache is not working correctly but
138133 // it is not a fatal error. If we spot this in the wild, it warrants some
139- // investigation.
140- LOG_WARNING_ONCE (" NOSENTRY Cached data for ESM module was rejected" );
134+ // investigation but is not critical.
135+ LOG_WARNING_ONCE (" NOSENTRY Cached data for an ESM module was rejected" );
136+ observer.onCompileCacheRejected (js.v8Isolate );
141137 }
142138 }
143139
140+ // Let's just double check that our options are valid. They should be
141+ // since we're either consuming cached data or not using any options at all.
142+ KJ_ASSERT (v8::ScriptCompiler::CompileOptionsIsValid (options));
144143 if (!v8::ScriptCompiler::CompileModule (js.v8Isolate , &source, options).ToLocal (&module )) {
145144 return v8::MaybeLocal<v8::Module>();
146145 }
147146 }
148147
149- // If the options are still kNoCompileOptions, then we did not have or use cached
150- // data. We should generate the cache now, if possible. We lock to ensure that
151- // we do not generate the cache multiple times needlessly. When the lock is acquired
152- // we check again to see if the cache is still empty, and skip generating if it is not.
148+ // If options is still kNoCompileOptions at this point, it means that we did not
149+ // find any cached data for this module, or the cached data was rejected. In the
150+ // case it was rejected, we just move on. If there is no cached data, we try
151+ // generating it and store it. Multiple threads can end up lining up here to
152+ // acquire the lock and generate the cache. We'll test to see if the cached
153+ // data is still empty once the lock is acquired, and if it is not, we'll skip
154+ // generation.
153155 if (options == v8::ScriptCompiler::CompileOptions::kNoCompileOptions ) {
154156 auto lock = cachedData.lockExclusive ();
155- if (auto ptr = v8::ScriptCompiler::CreateCodeCache (module ->GetUnboundModuleScript ())) {
156- kj::Own<v8::ScriptCompiler::CachedData> cached (
157- ptr, kj::_::HeapDisposer<v8::ScriptCompiler::CachedData>::instance);
158- *lock = kj::mv (cached);
157+ if (*lock == kj::none) {
158+ if (auto ptr = v8::ScriptCompiler::CreateCodeCache (module ->GetUnboundModuleScript ())) {
159+ kj::Own<v8::ScriptCompiler::CachedData> cached (
160+ ptr, kj::_::HeapDisposer<v8::ScriptCompiler::CachedData>::instance);
161+ *lock = kj::mv (cached);
162+ observer.onCompileCacheGenerated (js.v8Isolate );
163+ } else {
164+ observer.onCompileCacheGenerationFailed (js.v8Isolate );
165+ }
159166 }
160167 }
161168
@@ -172,7 +179,7 @@ class EsModule final: public Module {
172179 v8::Local<v8::Module> module ,
173180 const CompilationObserver& observer,
174181 kj::Maybe<EvalCallback>& maybeEvalCallback) const override {
175- if (!ensureInstantiated (js, module , observer, this )) {
182+ if (!ensureInstantiated (js, module , observer, * this )) {
176183 return v8::MaybeLocal<v8::Value>();
177184 };
178185
@@ -254,7 +261,7 @@ class SyntheticModule final: public Module {
254261 v8::Local<v8::Module> module ,
255262 const CompilationObserver& observer,
256263 kj::Maybe<EvalCallback>& maybeEvalCallback) const override {
257- if (!ensureInstantiated (js, module , observer, this )) {
264+ if (!ensureInstantiated (js, module , observer, * this )) {
258265 return v8::MaybeLocal<v8::Value>();
259266 }
260267
@@ -398,10 +405,10 @@ class IsolateModuleRegistry final {
398405 js.throwException (JsValue (module ->GetException ()));
399406 }
400407
401- // TODO(new-module-registry): Circular dependencies should be fine
402- // when we are talking strictly about CJS/Node.js style modules.
403- // For ESM, it becomes more problematic because v8 will not allow
404- // us to grab the default export while the module is still evaluating.
408+ // Circular dependencies should be fine when we are talking strictly
409+ // about CJS/Node.js style modules. For ESM, it becomes more problematic
410+ // because v8 will not allow us to grab the default export while the module
411+ // is still evaluating.
405412
406413 if (entry.module .isEsm () && status == v8::Module::kEvaluating ) {
407414 JSG_FAIL_REQUIRE (Error, " Circular dependency when resolving module: " , specifier);
@@ -1294,12 +1301,16 @@ Module::Module(Url specifier, Type type, Flags flags)
12941301 type_(type),
12951302 flags_(flags) {}
12961303
1297- v8::Maybe< bool > Module::instantiate (
1304+ bool Module::instantiate (
12981305 Lock& js, v8::Local<v8::Module> module , const CompilationObserver& observer) const {
12991306 if (module ->GetStatus () != v8::Module::kUninstantiated ) {
1300- return v8::Just ( true ) ;
1307+ return true ;
13011308 }
1302- return module ->InstantiateModule (js.v8Context (), resolveCallback);
1309+ // InstantiateModule is one of those methods that returns a Maybe<bool> but
1310+ // never returns Just(false). It either returns Just(true) or an empty Maybe
1311+ // to signal that the instantiation failed. Eventually I would expect V8 to
1312+ // replace the return value with a Maybe<void>.
1313+ return module ->InstantiateModule (js.v8Context (), resolveCallback).IsJust ();
13031314}
13041315
13051316bool Module::isEval () const {
0 commit comments