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(node): seperate worker module cache #23634

Merged
merged 11 commits into from
May 16, 2024
62 changes: 61 additions & 1 deletion cli/module_loader.rs
Copy link
Member

Choose a reason for hiding this comment

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

How's the memory usage? Does it reuse the Arc<str> when it can? (ex. for remote modules)

Also, what happens with --reload?

Copy link
Member

Choose a reason for hiding this comment

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

@littledivy do you think we could reduce memory usage by sharing the strings in memory when they're the same?

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, maybe we could store specifier -> hash mapping in a shared container and compare that in the file fetcher. Doing this check without comparing specifiers first may be expensive but more effective in some cases.

Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ impl ModuleLoadPreparer {
}
}

pub fn new_for_worker(
&self,
graph_container: Arc<ModuleGraphContainer>,
) -> Self {
Self {
options: self.options.clone(),
graph_container,
lockfile: self.lockfile.clone(),
module_graph_builder: self.module_graph_builder.clone(),
progress_bar: self.progress_bar.clone(),
type_checker: self.type_checker.clone(),
}
}

/// This method must be called for a module or a static importer of that
/// module before attempting to `load()` it from a `JsRuntime`. It will
/// populate the graph data in memory with the necessary source code, write
Expand Down Expand Up @@ -221,13 +235,22 @@ impl ModuleLoadPreparer {
}
}

#[derive(Clone)]
struct PreparedModuleLoader {
emitter: Arc<Emitter>,
graph_container: Arc<ModuleGraphContainer>,
parsed_source_cache: Arc<ParsedSourceCache>,
}

impl PreparedModuleLoader {
pub fn for_worker(&self, graph_container: Arc<ModuleGraphContainer>) -> Self {
littledivy marked this conversation as resolved.
Show resolved Hide resolved
Self {
emitter: self.emitter.clone(),
graph_container,
parsed_source_cache: self.parsed_source_cache.clone(),
}
}

pub fn load_prepared_module(
&self,
specifier: &ModuleSpecifier,
Expand Down Expand Up @@ -319,6 +342,34 @@ struct SharedCliModuleLoaderState {
module_info_cache: Arc<ModuleInfoCache>,
}

impl SharedCliModuleLoaderState {
fn for_worker(&self) -> Self {
let graph_container =
Arc::new(ModuleGraphContainer::new(deno_graph::GraphKind::All));
let module_load_preparer = self
.module_load_preparer
.new_for_worker(graph_container.clone());
let prepared_module_loader = self
.prepared_module_loader
.for_worker(graph_container.clone());

Self {
lib_window: self.lib_worker,
lib_worker: self.lib_worker,
is_inspecting: self.is_inspecting,
is_repl: self.is_repl,
graph_container,
module_load_preparer: Arc::new(module_load_preparer),
prepared_module_loader,
resolver: self.resolver.clone(),
node_resolver: self.node_resolver.clone(),
npm_module_loader: self.npm_module_loader.clone(),
code_cache: self.code_cache.clone(),
module_info_cache: self.module_info_cache.clone(),
}
}
}

pub struct CliModuleLoaderFactory {
shared: Arc<SharedCliModuleLoaderState>,
}
Expand Down Expand Up @@ -367,12 +418,19 @@ impl CliModuleLoaderFactory {
lib: TsTypeLib,
root_permissions: PermissionsContainer,
dynamic_permissions: PermissionsContainer,
worker: bool,
) -> Rc<dyn ModuleLoader> {
let shared = if worker {
Arc::new(self.shared.for_worker())
} else {
self.shared.clone()
};

Rc::new(CliModuleLoader {
lib,
root_permissions,
dynamic_permissions,
shared: self.shared.clone(),
shared,
})
}
}
Expand All @@ -387,6 +445,7 @@ impl ModuleLoaderFactory for CliModuleLoaderFactory {
self.shared.lib_window,
root_permissions,
dynamic_permissions,
false,
)
}

Expand All @@ -399,6 +458,7 @@ impl ModuleLoaderFactory for CliModuleLoaderFactory {
self.shared.lib_worker,
root_permissions,
dynamic_permissions,
true,
)
}

Expand Down
1 change: 1 addition & 0 deletions cli/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ impl CliNodeResolver {
}
}

#[derive(Clone)]
pub struct NpmModuleLoader {
cjs_resolutions: Arc<CjsResolutionStore>,
node_code_translator: Arc<CliNodeCodeTranslator>,
Expand Down
4 changes: 4 additions & 0 deletions tests/specs/node/worker_threads_cache/__test__.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
Copy link
Member

Choose a reason for hiding this comment

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

nit: it would be nice to convert this test into a step under worker_threads folder instead of creating worker_threads_cache folder.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm. I thought it would be clean to convert several tests of a module into steps but it will break reporting.

Copy link
Member

Choose a reason for hiding this comment

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

After #23667, you can now declare serveral tests in __test__.jsonc itself.

"args": "run -A main.ts",
"out": "main.out"
}
2 changes: 2 additions & 0 deletions tests/specs/node/worker_threads_cache/main.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[Module: null prototype] { default: true }
[Module: null prototype] { default: false }
13 changes: 13 additions & 0 deletions tests/specs/node/worker_threads_cache/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import fs from "node:fs/promises";
import { isMainThread, Worker } from "node:worker_threads";

await fs.writeFile("mod.mjs", "export default " + isMainThread);

const path = new URL("mod.mjs", import.meta.url);
const i = await import(path.href);
console.log(i);

if (isMainThread) {
const worker = new Worker(new URL("test.mjs", import.meta.url));
worker.on("message", (msg) => console.log(msg));
}
Loading