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

perf(ext/fetch): use trust_dns_resolver instead of default GaiResolver #26740

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

sahandevs
Copy link

I've noticed while calling the fetch() function, a new thread spawns, which I don't think is necessary. We can reproduce this easily, by running the following script and watching new threads spawn and die using htop.

function sleep(ms: number) {
  return new Promise(r => setTimeout(r, ms));
}
console.log("pid=", Deno.pid);
while(true) {
  await sleep(Math.random() * 100 + 10000);
  console.log(Date.now());
  try {
    await fetch(`https://google-${parseInt(Math.random() * 100)}.com`);
  } catch (e) {}
}

export { }

So I investigated this issue, it seems hyper_util::builder uses something called GaiResolver in order to resolve hosts. GaiResolver works by calling stdlib's to_socket_addrs in a tokio::spawn_blocking which causes tokio to spawn some worker threads.

This PR fixes this issue by using trust_dns_resolver and replacing the GaiResolver.

@CLAassistant
Copy link

CLAassistant commented Nov 5, 2024

CLA assistant check
All committers have signed the CLA.

@sahandevs sahandevs force-pushed the use-async-resolver-in-ext-fetch branch from f5e7f20 to 377ed02 Compare November 5, 2024 13:09
@bartlomieju
Copy link
Member

Can you please provide some information what prompted you to open this PR? It is expected that there will be several Tokio threads spawned - Deno puts a limit of "blocking" threads at 32 (or 64 on Windows) that can be used for async FS operations and others, so these won't grow unbounded.

I'm skeptical of switching out the resolver as it might lead to a subtle bugs that will be hard to debug, we might want to consider having an env var, or a flag that switches the resolver though.

@sahandevs
Copy link
Author

sahandevs commented Nov 5, 2024

@bartlomieju
I'm using deno runtime in order run scripts in isolation (like how https://github.com/supabase/edge-runtime uses deno runtime to create edge function, albeit edge-runtime has its own runtime and only uses deno_core I think). I'm running every script with separate instances of deno runtime and tokio rt pair (which is single threaded) and
Because the number of scripts are high, I prefer limiting each script to a single thread so I won't need to do any extra book keeping (I've also limited or removed APIs that causes threads to spawn). Also because each script has it's own tokio rt, the 32 blocking thread is per script (this is for my use-case of course, it shouldn't be a problem with the normal deno use)

If you are worried about the subtle bugs (which I do agree may happen, especially because it's DNS), how about I add something like resolver: Resolver::Gai /* Resolver::Trust*/ in runtime init and an env variable (like your suggestion) for deno cli?

@sahandevs sahandevs force-pushed the use-async-resolver-in-ext-fetch branch from 377ed02 to e672d0a Compare November 5, 2024 13:39
@bartlomieju
Copy link
Member

@bartlomieju I'm using deno runtime in order run scripts in isolation (like how https://github.com/supabase/edge-runtime uses deno runtime to create edge function, albeit edge-runtime has its own runtime and only uses deno_core I think). I'm running every script with separate instances of deno runtime and tokio rt pair (which is single threaded) and Because the number of scripts are high, I prefer limiting each script to a single thread so I won't need to do any extra book keeping (I've also limited or removed APIs that causes threads to spawn). Also because each script has it's own tokio rt, the 32 blocking thread is per script (this is for my use-case of course, it shouldn't be a problem with the normal deno use)

If you are worried about the subtle bugs (which I do agree may happen, especially because it's DNS), how about I add something like resolver: Resolver::Gai /* Resolver::Trust*/ in runtime init and an env variable (like your suggestion) for deno cli?

Thanks for explanation, feels like something that was already tried in #22293 and #17166. I'm fine in making the resolver configurable for embedders, but for now I'd like to keep using the default resolver in Deno itself. If you could restructure the code to make resolver configurable in deno_fetch extension, I'll be happy to merge the PR, but the default implementation that the CLI uses should stay the same.

@sahandevs sahandevs force-pushed the use-async-resolver-in-ext-fetch branch from e672d0a to d806f7a Compare November 6, 2024 11:07
@sahandevs
Copy link
Author

@bartlomieju I've applied your suggestion and made trust resolver optional. Would you please take a look?

@bartlomieju bartlomieju added this to the 2.1.0 milestone Nov 6, 2024
@bartlomieju
Copy link
Member

@sahandevs I'm off for a few days, I'll review and land this PR next week!

@sahandevs sahandevs force-pushed the use-async-resolver-in-ext-fetch branch 4 times, most recently from c0cf2b1 to e5e2687 Compare November 12, 2024 20:43
@@ -50,6 +50,7 @@ async fn main() -> Result<(), AnyError> {
node_services: Default::default(),
npm_process_state_provider: Default::default(),
root_cert_store_provider: Default::default(),
fetch_resolver: Default::default(),
Copy link
Member

Choose a reason for hiding this comment

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

Can you please rename this to fetch_dns_resolver?

ext/kv/remote.rs Outdated
@@ -197,6 +197,7 @@ impl<P: RemoteDbHandlerPermissions + 'static> DatabaseHandler
root_cert_store: options.root_cert_store()?,
ca_certs: vec![],
proxy: options.proxy.clone(),
resolver: Default::default(),
Copy link
Member

Choose a reason for hiding this comment

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

And this one to dns_resolver

ext/fetch/dns.rs Outdated
if join_err.is_cancelled() {
Err(io::Error::new(io::ErrorKind::Interrupted, join_err))
} else {
panic!("gai background task failed: {:?}", join_err)
Copy link
Member

Choose a reason for hiding this comment

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

This should be handled gracefully with an error and not a panic

@sahandevs sahandevs force-pushed the use-async-resolver-in-ext-fetch branch from e5e2687 to 3733b68 Compare November 14, 2024 13:38
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.

3 participants