Sitelet https://github.com/nodejs/node/issues/52846
Skip to content

Hanging when using MessagePort from ES customization hooks via --import #52846

Description

@elliotgoodrich

Version

v21.1.0

Platform

Microsoft Windows NT 10.0.22631.0 x64

Subsystem

No response

What steps will reproduce the bug?

Running node --import ./bootstrap.mjs ./index.mjs will hang with the following code.

// hooks.mjs
export async function initialize({ port }) {
  port.postMessage('initialize');
}
// bootstrap.mjs
import { register } from 'node:module';
import { MessageChannel } from 'node:worker_threads';

const { port1, port2 } = new MessageChannel();

port1.on('message', (msg) => {
  console.log(`msg = ${msg}`);
});

register('./hooks.mjs', {
  parentURL: import.meta.url,
  data: { port: port2 },
  transferList: [port2],
});
// index.mjs
console.log('Hello World');

How often does it reproduce? Is there a required condition?

Reproducible 100% of the time.

What is the expected behavior? Why is that the expected behavior?

For node to print

$ node --import ./bootstrap.mjs ./index.mjs
msg = initialize
Hello World

and return normally.

What do you see instead?

node hangs, but prints the expected to the console

$ node --import ./bootstrap.mjs ./index.mjs
msg = initialize
Hello World

Additional information

node returns normally and no longer hangs if

  1. port.close() is added after port.postMessage('initialize');,
  2. or, removing the call to port1.on('message', ...)

This code was taken and simplified from the example in the documentation here https://nodejs.org/api/module.html#initialize.

Activity

  1. added
    loadersIssues and PRs related to ES module loaders.
    on May 7, 2024
  2. dygabo commented on May 8, 2024

    @dygabo
    Member

    This is not hanging but still running IMO. It must be happening because port1.on registers an event handler that keeps the process alive. You could implement on your protocol layer some message that unsubscribes for the 'message' event and that will allow the process to end.
    for example modify your message handler to get one message and unsubscribe like this:

      port1.on('message', (msg) => {
      console.log(`msg = ${msg}`);
      port1.off('message');
    });
    

    the .off call can be based on any other application logic on your apps side.

    @nodejs/loaders please correct me if I'm wrong.

    EDIT: tried that now, seems wrong. but you could unref the ports used for the comunication from the customization hooks thread:

    port1.on('message', (msg) => {
      console.log(`msg = ${msg}`);
    });
    
    port1.unref();
    port2.unref();
    
  3. elliotgoodrich commented on May 8, 2024

    @elliotgoodrich
    ContributorAuthor

    Thank you @dygabo! unref()ing port1 seems to be sufficient for my initial example - is there a reason to call port2.unref() as well?

    I am happy to add a PR to update the example in the node docs (https://nodejs.org/api/module.html#initialize) so others don't encounter this.

  4. dygabo commented on May 8, 2024

    @dygabo
    Member

    yes, it should be enough to unref port1 to allow the process to stop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    loadersIssues and PRs related to ES module loaders.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions