| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent b402799 commit 46b06be
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,7 +18,6 @@ const { | |||
| 18 | 18 | kIsExecuting, | |
| 19 | 19 | kRequiredModuleSymbol, | |
| 20 | 20 | } = require('internal/modules/cjs/loader'); | |
| 21 | - | ||
| 22 | 21 | const { imported_cjs_symbol } = internalBinding('symbols'); | |
| 23 | 22 | ||
| 24 | 23 | const assert = require('internal/assert'); | |
@@ -38,7 +37,13 @@ const { | |||
| 38 | 37 | forceDefaultLoader, | |
| 39 | 38 | } = require('internal/modules/esm/utils'); | |
| 40 | 39 | const { kImplicitTypeAttribute } = require('internal/modules/esm/assert'); | |
| 41 | - const { ModuleWrap, kEvaluating, kEvaluated } = internalBinding('module_wrap'); | ||
| 40 | + const { | ||
| 41 | + ModuleWrap, | ||
| 42 | + kEvaluated, | ||
| 43 | + kEvaluating, | ||
| 44 | + kInstantiated, | ||
| 45 | + throwIfPromiseRejected, | ||
| 46 | + } = internalBinding('module_wrap'); | ||
| 42 | 47 | const { | |
| 43 | 48 | urlToFilename, | |
| 44 | 49 | } = require('internal/modules/helpers'); | |
@@ -53,6 +58,10 @@ let defaultResolve, defaultLoad, defaultLoadSync, importMetaInitializer; | |||
| 53 | 58 | const { tracingChannel } = require('diagnostics_channel'); | |
| 54 | 59 | const onImport = tracingChannel('module.import'); | |
| 55 | 60 | ||
| 61 | + let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { | ||
| 62 | + debug = fn; | ||
| 63 | + }); | ||
| 64 | + | ||
| 56 | 65 | /** | |
| 57 | 66 | * @typedef {import('./hooks.js').HooksProxy} HooksProxy | |
| 58 | 67 | * @typedef {import('./module_job.js').ModuleJobBase} ModuleJobBase | |
@@ -86,6 +95,23 @@ function getTranslators() { | |||
| 86 | 95 | return translators; | |
| 87 | 96 | } | |
| 88 | 97 | ||
| 98 | + /** | ||
| 99 | + * Generate message about potential race condition caused by requiring a cached module that has started | ||
| 100 | + * async linking. | ||
| 101 | + * @param {string} filename Filename of the module being required. | ||
| 102 | + * @param {string|undefined} parentFilename Filename of the module calling require(). | ||
| 103 | + * @returns {string} Error message. | ||
| 104 | + */ | ||
| 105 | + function getRaceMessage(filename, parentFilename) { | ||
| 106 | + let raceMessage = `Cannot require() ES Module ${filename} because it is not yet fully loaded. `; | ||
| 107 | + raceMessage += 'This may be caused by a race condition if the module is simultaneously dynamically '; | ||
| 108 | + raceMessage += 'import()-ed via Promise.all(). Try await-ing the import() sequentially in a loop instead.'; | ||
| 109 | + if (parentFilename) { | ||
| 110 | + raceMessage += ` (from ${parentFilename})`; | ||
| 111 | + } | ||
| 112 | + return raceMessage; | ||
| 113 | + } | ||
| 114 | + | ||
| 89 | 115 | /** | |
| 90 | 116 | * @type {HooksProxy} | |
| 91 | 117 | * Multiple loader instances exist for various, specific reasons (see code comments at site). | |
@@ -340,35 +366,53 @@ class ModuleLoader { | |||
| 340 | 366 | // evaluated at this point. | |
| 341 | 367 | // TODO(joyeecheung): add something similar to CJS loader's requireStack to help | |
| 342 | 368 | // debugging the the problematic links in the graph for import. | |
| 369 | + debug('importSyncForRequire', parent?.filename, '->', filename, job); | ||
| 343 | 370 | if (job !== undefined) { | |
| 344 | 371 | mod[kRequiredModuleSymbol] = job.module; | |
| 345 | 372 | const parentFilename = urlToFilename(parent?.filename); | |
| 346 | 373 | // TODO(node:55782): this race may stop to happen when the ESM resolution and loading become synchronous. | |
| 347 | 374 | if (!job.module) { | |
| 348 | - let message = `Cannot require() ES Module ${filename} because it is not yet fully loaded. `; | ||
| 349 | - message += 'This may be caused by a race condition if the module is simultaneously dynamically '; | ||
| 350 | - message += 'import()-ed via Promise.all(). Try await-ing the import() sequentially in a loop instead.'; | ||
| 351 | - if (parentFilename) { | ||
| 352 | - message += ` (from ${parentFilename})`; | ||
| 353 | - } | ||
| 354 | - assert(job.module, message); | ||
| 375 | + assert.fail(getRaceMessage(filename, parentFilename)); | ||
| 355 | 376 | } | |
| 356 | 377 | if (job.module.async) { | |
| 357 | 378 | throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename); | |
| 358 | 379 | } | |
| 359 | - // job.module may be undefined if it's asynchronously loaded. Which means | ||
| 360 | - // there is likely a cycle. | ||
| 361 | - if (job.module.getStatus() !== kEvaluated) { | ||
| 362 | - let message = `Cannot require() ES Module ${filename} in a cycle.`; | ||
| 363 | - if (parentFilename) { | ||
| 364 | - message += ` (from ${parentFilename})`; | ||
| 365 | - } | ||
| 366 | - message += 'A cycle involving require(esm) is disallowed to maintain '; | ||
| 367 | - message += 'invariants madated by the ECMAScript specification'; | ||
| 368 | - message += 'Try making at least part of the dependency in the graph lazily loaded.'; | ||
| 369 | - throw new ERR_REQUIRE_CYCLE_MODULE(message); | ||
| 380 | + const status = job.module.getStatus(); | ||
| 381 | + debug('Module status', filename, status); | ||
| 382 | + if (status === kEvaluated) { | ||
| 383 | + return { wrap: job.module, namespace: job.module.getNamespaceSync(filename, parentFilename) }; | ||
| 384 | + } else if (status === kInstantiated) { | ||
| 385 | + // When it's an async job cached by another import request, | ||
| 386 | + // which has finished linking but has not started its | ||
| 387 | + // evaluation because the async run() task would be later | ||
| 388 | + // in line. Then start the evaluation now with runSync(), which | ||
| 389 | + // is guaranteed to finish by the time the other run() get to it, | ||
| 390 | + // and the other task would just get the cached evaluation results, | ||
| 391 | + // similar to what would happen when both are async. | ||
| 392 | + mod[kRequiredModuleSymbol] = job.module; | ||
| 393 | + const { namespace } = job.runSync(parent); | ||
| 394 | + return { wrap: job.module, namespace: namespace || job.module.getNamespace() }; | ||
| 370 | 395 | } | |
| 371 | - return { wrap: job.module, namespace: job.module.getNamespaceSync(filename, parentFilename) }; | ||
| 396 | + // When the cached async job have already encountered a linking | ||
| 397 | + // error that gets wrapped into a rejection, but is still later | ||
| 398 | + // in line to throw on it, just unwrap and throw the linking error | ||
| 399 | + // from require(). | ||
| 400 | + if (job.instantiated) { | ||
| 401 | + throwIfPromiseRejected(job.instantiated); | ||
| 402 | + } | ||
| 403 | + if (status !== kEvaluating) { | ||
| 404 | + assert.fail(`Unexpected module status ${status}. ` + | ||
| 405 | + getRaceMessage(filename, parentFilename)); | ||
| 406 | + } | ||
| 407 | + let message = `Cannot require() ES Module ${filename} in a cycle.`; | ||
| 408 | + if (parentFilename) { | ||
| 409 | + message += ` (from ${parentFilename})`; | ||
| 410 | + } | ||
| 411 | + message += 'A cycle involving require(esm) is disallowed to maintain '; | ||
| 412 | + message += 'invariants madated by the ECMAScript specification'; | ||
| 413 | + message += 'Try making at least part of the dependency in the graph lazily loaded.'; | ||
| 414 | + throw new ERR_REQUIRE_CYCLE_MODULE(message); | ||
| 415 | + | ||
| 372 | 416 | } | |
| 373 | 417 | // TODO(joyeecheung): refactor this so that we pre-parse in C++ and hit the | |
| 374 | 418 | // cache here, or use a carrier object to carry the compiled module script | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -22,7 +22,7 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { | |||
| 22 | 22 | debug = fn; | |
| 23 | 23 | }); | |
| 24 | 24 | ||
| 25 | - const { ModuleWrap, kInstantiated } = internalBinding('module_wrap'); | ||
| 25 | + const { ModuleWrap, kEvaluationPhase, kInstantiated } = internalBinding('module_wrap'); | ||
| 26 | 26 | const { | |
| 27 | 27 | privateSymbols: { | |
| 28 | 28 | entry_point_module_private_symbol, | |
@@ -246,19 +246,20 @@ class ModuleJob extends ModuleJobBase { | |||
| 246 | 246 | } | |
| 247 | 247 | } | |
| 248 | 248 | ||
| 249 | - runSync() { | ||
| 249 | + runSync(parent) { | ||
| 250 | + assert(this.phase === kEvaluationPhase); | ||
| 250 | 251 | assert(this.module instanceof ModuleWrap); | |
| 251 | 252 | if (this.instantiated !== undefined) { | |
| 252 | 253 | return { __proto__: null, module: this.module }; | |
| 253 | 254 | } | |
| 254 | 255 | ||
| 255 | 256 | this.module.instantiate(); | |
| 256 | 257 | this.instantiated = PromiseResolve(); | |
| 257 | - const timeout = -1; | ||
| 258 | - const breakOnSigint = false; | ||
| 259 | 258 | setHasStartedUserESMExecution(); | |
| 260 | - this.module.evaluate(timeout, breakOnSigint); | ||
| 261 | - return { __proto__: null, module: this.module }; | ||
| 259 | + const filename = urlToFilename(this.url); | ||
| 260 | + const parentFilename = urlToFilename(parent?.filename); | ||
| 261 | + const namespace = this.module.evaluateSync(filename, parentFilename); | ||
| 262 | + return { __proto__: null, module: this.module, namespace }; | ||
| 262 | 263 | } | |
| 263 | 264 | ||
| 264 | 265 | async run(isEntryPoint = false) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,6 +36,7 @@ using v8::Int32; | |||
| 36 | 36 | using v8::Integer; | |
| 37 | 37 | using v8::Isolate; | |
| 38 | 38 | using v8::Just; | |
| 39 | + using v8::JustVoid; | ||
| 39 | 40 | using v8::Local; | |
| 40 | 41 | using v8::LocalVector; | |
| 41 | 42 | using v8::Maybe; | |
@@ -46,6 +47,7 @@ using v8::MicrotaskQueue; | |||
| 46 | 47 | using v8::Module; | |
| 47 | 48 | using v8::ModuleRequest; | |
| 48 | 49 | using v8::Name; | |
| 50 | + using v8::Nothing; | ||
| 49 | 51 | using v8::Null; | |
| 50 | 52 | using v8::Object; | |
| 51 | 53 | using v8::ObjectTemplate; | |
@@ -653,6 +655,43 @@ void ModuleWrap::InstantiateSync(const FunctionCallbackInfo<Value>& args) { | |||
| 653 | 655 | args.GetReturnValue().Set(module->IsGraphAsync()); | |
| 654 | 656 | } | |
| 655 | 657 | ||
| 658 | + Maybe<void> ThrowIfPromiseRejected(Realm* realm, Local<Promise> promise) { | ||
| 659 | + Isolate* isolate = realm->isolate(); | ||
| 660 | + Local<Context> context = realm->context(); | ||
| 661 | + if (promise->State() != Promise::PromiseState::kRejected) { | ||
| 662 | + return JustVoid(); | ||
| 663 | + } | ||
| 664 | + // The rejected promise is created by V8, so we don't get a chance to mark | ||
| 665 | + // it as resolved before the rejection happens from evaluation. But we can | ||
| 666 | + // tell the promise rejection callback to treat it as a promise rejected | ||
| 667 | + // before handler was added which would remove it from the unhandled | ||
| 668 | + // rejection handling, since we are converting it into an error and throw | ||
| 669 | + // from here directly. | ||
| 670 | + Local<Value> type = | ||
| 671 | + Integer::New(isolate, | ||
| 672 | + static_cast<int32_t>( | ||
| 673 | + PromiseRejectEvent::kPromiseHandlerAddedAfterReject)); | ||
| 674 | + Local<Value> args[] = {type, promise, Undefined(isolate)}; | ||
| 675 | + if (realm->promise_reject_callback() | ||
| 676 | + ->Call(context, Undefined(isolate), arraysize(args), args) | ||
| 677 | + .IsEmpty()) { | ||
| 678 | + return Nothing<void>(); | ||
| 679 | + } | ||
| 680 | + Local<Value> exception = promise->Result(); | ||
| 681 | + Local<Message> message = Exception::CreateMessage(isolate, exception); | ||
| 682 | + AppendExceptionLine( | ||
| 683 | + realm->env(), exception, message, ErrorHandlingMode::MODULE_ERROR); | ||
| 684 | + isolate->ThrowException(exception); | ||
| 685 | + return Nothing<void>(); | ||
| 686 | + } | ||
| 687 | + | ||
| 688 | + void ThrowIfPromiseRejected(const FunctionCallbackInfo<Value>& args) { | ||
| 689 | + if (!args[0]->IsPromise()) { | ||
| 690 | + return; | ||
| 691 | + } | ||
| 692 | + ThrowIfPromiseRejected(Realm::GetCurrent(args), args[0].As<Promise>()); | ||
| 693 | + } | ||
| 694 | + | ||
| 656 | 695 | void ModuleWrap::EvaluateSync(const FunctionCallbackInfo<Value>& args) { | |
| 657 | 696 | Realm* realm = Realm::GetCurrent(args); | |
| 658 | 697 | Isolate* isolate = args.GetIsolate(); | |
@@ -677,28 +716,7 @@ void ModuleWrap::EvaluateSync(const FunctionCallbackInfo<Value>& args) { | |||
| 677 | 716 | ||
| 678 | 717 | CHECK(result->IsPromise()); | |
| 679 | 718 | Local<Promise> promise = result.As<Promise>(); | |
| 680 | - if (promise->State() == Promise::PromiseState::kRejected) { | ||
| 681 | - // The rejected promise is created by V8, so we don't get a chance to mark | ||
| 682 | - // it as resolved before the rejection happens from evaluation. But we can | ||
| 683 | - // tell the promise rejection callback to treat it as a promise rejected | ||
| 684 | - // before handler was added which would remove it from the unhandled | ||
| 685 | - // rejection handling, since we are converting it into an error and throw | ||
| 686 | - // from here directly. | ||
| 687 | - Local<Value> type = | ||
| 688 | - Integer::New(isolate, | ||
| 689 | - static_cast<int32_t>( | ||
| 690 | - PromiseRejectEvent::kPromiseHandlerAddedAfterReject)); | ||
| 691 | - Local<Value> args[] = {type, promise, Undefined(isolate)}; | ||
| 692 | - if (env->promise_reject_callback() | ||
| 693 | - ->Call(context, Undefined(isolate), arraysize(args), args) | ||
| 694 | - .IsEmpty()) { | ||
| 695 | - return; | ||
| 696 | - } | ||
| 697 | - Local<Value> exception = promise->Result(); | ||
| 698 | - Local<Message> message = Exception::CreateMessage(isolate, exception); | ||
| 699 | - AppendExceptionLine( | ||
| 700 | - env, exception, message, ErrorHandlingMode::MODULE_ERROR); | ||
| 701 | - isolate->ThrowException(exception); | ||
| 719 | + if (ThrowIfPromiseRejected(realm, promise).IsNothing()) { | ||
| 702 | 720 | return; | |
| 703 | 721 | } | |
| 704 | 722 | ||
@@ -1152,6 +1170,7 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data, | |||
| 1152 | 1170 | target, | |
| 1153 | 1171 | "createRequiredModuleFacade", | |
| 1154 | 1172 | CreateRequiredModuleFacade); | |
| 1173 | + SetMethod(isolate, target, "throwIfPromiseRejected", ThrowIfPromiseRejected); | ||
| 1155 | 1174 | } | |
| 1156 | 1175 | ||
| 1157 | 1176 | void ModuleWrap::CreatePerContextProperties(Local<Object> target, | |
@@ -1196,6 +1215,7 @@ void ModuleWrap::RegisterExternalReferences( | |||
| 1196 | 1215 | ||
| 1197 | 1216 | registry->Register(SetImportModuleDynamicallyCallback); | |
| 1198 | 1217 | registry->Register(SetInitializeImportMetaObjectCallback); | |
| 1218 | + registry->Register(ThrowIfPromiseRejected); | ||
| 1199 | 1219 | } | |
| 1200 | 1220 | } // namespace loader | |
| 1201 | 1221 | } // namespace node | |
| Back | FazBrowse Home | New Git URL |
0 commit comments