Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,16 @@ crate.annotation(
# other workers idle. One patch file holds both because crate_universe applies
# an annotation's patches in sorted label order. Delete this annotation and the
# patch once the pinned or published asupersync contains both fixes.
# The blocking-pool stack patch touches other files, so its order does not
# matter: it gives pool threads the runtime's thread_stack_size (16 MiB in the
# CLI) instead of the standard library's 2 MiB.
crate.annotation(
crate = "asupersync",
patch_args = ["-p1"],
patches = ["//scripts:bazel/patches/asupersync-scheduler.patch"],
patches = [
"//scripts:bazel/patches/asupersync-blocking-pool-stack.patch",
"//scripts:bazel/patches/asupersync-scheduler.patch",
],
version = "0.6.0",
)

Expand Down
13 changes: 8 additions & 5 deletions crates/tracedecay-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ impl Drop for Spinner {
/// an explicit stack size gives every platform the same headroom.
const ASYNC_STACK_BYTES: usize = 16 * 1024 * 1024;
const MAX_ASYNC_WORKER_THREADS: usize = 16;
const MIN_LONG_LIVED_WORKER_THREADS: usize = 2;
/// The runtimes a command's process owns. The native runtime drives every
/// command's future.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand All @@ -256,9 +257,14 @@ fn async_worker_threads(command: Option<&Commands>) -> usize {
if is_one_shot_daemon_client(command) {
return 1;
}
// At least two, even on a one-CPU host: the reactor handoff patch
// (asupersync-scheduler.patch) promotes a parked follower only when
// the runtime has two or more parkers (`count < 2` returns early), so on
// one worker a long poll in `daemon run` or `serve` holds every socket
// wake and timer until it returns.
std::thread::available_parallelism()
.map_or(1, usize::from)
.clamp(1, MAX_ASYNC_WORKER_THREADS)
.clamp(MIN_LONG_LIVED_WORKER_THREADS, MAX_ASYNC_WORKER_THREADS)
}

/// Every command but `daemon run` is a daemon client or a local store tool
Expand Down Expand Up @@ -572,10 +578,7 @@ fn async_main() -> tracedecay_domain::errors::Result<CommandOutcome> {
.thread_name_prefix("tracedecay-native")
.worker_threads(worker_threads)
.thread_stack_size(ASYNC_STACK_BYTES)
.blocking_threads(
1,
blocking_threads.max(native_runtime::MAX_BLOCKING_THREADS),
),
.blocking_threads(1, native_runtime::MAX_BLOCKING_THREADS),
)
.map_err(|error| tracedecay_domain::errors::TraceDecayError::Config {
message: error.to_string(),
Expand Down
26 changes: 22 additions & 4 deletions crates/tracedecay-cli/src/startup_tests.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use super::{
AnalyticsAction, Cli, CommandFamily, CommandRuntime, Commands, DAEMON_CPU_THREADS_ENV,
DEFAULT_MAX_DAEMON_CPU_THREADS, HostBundleCliOptions, PackageHookAction, RAYON_NUM_THREADS_ENV,
ScoopPackageHookAction, StderrTracingDefault, command_profile_label, command_runtime,
daemon_cpu_threads_from, is_one_shot_daemon_client, normalize_tool_reserved_global_flags,
runs_worldwide_counter_flush, should_skip_agent_install_check, should_skip_startup_maintenance,
stderr_tracing_default, try_print_cli_version, validate_host_bundle_options,
ScoopPackageHookAction, StderrTracingDefault, async_worker_threads, command_profile_label,
command_runtime, daemon_cpu_threads_from, is_one_shot_daemon_client,
normalize_tool_reserved_global_flags, runs_worldwide_counter_flush,
should_skip_agent_install_check, should_skip_startup_maintenance, stderr_tracing_default,
try_print_cli_version, validate_host_bundle_options,
};
use clap::{CommandFactory, Parser};
use std::iter;
Expand Down Expand Up @@ -390,6 +391,23 @@ fn status_is_a_one_shot_daemon_client() {
]))));
}

#[test]
fn long_lived_commands_run_at_least_two_native_workers() {
for args in [&["daemon", "run"][..], &["serve"]] {
assert!(
async_worker_threads(Some(&parse_command(args))) >= 2,
"{args:?}"
);
}
for args in [&["status"][..], &["tool", "tracedecay_status"]] {
assert_eq!(
async_worker_threads(Some(&parse_command(args))),
1,
"{args:?}"
);
}
}

#[test]
fn only_the_foreground_daemon_owns_a_tokio_runtime() {
for args in [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ pub async fn settle_owned_blocking_task<T, O>(

/// Starts `work` on the native runtime and detaches it. For work that never
/// reaches Tokio. A refused start is logged, and `work` is dropped.
#[track_caller]
pub(crate) fn spawn_detached(event: &'static str, work: impl Future<Output = ()> + Send + 'static) {
if let Err(error) = native_runtime::NativeTask::spawn(work) {
tracing::warn!(event, %error, "native task did not start");
Expand Down
1 change: 1 addition & 0 deletions crates/tracedecay-lsp/src/gateway/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub trait LspRuntimeTask: Send + Sync {

/// Runtime injection used by broker policy without taking a Tokio dependency.
pub trait LspRuntimeSpawner: Send + Sync {
#[track_caller]
fn spawn(&self, future: LspRuntimeFuture<()>) -> Box<dyn LspRuntimeTask>;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/tracedecay-rusqlite-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ tracedecay-contracts = { path = "../tracedecay-contracts" }
tracedecay-store = { path = "../tracedecay-store" }
tracing = "0.1"

# Thread CPU time for the debug-build long-poll guard on native tasks.
# Thread CPU time for the long-poll count on native tasks.
[target.'cfg(unix)'.dependencies]
libc = "0.2"

Expand Down
Loading
Loading