From 96e0bf47f71f15c921538f6f84e84d940dd00849 Mon Sep 17 00:00:00 2001 From: Xinhao Yuan Date: Mon, 30 Mar 2026 13:20:39 -0700 Subject: [PATCH] Make runner cleanup timeout configurable via a flag. PiperOrigin-RevId: 891876869 --- centipede/centipede_callbacks.cc | 8 ++++---- centipede/centipede_flags.inc | 2 ++ fuzztest/init_fuzztest.cc | 6 ++++++ fuzztest/internal/BUILD | 2 +- fuzztest/internal/centipede_adaptor.cc | 1 + fuzztest/internal/configuration.cc | 10 +++++++++- fuzztest/internal/configuration.h | 2 ++ fuzztest/internal/configuration_test.cc | 4 ++++ 8 files changed, 29 insertions(+), 6 deletions(-) diff --git a/centipede/centipede_callbacks.cc b/centipede/centipede_callbacks.cc index c37fa85bb..653721210 100644 --- a/centipede/centipede_callbacks.cc +++ b/centipede/centipede_callbacks.cc @@ -62,7 +62,6 @@ namespace fuzztest::internal { -constexpr auto kCommandCleanupTimeout = absl::Seconds(60); constexpr auto kPollMinimalTimeout = absl::Milliseconds(1); class CentipedeCallbacks::PersistentModeServer { @@ -458,7 +457,8 @@ void CentipedeCallbacks::CleanUpPersistentMode() { [&](auto& command_context) { if (command_context->cmd.is_executing() && command_context->persistent_mode_server != nullptr) { - const absl::Time deadline = absl::Now() + kCommandCleanupTimeout; + const absl::Time deadline = + absl::Now() + env_.runner_cleanup_timeout; command_context->persistent_mode_server->RequestExit(deadline); const auto ret = command_context->cmd.Wait(deadline); FUZZTEST_LOG_IF(ERROR, !ret.has_value()) @@ -506,9 +506,9 @@ int CentipedeCallbacks::RunBatchForBinary(std::string_view binary) { exit_code = [&] { if (!cmd.is_executing()) return EXIT_FAILURE; FUZZTEST_LOG(ERROR) << "Cleaning up the batch execution with timeout: " - << kCommandCleanupTimeout; + << env_.runner_cleanup_timeout; cmd.RequestStop(); - const auto ret = cmd.Wait(absl::Now() + kCommandCleanupTimeout); + const auto ret = cmd.Wait(absl::Now() + env_.runner_cleanup_timeout); if (ret.has_value()) return *ret; FUZZTEST_LOG(ERROR) << "Failed to wait for the batch execution cleanup."; return EXIT_FAILURE; diff --git a/centipede/centipede_flags.inc b/centipede/centipede_flags.inc index ad7b1f4be..1a171d769 100644 --- a/centipede/centipede_flags.inc +++ b/centipede/centipede_flags.inc @@ -160,6 +160,8 @@ CENTIPEDE_FLAG( "vary depending on the runner.") CENTIPEDE_FLAG(size_t, ignore_timeout_reports, false, "If set, will ignore reporting timeouts as errors.") +CENTIPEDE_FLAG(absl::Duration, runner_cleanup_timeout, absl::Seconds(60), + "Cleanup timeout for runner commands.") CENTIPEDE_FLAG( absl::Time, stop_at, absl::InfiniteFuture(), "Stop fuzzing in all shards (--total_shards) at approximately this " diff --git a/fuzztest/init_fuzztest.cc b/fuzztest/init_fuzztest.cc index bd137b529..0653dce44 100644 --- a/fuzztest/init_fuzztest.cc +++ b/fuzztest/init_fuzztest.cc @@ -175,6 +175,11 @@ FUZZTEST_DEFINE_FLAG( bool, print_subprocess_log, false, "If set, print the log of the subprocesses spawned by FuzzTest."); +FUZZTEST_DEFINE_FLAG( + absl::Duration, subprocess_cleanup_timeout, absl::Seconds(60), + "The timeout for the subprocesses spanwed by FuzzTest to exit cleanly. " + "After the timeout, the subprocesses will be forcefully terminated."); + FUZZTEST_DEFINE_FLAG( bool, continue_after_crash, false, "Controls the fuzzing and corpus replaying behavior when a crashing input " @@ -369,6 +374,7 @@ internal::Configuration CreateConfigurationsFromFlags( /*replay_in_single_process=*/false, absl::GetFlag(FUZZTEST_FLAG(execution_id)), absl::GetFlag(FUZZTEST_FLAG(print_subprocess_log)), + absl::GetFlag(FUZZTEST_FLAG(subprocess_cleanup_timeout)), /*stack_limit=*/absl::GetFlag(FUZZTEST_FLAG(stack_limit_kb)) * 1024, /*rss_limit=*/absl::GetFlag(FUZZTEST_FLAG(rss_limit_mb)) * 1024 * 1024, absl::GetFlag(FUZZTEST_FLAG(time_limit_per_input)), diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD index 2a9c00a68..d6b0032d9 100644 --- a/fuzztest/internal/BUILD +++ b/fuzztest/internal/BUILD @@ -56,6 +56,7 @@ cc_library( ":io", ":logging", ":runtime", + ":serialization", ":subprocess", ":table_of_recent_compares", "@abseil-cpp//absl/algorithm:container", @@ -87,7 +88,6 @@ cc_library( "@com_google_fuzztest//common:logging", "@com_google_fuzztest//common:remote_file", "@com_google_fuzztest//common:temp_dir", - "@com_google_fuzztest//fuzztest/internal:serialization", "@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl", ], ) diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc index 5ac388206..c4fc98991 100644 --- a/fuzztest/internal/centipede_adaptor.cc +++ b/fuzztest/internal/centipede_adaptor.cc @@ -269,6 +269,7 @@ fuzztest::internal::Environment CreateCentipedeEnvironmentFromConfiguration( // Always fail on crash for reproducer tests. configuration.crashing_input_to_reproduce.has_value(); env.print_runner_log = configuration.print_subprocess_log; + env.runner_cleanup_timeout = configuration.subprocess_cleanup_timeout; env.workdir = workdir; if (configuration.corpus_database.empty()) { if (total_time_limit != absl::InfiniteDuration()) { diff --git a/fuzztest/internal/configuration.cc b/fuzztest/internal/configuration.cc index 459eb4d67..0d5573890 100644 --- a/fuzztest/internal/configuration.cc +++ b/fuzztest/internal/configuration.cc @@ -197,6 +197,8 @@ absl::StatusOr ParseDuration(absl::string_view duration) { } // namespace std::string Configuration::Serialize() const { + std::string subprocess_cleanup_timeout_str = + absl::FormatDuration(subprocess_cleanup_timeout); std::string time_limit_per_input_str = absl::FormatDuration(time_limit_per_input); std::string time_limit_str = absl::FormatDuration(time_limit); @@ -209,7 +211,8 @@ std::string Configuration::Serialize() const { SpaceFor(reproduce_findings_as_separate_tests) + SpaceFor(replay_coverage_inputs) + SpaceFor(only_replay) + SpaceFor(replay_in_single_process) + SpaceFor(execution_id) + - SpaceFor(print_subprocess_log) + SpaceFor(stack_limit) + + SpaceFor(print_subprocess_log) + + SpaceFor(subprocess_cleanup_timeout_str) + SpaceFor(stack_limit) + SpaceFor(rss_limit) + SpaceFor(time_limit_per_input_str) + SpaceFor(time_limit_str) + SpaceFor(time_budget_type_str) + SpaceFor(jobs) + SpaceFor(centipede_command) + @@ -229,6 +232,7 @@ std::string Configuration::Serialize() const { offset = WriteIntegral(out, offset, replay_in_single_process); offset = WriteOptionalString(out, offset, execution_id); offset = WriteIntegral(out, offset, print_subprocess_log); + offset = WriteString(out, offset, subprocess_cleanup_timeout_str); offset = WriteIntegral(out, offset, stack_limit); offset = WriteIntegral(out, offset, rss_limit); offset = WriteString(out, offset, time_limit_per_input_str); @@ -260,6 +264,7 @@ absl::StatusOr Configuration::Deserialize( ASSIGN_OR_RETURN(replay_in_single_process, Consume(serialized)); ASSIGN_OR_RETURN(execution_id, ConsumeOptionalString(serialized)); ASSIGN_OR_RETURN(print_subprocess_log, Consume(serialized)); + ASSIGN_OR_RETURN(subprocess_cleanup_timeout_str, ConsumeString(serialized)); ASSIGN_OR_RETURN(stack_limit, Consume(serialized)); ASSIGN_OR_RETURN(rss_limit, Consume(serialized)); ASSIGN_OR_RETURN(time_limit_per_input_str, ConsumeString(serialized)); @@ -275,6 +280,8 @@ absl::StatusOr Configuration::Deserialize( return absl::InvalidArgumentError( "Buffer is not empty after consuming a serialized configuration."); } + ASSIGN_OR_RETURN(subprocess_cleanup_timeout, + ParseDuration(*subprocess_cleanup_timeout_str)); ASSIGN_OR_RETURN(time_limit_per_input, ParseDuration(*time_limit_per_input_str)); ASSIGN_OR_RETURN(time_limit, ParseDuration(*time_limit_str)); @@ -293,6 +300,7 @@ absl::StatusOr Configuration::Deserialize( *replay_in_single_process, *std::move(execution_id), *print_subprocess_log, + *subprocess_cleanup_timeout, *stack_limit, *rss_limit, *time_limit_per_input, diff --git a/fuzztest/internal/configuration.h b/fuzztest/internal/configuration.h index a6fc37c37..34e61f6a9 100644 --- a/fuzztest/internal/configuration.h +++ b/fuzztest/internal/configuration.h @@ -83,6 +83,8 @@ struct Configuration { std::optional execution_id; // If set, print log from subprocesses spawned by FuzzTest. bool print_subprocess_log = false; + // The timeout for cleaning up subprocesses spawned by FuzzTest. + absl::Duration subprocess_cleanup_timeout = absl::Seconds(60); // Stack limit in bytes. size_t stack_limit = 128 * 1024; diff --git a/fuzztest/internal/configuration_test.cc b/fuzztest/internal/configuration_test.cc index 4b0796cc2..fe79800e3 100644 --- a/fuzztest/internal/configuration_test.cc +++ b/fuzztest/internal/configuration_test.cc @@ -28,6 +28,8 @@ MATCHER_P(IsOkAndEquals, config, "") { config.replay_in_single_process == other->replay_in_single_process && config.execution_id == other->execution_id && config.print_subprocess_log == other->print_subprocess_log && + config.subprocess_cleanup_timeout == + other->subprocess_cleanup_timeout && config.stack_limit == other->stack_limit && config.rss_limit == other->rss_limit && config.time_limit_per_input == other->time_limit_per_input && @@ -56,6 +58,7 @@ TEST(ConfigurationTest, /*replay_in_single_process=*/true, "execution_id", /*print_subprocess_log=*/true, + /*subprocess_cleanup_time=*/absl::Seconds(42), /*stack_limit=*/100, /*rss_limit=*/200, /*time_limit_per_input=*/absl::Seconds(42), @@ -85,6 +88,7 @@ TEST(ConfigurationTest, /*replay_in_single_process=*/true, "execution_id", /*print_subprocess_log=*/true, + /*subprocess_cleanup_time=*/absl::Seconds(42), /*stack_limit=*/100, /*rss_limit=*/200, /*time_limit_per_input=*/absl::Seconds(42),