Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ext/node) worker_threads ESM handling #22841

Merged
merged 20 commits into from Mar 20, 2024

Conversation

mash-graz
Copy link
Contributor

@mash-graz mash-graz commented Mar 11, 2024

Fixes #22840
Fixes #22964

I still have to look for better test example, because for the very simple parallel/test-worker-esmodule.js and its *.mjs worker file it doesn't make difference if this patch gets applied, but my projects, which use .js files in ordinary packages, do not work anymore without this modification.

I'll leave it as draft in the meanwhile...

Copy link
Member

@dsherret dsherret left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Let me know if you need further help on this one.

Probably a test could be added in tests/integration/npm_tests.rs

const cwdFileUrl = toFileUrl(Deno.cwd());
specifier =
`data:text/javascript,(async function() {const { createRequire } = await import("node:module");const require = createRequire("${cwdFileUrl}");require("${specifier}");})();`;
} else {
specifier = toFileUrl(specifier as string);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevertheless, a more general solution for the file type detection...

There is a function, url_to_node_resolution on NodeResolver. Probably that should be used everywhere. Also, all files outside a node_modules directory are considered ESM in Deno (there is a in_npm_package function on NodeResolver to tell that).

Copy link
Contributor Author

@mash-graz mash-graz Mar 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The url_to_node_resolution handles only an URL to path resp. string conversion.
But it doesn't provide any file type related differentiation, if I'm not wrong.

Right now the format decision is done by looking at 1.) the file suffix and 2.) op_require_read_closest_package_json to check, if a typ: module entry is present somewhere in a package.json file above the script.

But the code without my patch always seemed to choose the exactly opposite consequence based on these checks! ESM files got wrapped in require(...) fragments and CJS files became imported by their URL, not the other way around! (<= But please double-check this claim!)

But .mjs files still seemed to work all the time somehow and simply ignored this defect.
That's why the unit test doesn't report an error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The url_to_node_resolution handles only an URL to path resp. string conversion.
But it doesn't provide any file type related differentiation, if I'm not wrong.

It works for file:/// urls.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's interesting!
Which property of the URL object could be used for a more useful import type specific proceeding?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dsherret: I think, I finally understood now your url_to_node_resolution! :)

I'll try to use it as replacement here as well to minimize the code duplication and unexpected behavior.

ext/node/polyfills/worker_threads.ts Outdated Show resolved Hide resolved
@mash-graz mash-graz marked this pull request as ready for review March 11, 2024 15:51
@bartlomieju
Copy link
Member

@mash-graz can you link to some of the package you said don't work without this patch? We can boil down a test case out of them.

@mash-graz
Copy link
Contributor Author

I noticed the issue while working on this jco porting attempts, but this working tree is in a such a horrible chaotic state right now, that I can hardly share it.

But I'll look, if I can extract a working example till tomorrow...

It's really crazy -- I can somehow understand, what's wrong with this particular code passage, but I can not grasp, why most examples, and the *.mjs files in particular, nevertheless work most of the time, although the get imported in an utterly inadequate way.

@mash-graz
Copy link
Contributor Author

o.k. -- I have prepared a practical demonstration of this issue for you:

  1. checkout the deno_demo branch of https://github.com/mash-graz/jco

  2. at the top level directory use this command to start the mocha test run for the preview2-shim workspace:

❯ ../deno/target/debug/deno run -A npm:mocha -u tdd ./packages/preview2-shim/test/test.js

  Node.js Preview2
