Add stop request reason and separate stop request and set stop time reset. The stop reason is to be exported later. The separation is because that we don't want to clean up the stop request when resetting the stop time, otherwise there is a time window between checking early stop and resetting the early stop where the requests issued in-between (mainly from the signal handlers) would be ignored. PiperOrigin-RevId: 937374916
diff --git a/centipede/centipede.cc b/centipede/centipede.cc index e2a0ee3..58876ec 100644 --- a/centipede/centipede.cc +++ b/centipede/centipede.cc
@@ -470,7 +470,9 @@ if (stop_condition_.EarlyStopRequested()) return false; if (!success && env_.exit_on_crash) { FUZZTEST_LOG(INFO) << "--exit_on_crash is enabled; exiting soon"; - stop_condition_.RequestEarlyStop(EXIT_FAILURE); + stop_condition_.RequestEarlyStop(EXIT_FAILURE, + "A crash was found in the test with " + "--exit_on_crash set for the engine"); return false; } bool batch_gained_new_coverage = false; @@ -1017,7 +1019,7 @@ if (batch_result.IsSkippedTest()) { log_execution_failure("Skipped Test: "); FUZZTEST_LOG(INFO) << "Requesting early stop due to skipped test."; - stop_condition_.RequestEarlyStop(EXIT_SUCCESS); + stop_condition_.RequestEarlyStop(EXIT_SUCCESS, "The test was skipped"); return; } @@ -1025,7 +1027,7 @@ log_execution_failure("Test Setup Failure: "); FUZZTEST_LOG(INFO) << "Requesting early stop due to setup failure in the test."; - stop_condition_.RequestEarlyStop(EXIT_FAILURE); + stop_condition_.RequestEarlyStop(EXIT_FAILURE, "Setup failed in the test"); return; }
diff --git a/centipede/centipede_default_callbacks.cc b/centipede/centipede_default_callbacks.cc index d8b01a3..ffc8cf1 100644 --- a/centipede/centipede_default_callbacks.cc +++ b/centipede/centipede_default_callbacks.cc
@@ -124,7 +124,8 @@ PrintExecutionLog(); FUZZTEST_LOG(ERROR) << "Test binary failed to mutate inputs at the final " "attempt - exiting."; - stop_condition_.RequestEarlyStop(EXIT_FAILURE); + stop_condition_.RequestEarlyStop(EXIT_FAILURE, + "Test binary failed to mutate inputs"); return {}; } }
diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc index c519be1..95f1b7b 100644 --- a/centipede/centipede_interface.cc +++ b/centipede/centipede_interface.cc
@@ -82,7 +82,7 @@ // Runs env.for_each_blob on every blob extracted from env.args. // Returns EXIT_SUCCESS on success, EXIT_FAILURE otherwise. -int ForEachBlob(const Environment& env, StopCondition& stop_condition) { +void ForEachBlob(const Environment& env, StopCondition& stop_condition) { auto tmpdir = TemporaryLocalDirPath(); CreateLocalDirRemovedAtExit(tmpdir); std::string tmpfile = std::filesystem::path(tmpdir).append("t"); @@ -92,8 +92,11 @@ auto blob_reader = DefaultBlobFileReaderFactory(); absl::Status open_status = blob_reader->Open(arg); if (!open_status.ok()) { - FUZZTEST_LOG(INFO) << "Failed to open " << arg << ": " << open_status; - return EXIT_FAILURE; + const std::string stop_reason = + absl::StrCat("Failed to open ", arg, ": ", open_status); + FUZZTEST_LOG(ERROR) << stop_reason; + stop_condition.RequestEarlyStop(EXIT_FAILURE, stop_reason); + return; } ByteSpan blob; while (blob_reader->Read(blob) == absl::OkStatus()) { @@ -105,10 +108,9 @@ // If this flag gets active use, we may want to define special cases, // e.g. if for_each_blob=="cp %P /some/where" we can do it in-process. cmd.Execute(); - if (stop_condition.ShouldStop()) return stop_condition.ExitCode(); + if (stop_condition.ShouldStop()) return; } } - return EXIT_SUCCESS; } // Loads corpora from work dirs provided in `env.args`, if there are two args @@ -179,10 +181,10 @@ return envs; } -int Fuzz(const Environment& env, const BinaryInfo& binary_info, - std::string_view pcs_file_path, - CentipedeCallbacksFactory& callbacks_factory, - StopCondition& stop_condition) { +void Fuzz(const Environment& env, const BinaryInfo& binary_info, + std::string_view pcs_file_path, + CentipedeCallbacksFactory& callbacks_factory, + StopCondition& stop_condition) { CoverageLogger coverage_logger(binary_info.pc_table, binary_info.symbols); std::vector<Environment> envs = @@ -258,8 +260,6 @@ } if (!env.knobs_file.empty()) PrintRewardValues(stats_vec, std::cerr); - - return stop_condition.ExitCode(); } TestShard SetUpTestSharding() { @@ -341,9 +341,9 @@ PeriodicAction::ZeroDelayConstInterval(absl::Seconds(15))}; } -int UpdateCorpusDatabase(Environment env, - CentipedeCallbacksFactory& callbacks_factory, - StopCondition& stop_condition) { +void UpdateCorpusDatabase(Environment env, + CentipedeCallbacksFactory& callbacks_factory, + StopCondition& stop_condition) { FUZZTEST_LOG(INFO) << "Starting the update of the corpus database for:" << "\nFuzz test: " << env.test_name << "\nBinary: " << env.binary @@ -391,13 +391,12 @@ FUZZTEST_LOG(INFO) << "Test shard index: " << test_shard_index << " Total test shards: " << total_test_shards; - int exit_code = EXIT_SUCCESS; + StopCondition::EarlyStopRequest stop_request; // Step 2: Run the fuzz test. - // Clean up previous stop requests. stop_time will be set later. - stop_condition.ClearEarlyStopRequestAndSetStopTime( - /*stop_time=*/absl::InfiniteFuture()); + // Unset stop time. stop_time will be set later. + stop_condition.SetStopTime(/*stop_time=*/absl::InfiniteFuture()); if (!is_workdir_specified) { env.workdir = base_workdir_path / env.test_name; @@ -423,7 +422,7 @@ if (!RemotePathExists(WorkDir{env}.CoverageDirPath())) { FUZZTEST_LOG(INFO) << "Skipping running the fuzz test " << env.test_name; - return exit_code; + return; } // If execution IDs match and the previous coverage exists, it means // the same workflow got interrupted when running the test. So we resume @@ -490,16 +489,16 @@ } is_resuming = false; - if (stop_condition.EarlyStopRequested()) { - if (stop_condition.ExitCode() != EXIT_SUCCESS) { - exit_code = stop_condition.ExitCode(); + if (stop_condition.EarlyStopRequested(&stop_request)) { + if (stop_request.exit_code != EXIT_SUCCESS) { FUZZTEST_LOG(ERROR) << "Early stop requested for test " << env.test_name - << " with failure exit code " << exit_code; + << " with failure exit code " + << stop_request.exit_code; } else { FUZZTEST_LOG(INFO) << "Skipping test " << env.test_name << " due to early stop requested without failure."; } - return exit_code; + return; } FUZZTEST_LOG(INFO) << (env.fuzztest_only_replay ? "Replaying " : "Fuzzing ") @@ -507,8 +506,7 @@ << "\n\tTest binary: " << env.binary; const absl::Time start_time = absl::Now(); - stop_condition.ClearEarlyStopRequestAndSetStopTime(/*stop_time=*/start_time + - time_limit); + stop_condition.SetStopTime(/*stop_time=*/start_time + time_limit); PeriodicAction record_fuzzing_time = RecordFuzzingTime(fuzzing_time_file, start_time - time_spent); Fuzz(env, binary_info, pcs_file_path, callbacks_factory, stop_condition); @@ -523,26 +521,25 @@ (stats_dir / absl::StrCat("fuzzing_stats_", execution_stamp)).c_str())); } - if (stop_condition.EarlyStopRequested()) { - if (stop_condition.ExitCode() != EXIT_SUCCESS) { - exit_code = stop_condition.ExitCode(); + if (stop_condition.EarlyStopRequested(&stop_request)) { + if (stop_request.exit_code != EXIT_SUCCESS) { FUZZTEST_LOG(ERROR) << "Early stop requested for test " << env.test_name - << " with failure exit code " << exit_code; + << " with failure exit code " + << stop_request.exit_code; } else { FUZZTEST_LOG(INFO) << "Skip updating corpus database due to early stop " "requested without failure."; } - return exit_code; + return; } // The test time limit does not apply for the rest of the steps. - stop_condition.ClearEarlyStopRequestAndSetStopTime( - /*stop_time=*/absl::InfiniteFuture()); + stop_condition.SetStopTime(/*stop_time=*/absl::InfiniteFuture()); // TODO(xinhaoyuan): Have a separate flag to skip corpus updating instead // of checking whether workdir is specified or not. const bool skip_corpus_db_update = env.fuzztest_only_replay || is_workdir_specified; - if (skip_corpus_db_update && !env.report_crash_summary) return exit_code; + if (skip_corpus_db_update && !env.report_crash_summary) return; // Deduplicate and optionally update the crashing inputs. CrashSummary crash_summary{env.fuzztest_binary_identifier, env.test_name}; @@ -557,7 +554,7 @@ crash_signature, crash_details.description}); } crash_summary.Report(&std::cerr); - return exit_code; + return; } OrganizeCrashingInputs(regression_dir, fuzztest_db_path / "crashing", env, callbacks_factory, crashes_by_signature, crash_summary, @@ -582,8 +579,6 @@ FUZZTEST_CHECK_OK( RemoteFileRename(corpus_file, (coverage_dir / file_name).c_str())); } - - return exit_code; } int ListCrashIds(const Environment& env) { @@ -613,9 +608,9 @@ return EXIT_SUCCESS; } -int ReplayCrash(const Environment& env, - CentipedeCallbacksFactory& callbacks_factory, - StopCondition& stop_condition) { +void ReplayCrash(const Environment& env, + CentipedeCallbacksFactory& callbacks_factory, + StopCondition& stop_condition) { FUZZTEST_CHECK(!env.crash_id.empty()) << "Need crash_id to be set for replay a crash"; FUZZTEST_CHECK(!env.test_name.empty()) @@ -643,8 +638,7 @@ crash_corpus_config, env.binary_name, env.binary_hash)); Environment run_crash_env = env; run_crash_env.load_shards_only = true; - int fuzz_result = - Fuzz(run_crash_env, {}, "", callbacks_factory, stop_condition); + Fuzz(run_crash_env, {}, "", callbacks_factory, stop_condition); if (env.report_crash_summary) { CrashSummary crash_summary{env.fuzztest_binary_identifier, env.test_name}; const absl::flat_hash_map<std::string, CrashDetails> crashes_by_signature = @@ -657,7 +651,6 @@ } crash_summary.Report(&std::cerr); } - return fuzz_result; } int ExportCrash(const Environment& env) { @@ -698,7 +691,16 @@ if (stop_condition == nullptr) { stop_condition = &default_stop_condition; } - stop_condition->ClearEarlyStopRequestAndSetStopTime(env.stop_at); + stop_condition->SetStopTime(env.stop_at); + + auto HandleEarlyStopAndGetExitCode = [&]() -> int { + StopCondition::EarlyStopRequest stop_request; + const bool stop_requested = + stop_condition->EarlyStopRequested(&stop_request); + if (!stop_requested) return EXIT_SUCCESS; + // Write reason to some diagnostic output. + return stop_request.exit_code; + }; if (!env.corpus_to_files.empty()) { Centipede::CorpusToFiles(env, env.corpus_to_files); @@ -713,12 +715,16 @@ return EXIT_FAILURE; } - if (!env.for_each_blob.empty()) return ForEachBlob(env, *stop_condition); + if (!env.for_each_blob.empty()) { + ForEachBlob(env, *stop_condition); + return HandleEarlyStopAndGetExitCode(); + } if (!env.minimize_crash_file_path.empty()) { ByteArray crashy_input; ReadFromLocalFile(env.minimize_crash_file_path, crashy_input); - return MinimizeCrash(crashy_input, env, callbacks_factory, *stop_condition); + MinimizeCrash(crashy_input, env, callbacks_factory, *stop_condition); + return HandleEarlyStopAndGetExitCode(); } // Just export the corpus from a local dir and exit. @@ -791,7 +797,8 @@ return ListCrashIds(updated_env); } if (env.replay_crash) { - return ReplayCrash(updated_env, callbacks_factory, *stop_condition); + ReplayCrash(updated_env, callbacks_factory, *stop_condition); + return HandleEarlyStopAndGetExitCode(); } if (env.export_crash) { return ExportCrash(updated_env); @@ -804,8 +811,8 @@ FUZZTEST_CHECK(updated_env.fuzztest_time_limit_per_test >= absl::Seconds(1)) << "Time limit per fuzz test must be at least 1 second."; - return UpdateCorpusDatabase(updated_env, callbacks_factory, - *stop_condition); + UpdateCorpusDatabase(updated_env, callbacks_factory, *stop_condition); + return HandleEarlyStopAndGetExitCode(); } } @@ -821,8 +828,9 @@ if (env.analyze) return Analyze(env); - return Fuzz(env, binary_info, pcs_file_path, callbacks_factory, - *stop_condition); + Fuzz(env, binary_info, pcs_file_path, callbacks_factory, *stop_condition); + return HandleEarlyStopAndGetExitCode(); + // TODO: fniksic - Report the crash summary here if requested. What are the // binary identifier and the fuzz test name here? }
diff --git a/centipede/centipede_main.cc b/centipede/centipede_main.cc index e38dcaf..36a369f 100644 --- a/centipede/centipede_main.cc +++ b/centipede/centipede_main.cc
@@ -36,7 +36,8 @@ const char msg[] = "\n[!] Ctrl-C pressed: winding down\n"; [[maybe_unused]] auto write_res = write(STDERR_FILENO, msg, sizeof(msg) - 1); - global_stop_condition.RequestEarlyStop(EXIT_FAILURE); + global_stop_condition.RequestEarlyStopInSignal(EXIT_FAILURE, + "Ctrl-C pressed"); }; sigaction(SIGINT, &sigact, nullptr); }
diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc index 7a7f1d7..67a9b0e 100644 --- a/centipede/centipede_test.cc +++ b/centipede/centipede_test.cc
@@ -1299,8 +1299,9 @@ EXPECT_THAT(callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs), inputs.size()), IsEmpty()); - EXPECT_TRUE(stop_condition.EarlyStopRequested()); - EXPECT_EQ(stop_condition.ExitCode(), EXIT_FAILURE); + StopCondition::EarlyStopRequest stop_request; + EXPECT_TRUE(stop_condition.EarlyStopRequested(&stop_request)); + EXPECT_EQ(stop_request.exit_code, EXIT_FAILURE); } TEST_F(CentipedeWithTemporaryLocalDir, @@ -1325,7 +1326,7 @@ StopCondition stop_condition; CentipedeDefaultCallbacks callbacks(env, stop_condition); const auto start = absl::Now(); - stop_condition.ClearEarlyStopRequestAndSetStopTime(start + absl::Seconds(3)); + stop_condition.SetStopTime(start + absl::Seconds(3)); std::vector<ByteArray> seeds; callbacks.GetSeeds(/*num_seeds=*/1, seeds); // Give it some slack to stop in 5s.
diff --git a/centipede/command.cc b/centipede/command.cc index 4afab0a..759a79c 100644 --- a/centipede/command.cc +++ b/centipede/command.cc
@@ -508,7 +508,8 @@ const auto signal = WTERMSIG(exit_code); if (signal == SIGINT) { if (stop_condition != nullptr) { - stop_condition->RequestEarlyStop(EXIT_FAILURE); + stop_condition->RequestEarlyStop( + EXIT_FAILURE, "Command killed: signal=SIGINT (likely Ctrl-C)"); } // When the user kills Centipede via ^C, they are unlikely to be // interested in any of the subprocesses' outputs. Also, ^C terminates all
diff --git a/centipede/stop.cc b/centipede/stop.cc index b7761a4..2ba2ed5 100644 --- a/centipede/stop.cc +++ b/centipede/stop.cc
@@ -14,25 +14,75 @@ #include "./centipede/stop.h" +#include <algorithm> #include <atomic> #include <cstdlib> +#include <string_view> +#include <thread> // NOLINT: for std::this_thread #include "absl/time/clock.h" #include "absl/time/time.h" namespace fuzztest::internal { -bool StopCondition::EarlyStopRequested() const { - return early_stop_.load(std::memory_order_acquire).is_requested; -} +StopCondition::StopCondition() { reason_.reserve(64); } -void StopCondition::ClearEarlyStopRequestAndSetStopTime(absl::Time stop_time) { - early_stop_.store({}, std::memory_order_release); +void StopCondition::SetStopTime(absl::Time stop_time) { stop_time_ = stop_time; } -void StopCondition::RequestEarlyStop(int exit_code) { - early_stop_.store({exit_code, true}, std::memory_order_release); +void StopCondition::ClearEarlyStopRequest() { + if (!stop_requested_.load(std::memory_order_acquire)) { + return; + } + // Wait until the request is fully written. + while (!stop_request_ready_.load(std::memory_order_acquire)) { + std::this_thread::yield(); + } + + exit_code_ = EXIT_SUCCESS; + reason_.clear(); + + stop_request_ready_.store(false, std::memory_order_release); + stop_requested_.store(false, std::memory_order_release); +} + +bool StopCondition::EarlyStopRequested(EarlyStopRequest* request) const { + if (!stop_requested_.load(std::memory_order_acquire)) { + return false; + } + if (request == nullptr) { + return true; + } + // Wait until the request is fully written. + while (!stop_request_ready_.load(std::memory_order_acquire)) { + std::this_thread::yield(); + } + request->exit_code = exit_code_; + request->reason = reason_; + return true; +} + +void StopCondition::RequestEarlyStop(int exit_code, std::string_view reason) { + // Only write the reason if it hasn't been requested yet, to avoid races + // overwriting it, although races are rare. + if (stop_requested_.exchange(true)) return; + exit_code_ = exit_code; + // Full-copy - may allocate memory. + reason_ = reason; + stop_request_ready_.store(true, std::memory_order_release); +} + +void StopCondition::RequestEarlyStopInSignal(int exit_code, + std::string_view reason) { + // Only write the reason if it hasn't been requested yet, to avoid races + // overwriting it, although races are rare. + if (stop_requested_.exchange(true)) return; + exit_code_ = exit_code; + // Copy up to the capacity - should not allocate memory. + const auto copy_len = std::min(reason_.capacity(), reason.size()); + reason_ = reason.substr(0, copy_len); + stop_request_ready_.store(true, std::memory_order_release); } absl::Time StopCondition::GetStopTime() const { return stop_time_; } @@ -41,8 +91,4 @@ return EarlyStopRequested() || stop_time_ < absl::Now(); } -int StopCondition::ExitCode() const { - return early_stop_.load(std::memory_order_acquire).exit_code; -} - } // namespace fuzztest::internal
diff --git a/centipede/stop.h b/centipede/stop.h index f000fd5..4e1d92e 100644 --- a/centipede/stop.h +++ b/centipede/stop.h
@@ -17,6 +17,8 @@ #include <atomic> #include <cstdlib> +#include <string> +#include <string_view> #include "absl/time/time.h" @@ -25,32 +27,50 @@ // Encapsulates the stop condition state for Centipede. class StopCondition { public: - StopCondition() = default; + StopCondition(); StopCondition(const StopCondition&) = delete; StopCondition& operator=(const StopCondition&) = delete; StopCondition(StopCondition&&) = delete; StopCondition& operator=(StopCondition&&) = delete; - // Clears the request to stop early and sets the stop time. + // Clears the request to stop early and sets the new `stop_time`. // // REQUIRES: Must be called before starting concurrent threads that may invoke - // the other methods on this object instance. In particular, calling this - // function concurrently with `ShouldStop()` is not thread-safe. - void ClearEarlyStopRequestAndSetStopTime(absl::Time stop_time); + // the other methods on this object instance. Specifically, calling this + // function concurrently with `EarlyStopRequested()` is not thread-safe. + void ClearEarlyStopRequest(); + + struct EarlyStopRequest { + int exit_code = EXIT_SUCCESS; + std::string reason; + }; + + // Returns whether `RequestEarlyStop()` was called or not since the most + // recent call to `ClearEarlyStopRequest()` (if any). If `request` is not + // null, copy the stop request to the referred instance when early stop is + // requested. + // + // ENSURES: Thread-safe unless with `ClearEarlyStopRequest()`. + bool EarlyStopRequested(EarlyStopRequest* request = nullptr) const; // Requests that Centipede soon stops whatever it is doing (fuzzing, // minimizing reproducer, etc.), with `exit_code` indicating success (zero) or // failure (non-zero). // - // ENSURES: Thread-safe and safe to call from signal handlers. - void RequestEarlyStop(int exit_code); - - // Returns whether `RequestEarlyStop()` was called or not since the most - // recent call to `ClearEarlyStopRequestAndSetStopTime()` (if any). - // // ENSURES: Thread-safe. - bool EarlyStopRequested() const; + void RequestEarlyStop(int exit_code, std::string_view reason); + + // Similar to `RequestEarlyStop`, but safe to call in signal handlers, while + // `reason` maybe truncated due to no memory allocation. + void RequestEarlyStopInSignal(int exit_code, std::string_view reason); + + // Sets the stop time. + // + // REQUIRES: Must be called before starting concurrent threads that may invoke + // the functions defined in this class. Specifically, calling this function + // concurrently with `ShouldStop()` and `GetStopTime()` is not thread-safe. + void SetStopTime(absl::Time stop_time); // Returns true iff it is time to stop, either because the stopping time has // been reached or `RequestEarlyStop()` was called since the most recent call @@ -66,13 +86,6 @@ // ENSURES: Thread-safe. absl::Time GetStopTime() const; - // Returns the value most recently passed to `RequestEarlyStop()` or 0 if - // `RequestEarlyStop()` was not called since the most recent call to - // `ClearEarlyStopRequestAndSetStopTime()` (if any). - // - // ENSURES: Thread-safe. - int ExitCode() const; - private: struct EarlyStop { int exit_code = EXIT_SUCCESS; @@ -81,6 +94,12 @@ static_assert(std::atomic<EarlyStop>::is_always_lock_free); std::atomic<EarlyStop> early_stop_{EarlyStop{}}; absl::Time stop_time_ = absl::InfiniteFuture(); + // Set to true when RequestEarlyStop* is requested. + std::atomic<bool> stop_requested_ = false; + // Set to true when the below field is fully set. + std::atomic<bool> stop_request_ready_ = false; + int exit_code_ = EXIT_SUCCESS; + std::string reason_; }; } // namespace fuzztest::internal
diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc index 7c23407..c6eecee 100644 --- a/fuzztest/internal/centipede_adaptor.cc +++ b/fuzztest/internal/centipede_adaptor.cc
@@ -360,19 +360,24 @@ sigemptyset(&new_sigact.sa_mask); new_sigact.sa_handler = [](int signum) { Runtime::instance().SetTerminationRequested(); - global_stop_condition.RequestEarlyStop(EXIT_SUCCESS); const int fd = GetStderrFdDup() != -1 ? GetStderrFdDup() : STDERR_FILENO; if (signum == SIGTERM) { + global_stop_condition.RequestEarlyStopInSignal(EXIT_SUCCESS, + "SIGTERM received"); constexpr char kMsg[] = "\n[!] SIGTERM received - stopping fuzzing.\n"; write(fd, kMsg, sizeof(kMsg) - 1); return; } else if (signum == SIGHUP) { + global_stop_condition.RequestEarlyStopInSignal(EXIT_SUCCESS, + "SIGHUP received"); constexpr char kMsg[] = "\n[!] SIGHUP received - stopping fuzzing.\n"; write(fd, kMsg, sizeof(kMsg) - 1); return; } else if (signum == SIGINT) { + global_stop_condition.RequestEarlyStopInSignal(EXIT_SUCCESS, + "SIGINT received"); constexpr char kMsg[] = "\n[!] SIGINT received - stopping fuzzing.\n"; write(fd, kMsg, sizeof(kMsg) - 1); return; @@ -436,11 +441,12 @@ << "Termination status must be Exited if not Signaled"; return static_cast<int>(std::get<ExitCodeT>(status.Status())); } - global_stop_condition.ClearEarlyStopRequestAndSetStopTime( - absl::InfiniteFuture()); + global_stop_condition.SetStopTime(absl::InfiniteFuture()); static absl::NoDestructor<DefaultCallbacksFactory<CentipedeDefaultCallbacks>> factory; - return CentipedeMain(env, *factory, &global_stop_condition); + const int ret = CentipedeMain(env, *factory, &global_stop_condition); + global_stop_condition.ClearEarlyStopRequest(); + return ret; } } // namespace