Make runner cleanup timeout configurable via a flag.

PiperOrigin-RevId: 891876869
diff --git a/centipede/centipede_callbacks.cc b/centipede/centipede_callbacks.cc
index c37fa85..6537212 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 @@
           [&](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 @@
     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 ad7b1f4..1a171d7 100644
--- a/centipede/centipede_flags.inc
+++ b/centipede/centipede_flags.inc
@@ -160,6 +160,8 @@
     "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 bd137b5..0653dce 100644
--- a/fuzztest/init_fuzztest.cc
+++ b/fuzztest/init_fuzztest.cc
@@ -176,6 +176,11 @@
     "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 "
     "is found. If set to false (default), the test execution will stop upon "
@@ -369,6 +374,7 @@
       /*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 2a9c00a..d6b0032 100644
--- a/fuzztest/internal/BUILD
+++ b/fuzztest/internal/BUILD
@@ -56,6 +56,7 @@
         ":io",
         ":logging",
         ":runtime",
+        ":serialization",
         ":subprocess",
         ":table_of_recent_compares",
         "@abseil-cpp//absl/algorithm:container",
@@ -87,7 +88,6 @@
         "@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 5ac3882..c4fc989 100644
--- a/fuzztest/internal/centipede_adaptor.cc
+++ b/fuzztest/internal/centipede_adaptor.cc
@@ -269,6 +269,7 @@
                       // 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 459eb4d..0d55738 100644
--- a/fuzztest/internal/configuration.cc
+++ b/fuzztest/internal/configuration.cc
@@ -197,6 +197,8 @@
 }  // 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 @@
              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 @@
   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 @@
     ASSIGN_OR_RETURN(replay_in_single_process, Consume<bool>(serialized));
     ASSIGN_OR_RETURN(execution_id, ConsumeOptionalString(serialized));
     ASSIGN_OR_RETURN(print_subprocess_log, Consume<bool>(serialized));
+    ASSIGN_OR_RETURN(subprocess_cleanup_timeout_str, ConsumeString(serialized));
     ASSIGN_OR_RETURN(stack_limit, Consume<size_t>(serialized));
     ASSIGN_OR_RETURN(rss_limit, Consume<size_t>(serialized));
     ASSIGN_OR_RETURN(time_limit_per_input_str, ConsumeString(serialized));
@@ -275,6 +280,8 @@
       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 @@
                          *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 a6fc37c..34e61f6 100644
--- a/fuzztest/internal/configuration.h
+++ b/fuzztest/internal/configuration.h
@@ -83,6 +83,8 @@
   std::optional<std::string> 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 4b0796c..fe79800 100644
--- a/fuzztest/internal/configuration_test.cc
+++ b/fuzztest/internal/configuration_test.cc
@@ -28,6 +28,8 @@
          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 @@
                               /*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 @@
                               /*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),