test stdouttest stderr    ✔ Stdio
error: Uncaught (in worker "") (in promise) Error: require() of ES Module /home/mash/Projects/jco/packages/preview2-shim/lib/io/worker-thread.js from /home/mash/Projects/jco not supported. Instead change the require to a dynamic import() which is available in all CommonJS modules.
    at createRequireEsmError (node:module:1084:15)
    at Object.Module._extensions..js (node:module:1065:13)
    at Module.load (node:module:919:32)
    at Function.Module._load (node:module:749:12)
    at Module.require (node:module:941:19)
    at require (node:module:1126:16)
    at data:text/javascript,(async function() {c......er-thread.js");})();:1:138

you will get this error, if you execute the unpatched deno git upstream version (47f3182)

but if you utilize this PR everything should work as expected and you will see something like this:

❯ ../deno/target/debug/deno run -A npm:mocha -u tdd ./packages/preview2-shim/test/test.js


  Node.js Preview2
test stdouttest stderr    ✔ Stdio
    1) FS read
    ✔ WASI HTTP (744ms)
    Clocks
      ✔ Wall clock
      ✔ Monotonic clock now
      ✔ Monotonic clock immediately resolved polls
      ✔ Monotonic clock subscribe duration
      ✔ Monotonic clock subscribe instant
    WASI Sockets (TCP)
      ✔ sockets.instanceNetwork() should be a singleton
      ✔ sockets.tcpCreateSocket() should throw not-supported
      ✔ tcp.bind(): should bind to a valid ipv4 address
      2) tcp.bind(): should bind to a valid ipv6 address and port=0
      ✔ tcp.bind(): should throw invalid-argument when invalid address family
      ✔ tcp.bind(): should throw invalid-state when already bound
      ✔ tcp.listen(): should listen to an ipv4 address
      3) tcp.connect(): should connect to a valid ipv4 address and port=0
    WASI Sockets (UDP)
      ✔ sockets.udpCreateSocket() should be a singleton
Warning: setTTL on UDP sockets ignored
      ✔ udp.bind(): should bind to a valid ipv4 address and port=0
Warning: setTTL on UDP sockets ignored
      ✔ udp.bind(): should bind to a valid ipv6 address and port=0
Warning: setTTL on UDP sockets ignored
      4) udp.stream(): should connect to a valid ipv4 address
Warning: setTTL on UDP sockets ignored
      ✔ udp.stream(): should connect to a valid ipv6 address (208ms)


  17 passing (2s)
  4 failing
  [...]

I really can't explain, why the import will not work in this case, but seems to work in many other situations. That's why I couldn't find a more suitable unit test.

But the reported Error: require() of ES Module was rather clear and further debug sessions lead me finally to this section in the source code resp. the suggested changes.

@mash-graz
Copy link
Contributor Author

I just added two additional unit tests.

But creating them, I had to notice, that exceptions generated during Worker creations, will not be caught by these tests! :(

for example, when I try to create a worker with a non-existing path specifier (here: ./cjs-file.xjs), I will see this output:

[worker_threads] inheritances ...error: Uncaught (in worker "$DENO_STD_NODE_WORKER_THREAD") Module not found "file:///home/mash/Projects/deno/tests/unit_node/testdata/worker_module/cjs-file.xjs".
 ok (118ms)

But the test will nevertheless pass and report:

ok | 17 passed | 0 failed (911ms)

@mash-graz
Copy link
Contributor Author

I finally figured out an indirect check for correct worker script import, which will fail on errors.
It's using a combination of error and message event expectation.

This should now at least cover all the possible processing paths within the patched sequence, although the CJS test is rather weak. I don't know if it should accept require(...) or __filename instructions in the imported content?

mash-graz referenced this pull request Mar 13, 2024
This commit fixes race condition in "node:worker_threads" module were
the first message did a setup of "threadId", "workerData" and
"environmentData".
Now this data is passed explicitly during workers creation and is set up
before any user code is executed.

Closes #22783
Closes #22672

---------

Co-authored-by: Satya Rohith <me@satyarohith.com>
@mash-graz mash-graz marked this pull request as draft March 14, 2024 11:10
Copy link
Member

@bartlomieju bartlomieju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR @mash-graz, I'm going to mark it as "Request changes" so that we can first fix regressions in #22934 and #22935 to not introduce more moving pieces and potential regressions. We'll land this one as soon as we fix these two problems.

@mash-graz
Copy link
Contributor Author

That's perfectly fine!

I try to prepare another improved/corrected version in the meanwhile. ;)

@lino-levan
Copy link
Contributor

With both of the linked issues now being resolved, would this PR be up for another review?

@mash-graz
Copy link
Contributor Author

Please wait just one or two days more!

I'm working on a much more satisfying rewrite, which handles both input options (ULRs or path entries) equally well. That's unfortunately not case with all these earlier variants. Therefore, this temporary draft flag again.

@mash-graz
Copy link
Contributor Author

mash-graz commented Mar 18, 2024

It's finally ready for review (again)! :)

I spend a lot of time into input verification and early exception throwing, because initialization errors on the worker side are usually only reported as uncaught exception and hard to handle in unit-tests, etc.

File and Data-URL-Objects should be accepted equally than absolute and relative path strings, in compliance to the expectations defined by the node documentation.

Although I did my best to handle also CJS scripts sufficiently by wrapping them in helper scripts, async mechanisms, Promises and timers will not work in this case. If you enable this line in the unit-test, the whole process will freeze:

//const p = await missing_toplevel_async();

CJS support isn't utterly irrelevant in this particular case, because the support for ESM in workers in VMs in node is extraordinarily problematic. You'll therefore find much code, that still use these old fashioned standards especially for these tasks.

I hope, the changes are mostly acceptable.

And thanks again for all other help!

@mash-graz mash-graz marked this pull request as ready for review March 18, 2024 02:37
@mash-graz mash-graz marked this pull request as draft March 18, 2024 03:53
@bartlomieju
Copy link
Member

@littledivy @satyarohith please review this PR, this might be overlapping with #22977 you're working on.

@mash-graz
Copy link
Contributor Author

Now it's finally passing the CI pipeline, but one important test (execution of the CJS wrapped code) is disabled on windows! :(

That's a really inconvenient situation, because fixing this necessary differences in handling of ESM vs. CJS scripts was the main goal of this PR.

How should we go on?

  • Should just open another PR with a much simpler fix for the original issue as an interim solution, to quickly solve the troubles reported in Support for SvelteKit #17248 and my first issue report?

  • How do you value this attempt to rewrite this section in rust, instead of just using the existing TS code? All the platform specific troubles in regard to URL and file path handling increased my doubts about these attempts.

  • If you are willing to merge this kind of PR, I would at least add a runtime platform check and disable the CMS support on windows.

It's very hard for me to debug the cause of these windows specific bugs. I do not have a setup to reproduce them locally and always waiting for the CI results is an enormous waste of time...

Please give me just a little bit of honest feedback, what would look as the most appreciable solution to you?

@mash-graz
Copy link
Contributor Author

The windows bug seems to be outside of my code changes in require():

 [worker_threads_test 011.97] thread 'worker-7' panicked at ext\node\ops\require.rs:198:57:
[worker_threads_test 011.97] called `Result::unwrap()` on an `Err` value: ()

I'll open another issue and Fix for this problem...
But It shouldn't be seen as a blocker for this PR, because it will happen with all other ad hoc fix attempts as well.

@mash-graz mash-graz force-pushed the esm-workers branch 4 times, most recently from e8f7257 to f50c52f Compare March 19, 2024 01:55
@littledivy
Copy link
Member

Should just open another PR with a much simpler fix for the original issue as an interim solution, to quickly solve the troubles reported in #17248 and my first issue report?

Just tested this PR on Nuxt and Sveltekit. It solves the problem for both, let's try to get this merged.

How do you value this attempt to rewrite this section in rust, instead of just using the existing TS code? All the platform specific troubles in regard to URL and file path handling increased my doubts about these attempts.

I like the port to Rust. Rewrites like these are welcome since they reduce the startup snapshot size.

I just noticed you enabled the tests on Windows. Does it work now?

@mash-graz
Copy link
Contributor Author

I just noticed you enabled the tests on Windows. Does it work now?

Yes -- it should now work on all platforms!
The CI system just stumbles over other flaky WPT problems.

I hope, it's more or less o.k. by now, but somebody should definitely take careful look on the code, because this windows related path issues nearly drove my crazy.

@mash-graz
Copy link
Contributor Author

@bartlomieju could you please take another look at this code.

It should now work on all platforms, but im sure, many thinks could be done better.
If you find code-issues, that should be changed before merging -- no problem!

Copy link
Member

@littledivy littledivy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@satyarohith satyarohith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Nice to have more operations done on the rust side.

@mash-graz
Copy link
Contributor Author

thanks for the approvals! :)

@satyarohith satyarohith merged commit 0d43a63 into denoland:main Mar 20, 2024
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Sveltekit deno task build not working Handling of ESM files executed as node:worker_threads broken
6 participants