summaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
authorNathan Whitaker <17734409+nathanwhit@users.noreply.github.com>2024-08-14 15:26:21 -0700
committerGitHub <noreply@github.com>2024-08-14 22:26:21 +0000
commite92a05b5518e5fd30559c96c5990b08657bbc3e4 (patch)
tree037cad394db9097d8f695810426a2de9ba03d825 /runtime
parent875ee618d318ea748e38641108d906eff34a9f86 (diff)
feat(serve): Opt-in parallelism for `deno serve` (#24920)
Adds a `parallel` flag to `deno serve`. When present, we spawn multiple workers to parallelize serving requests. ```bash deno serve --parallel main.ts ``` Currently on linux we use `SO_REUSEPORT` and rely on the fact that the kernel will distribute connections in a round-robin manner. On mac and windows, we sort of emulate this by cloning the underlying file descriptor and passing a handle to each worker. The connections will not be guaranteed to be fairly distributed (and in practice almost certainly won't be), but the distribution is still spread enough to provide a significant performance increase. --- (Run on an Macbook Pro with an M3 Max, serving `deno.com` baseline:: ``` ❯ wrk -d 30s -c 125 --latency http://127.0.0.1:8000 Running 30s test @ http://127.0.0.1:8000 2 threads and 125 connections Thread Stats Avg Stdev Max +/- Stdev Latency 239.78ms 13.56ms 330.54ms 79.12% Req/Sec 258.58 35.56 360.00 70.64% Latency Distribution 50% 236.72ms 75% 248.46ms 90% 256.84ms 99% 268.23ms 15458 requests in 30.02s, 2.47GB read Requests/sec: 514.89 Transfer/sec: 84.33MB ``` this PR (`with --parallel` flag) ``` ❯ wrk -d 30s -c 125 --latency http://127.0.0.1:8000 Running 30s test @ http://127.0.0.1:8000 2 threads and 125 connections Thread Stats Avg Stdev Max +/- Stdev Latency 117.40ms 142.84ms 590.45ms 79.07% Req/Sec 1.33k 175.19 1.77k 69.00% Latency Distribution 50% 22.34ms 75% 223.67ms 90% 357.32ms 99% 460.50ms 79636 requests in 30.07s, 12.74GB read Requests/sec: 2647.96 Transfer/sec: 433.71MB ```
Diffstat (limited to 'runtime')
-rw-r--r--runtime/js/99_main.js49
-rw-r--r--runtime/worker_bootstrap.rs40
2 files changed, 78 insertions, 11 deletions
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index ca96e34b7..5e25a3818 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -45,6 +45,7 @@ const {
PromiseResolve,
SafeSet,
StringPrototypeIncludes,
+ StringPrototypePadEnd,
StringPrototypeSplit,
StringPrototypeTrim,
Symbol,
@@ -709,8 +710,37 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
11: mode,
12: servePort,
13: serveHost,
+ 14: serveIsMain,
+ 15: serveWorkerCount,
} = runtimeOptions;
+ if (mode === executionModes.serve) {
+ if (serveIsMain && serveWorkerCount) {
+ const origLog = console.log;
+ const origError = console.error;
+ const prefix = `[serve-worker-0 ]`;
+ console.log = (...args) => {
+ return origLog(prefix, ...new primordials.SafeArrayIterator(args));
+ };
+ console.error = (...args) => {
+ return origError(prefix, ...new primordials.SafeArrayIterator(args));
+ };
+ } else if (serveWorkerCount !== null) {
+ const origLog = console.log;
+ const origError = console.error;
+ const base = `serve-worker-${serveWorkerCount + 1}`;
+ // 15 = "serve-worker-nn".length, assuming
+ // serveWorkerCount < 100
+ const prefix = `[${StringPrototypePadEnd(base, 15, " ")}]`;
+ console.log = (...args) => {
+ return origLog(prefix, ...new primordials.SafeArrayIterator(args));
+ };
+ console.error = (...args) => {
+ return origError(prefix, ...new primordials.SafeArrayIterator(args));
+ };
+ }
+ }
+
if (mode === executionModes.run || mode === executionModes.serve) {
let serve = undefined;
core.addMainModuleHandler((main) => {
@@ -725,13 +755,16 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
}
if (mode === executionModes.serve && !serve) {
- console.error(
- `%cerror: %cdeno serve requires %cexport default { fetch }%c in the main module, did you mean to run \"deno run\"?`,
- "color: yellow;",
- "color: inherit;",
- "font-weight: bold;",
- "font-weight: normal;",
- );
+ if (serveIsMain) {
+ // Only error if main worker
+ console.error(
+ `%cerror: %cdeno serve requires %cexport default { fetch }%c in the main module, did you mean to run \"deno run\"?`,
+ "color: yellow;",
+ "color: inherit;",
+ "font-weight: bold;",
+ "font-weight: normal;",
+ );
+ }
return;
}
@@ -746,7 +779,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
);
}
if (mode === executionModes.serve) {
- serve({ servePort, serveHost });
+ serve({ servePort, serveHost, serveIsMain, serveWorkerCount });
}
}
});
diff --git a/runtime/worker_bootstrap.rs b/runtime/worker_bootstrap.rs
index b13c3c428..afd3242e8 100644
--- a/runtime/worker_bootstrap.rs
+++ b/runtime/worker_bootstrap.rs
@@ -10,7 +10,6 @@ use deno_terminal::colors;
/// The execution mode for this worker. Some modes may have implicit behaviour.
#[derive(Copy, Clone)]
-#[repr(u8)]
pub enum WorkerExecutionMode {
/// No special behaviour.
None,
@@ -28,11 +27,39 @@ pub enum WorkerExecutionMode {
/// `deno bench`
Bench,
/// `deno serve`
- Serve,
+ Serve {
+ is_main: bool,
+ worker_count: Option<usize>,
+ },
/// `deno jupyter`
Jupyter,
}
+impl WorkerExecutionMode {
+ pub fn discriminant(&self) -> u8 {
+ match self {
+ WorkerExecutionMode::None => 0,
+ WorkerExecutionMode::Worker => 1,
+ WorkerExecutionMode::Run => 2,
+ WorkerExecutionMode::Repl => 3,
+ WorkerExecutionMode::Eval => 4,
+ WorkerExecutionMode::Test => 5,
+ WorkerExecutionMode::Bench => 6,
+ WorkerExecutionMode::Serve { .. } => 7,
+ WorkerExecutionMode::Jupyter => 8,
+ }
+ }
+ pub fn serve_info(&self) -> (Option<bool>, Option<usize>) {
+ match *self {
+ WorkerExecutionMode::Serve {
+ is_main,
+ worker_count,
+ } => (Some(is_main), worker_count),
+ _ => (None, None),
+ }
+ }
+}
+
/// The log level to use when printing diagnostic log messages, warnings,
/// or errors in the worker.
///
@@ -175,6 +202,10 @@ struct BootstrapV8<'a>(
u16,
// serve host
Option<&'a str>,
+ // serve is main
+ Option<bool>,
+ // serve worker count
+ Option<usize>,
);
impl BootstrapOptions {
@@ -186,6 +217,7 @@ impl BootstrapOptions {
let scope = RefCell::new(scope);
let ser = deno_core::serde_v8::Serializer::new(&scope);
+ let (serve_is_main, serve_worker_count) = self.mode.serve_info();
let bootstrap = BootstrapV8(
self.location.as_ref().map(|l| l.as_str()),
self.unstable,
@@ -198,9 +230,11 @@ impl BootstrapOptions {
self.disable_deprecated_api_warning,
self.verbose_deprecated_api_warning,
self.future,
- self.mode as u8 as _,
+ self.mode.discriminant() as _,
self.serve_port.unwrap_or_default(),
self.serve_host.as_deref(),
+ serve_is_main,
+ serve_worker_count,
);
bootstrap.serialize(ser).unwrap()