Skip to content

Fragile/buggy implementation of ES module loading #18249

Closed
@iamstolis

Description

@iamstolis
  • Version: v8.9.4
  • Platform: Linux stola-ThinkPad 3.16.0-38-generic io.js on The Changelog! #52~14.04.1-Ubuntu SMP Fri May 8 09:43:57 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
  • Subsystem: esm

The implementation of module loading depends on subtleties of the order of promise jobs (microtasks) in V8. In fact, I believe that some tests (like test-esm-namespace) pass because of issue 6007 in V8 only. I tried to run these tests on our alternative version of Node.js that embeds a different JavaScript engine and some of es-module tests failed with

Error: linking error, dependency promises must be resolved on instantiate
    at checkComplete (internal/loader/ModuleJob.js:75:27)
    at internal/loader/ModuleJob.js:58:11

The root of the problem seems to be in ModuleJob.js. ModuleWrap::ResolveCallback assumes/checks that promises that correspond to dependencies are resolved. Unfortunately, the callback (invoked through module.instantiate()) is triggered when moduleJob.linked promise is resolved. It is not triggered when all the promises created by ModuleWrap::Link are resolved. See the relevant part of ModuleJob.js:

    const linked = async () => {
      const dependencyJobs = [];
      ({ module: this.module,
         reflect: this.reflect } = await this.modulePromise);
      this.module.link(async (dependencySpecifier) => {
        const dependencyJobPromise =
            this.loader.getModuleJob(dependencySpecifier, url);
        dependencyJobs.push(dependencyJobPromise);
        const dependencyJob = await dependencyJobPromise;
        return (await dependencyJob.modulePromise).module;
      });
      return SafePromise.all(dependencyJobs);
    };
    this.linked = linked();

moduleJob.linked is resolved when all dependencyJobs are resolved. Unfortunately, the promises created by ModuleWrap::Link do not correspond to dependencyJobs. They correspond to the body of the async function passed to module.link. When the last dependencyJob (aka dependencyJobPromise) is resolved then SafePromise.all(dependencyJob) (aka moduleJob.linked) is resolved (and checkComplete/module.instantiate/ResolveCallback is triggered). This can happen before the whole body of the async function is finished (and the promise checked by ModuleWrap::ResolveCallback is resolved).

I think that the code should be rewritten such that there is a proper promise-based dependency (through Promise.prototype.then) between module.install and the promises used by ModuleWrap::ResolveCallback.

Metadata

Metadata

Assignees

No one assigned

    Labels

    esmIssues and PRs related to the ECMAScript Modules implementation.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions