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(core/modules): Fix concurrent loading of dynamic imports #11089

Merged
merged 2 commits into from
Jun 29, 2021

Conversation

nayeemrmn
Copy link
Collaborator

Fixes #3736.

The following guard:

if !is_registered {
is the cause of the bug. It causes RecursiveModuleLoad to not recurse a module foo's dependencies if it is already in the module map, causing the load to complete early, and letting the runtime try to instantiate foo. But if there is another pending load of foo, it may be the case that foo is in the module map but its dependencies aren't yet, which will cause instantiation to fail. So we should always visit all dependencies in a load and check registration for each module instead.

cc @bartlomieju

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.

@nayeemrmn great to see you looked into it. I've got some reservations that even though it fixes provided test case it might not be perfect solution.

Could you please use test case from #8421 (the unit test case to put in deno_core instead of integration one). I'm afraid we might have multiple calls to load() for the same module (would be good to assert number of the loads).

I guess this is a good way forward but ultimately we should keep track of all visited modules in ModuleMap instead of RecursiveModuleLoad.

core/modules.rs Outdated Show resolved Hide resolved
@nayeemrmn nayeemrmn marked this pull request as draft June 23, 2021 14:27
core/modules.rs Outdated Show resolved Hide resolved
@nayeemrmn nayeemrmn marked this pull request as ready for review June 24, 2021 08:18
@bartlomieju
Copy link
Member

I applied my unit test from #8421 and it shows that there are 7 module loads happening. I think this is correct so we should be able to land it. But before that, please rework the integration test into unit test in core/modules.rs (or take it directly from #8421)

core/modules.rs Outdated Show resolved Hide resolved
@nayeemrmn nayeemrmn marked this pull request as draft June 24, 2021 16:02
@nayeemrmn nayeemrmn marked this pull request as ready for review June 26, 2021 10:34
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.

Looks good to me 🚀

Thank you Nayeem, this problem has been haunting me for the past half a year. This is a very elegant solution to the problem, and it didn't require rewriting all of the module loading subsystem. Cleanly implemented with plenty of comments. Cheers!

init: LoadInit,
// TODO(bartlomieju): in future this value should
// be randomized
pub id: ModuleLoadId,
pub root_module_id: Option<ModuleId>,
pub state: LoadState,
pub module_map_rc: Rc<RefCell<ModuleMap>>,
Copy link
Member

Choose a reason for hiding this comment

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

Aha, nice!

Comment on lines +344 to +357
// Recurse the module's imports. There are two cases for each import:
// 1. If the module is not in the module map, start a new load for it in
// `self.pending`. The result of that load should eventually be passed to
// this function for recursion.
// 2. If the module is already in the module map, queue it up to be
// recursed synchronously here.
// This robustly ensures that the whole graph is in the module map before
// `LoadState::Done` is set.
let specifier =
crate::resolve_url(&module_source.module_url_found).unwrap();
let mut already_registered = VecDeque::new();
already_registered.push_back((module_id, specifier.clone()));
self.visited.insert(specifier);
while let Some((module_id, referrer)) = already_registered.pop_front() {
Copy link
Member

Choose a reason for hiding this comment

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

Nice! I believe this is the meat of this PR, very elegantly solved 👍

Comment on lines +415 to +419
// TODO(nayeemrmn): In this case we would ideally skip to
// `LoadState::LoadingImports` and synchronously recurse the imports
// like the bottom of `RecursiveModuleLoad::register_and_recurse()`.
// But the module map cannot be borrowed here. Instead fake a load
// event so it gets passed to that function and recursed eventually.
Copy link
Member

Choose a reason for hiding this comment

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

We can address this bit when adding support for import assertions

Comment on lines +1281 to +1283
// Regression test for https://github.com/denoland/deno/issues/3736.
#[test]
fn dyn_concurrent_circular_import() {
Copy link
Member

Choose a reason for hiding this comment

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

Thanks

@bartlomieju bartlomieju merged commit c577c27 into denoland:main Jun 29, 2021
@nayeemrmn nayeemrmn deleted the concurrent-dynamic-imports branch June 29, 2021 09:03
ry pushed a commit that referenced this pull request Jun 29, 2021
This commit changes implementation of module loading in "deno_core"
to track all currently fetched modules across all existing module loads.

In effect a bug that caused concurrent dynamic imports referencing the 
same module to fail is fixed.
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.

module resolve failed with dynamic import and circulated import
2 participants