fix/kpn-wedging-audit #3
+15
-2
@@ -8,8 +8,10 @@
|
||||
#include <memory>
|
||||
#endif
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <mutex>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@@ -433,9 +435,20 @@ private:
|
||||
|
||||
void start_watchdog() {
|
||||
watchdog_ = std::jthread([this](std::stop_token tok) {
|
||||
// Interruptible wait, not sleep_for. request_stop() cannot wake a
|
||||
// sleeping thread, so stop_watchdog()'s join blocked for up to a
|
||||
// full interval — three seconds by default, and unbounded for
|
||||
// anyone who set a long one to keep the periodic report quiet.
|
||||
// Every teardown paid it.
|
||||
std::mutex m;
|
||||
std::condition_variable_any cv;
|
||||
while (!tok.stop_requested()) {
|
||||
std::this_thread::sleep_for(watchdog_interval_);
|
||||
if (tok.stop_requested()) break;
|
||||
{
|
||||
std::unique_lock lk(m);
|
||||
if (cv.wait_for(lk, tok, watchdog_interval_,
|
||||
[&tok] { return tok.stop_requested(); }))
|
||||
break;
|
||||
}
|
||||
|
||||
auto s = collect_snapshots();
|
||||
check_hung_nodes();
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <kpn/kpn.hpp>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
using namespace kpn;
|
||||
@@ -54,3 +58,36 @@ TEST_CASE("stop disables input channels — producer push is silently dropped",
|
||||
in_ch.push(99);
|
||||
REQUIRE(in_ch.size() == 0);
|
||||
}
|
||||
|
||||
// Regression: stopping a network must not wait for the watchdog's next tick.
|
||||
//
|
||||
// The watchdog looped on std::this_thread::sleep_for(watchdog_interval_), and
|
||||
// request_stop() cannot wake a sleeping thread — so stop_watchdog()'s join
|
||||
// blocked until the current sleep expired. Every teardown paid up to a full
|
||||
// interval, three seconds by default, and a caller who set a long one to keep
|
||||
// the periodic report quiet got a stop() that looked like a hang. That is how
|
||||
// this was found: the error-handler case above set an hour.
|
||||
TEST_CASE("stopping a network does not wait for the watchdog interval", "[network]") {
|
||||
auto node = kpn::make_node<increment>(kpn::in<"v">{}, kpn::out<"w">{}, 4);
|
||||
kpn::Channel<int> out(4);
|
||||
node.set_output_channel<0>(&out);
|
||||
|
||||
kpn::Network net;
|
||||
net.add("inc", node).build();
|
||||
net.set_watchdog_interval(std::chrono::hours(1));
|
||||
net.start();
|
||||
|
||||
// Let the watchdog actually reach its wait. Without this the test races it:
|
||||
// stop_watchdog() runs before the thread has entered the loop, the token is
|
||||
// already set when it does, and it exits without ever waiting — which passes
|
||||
// against the bug as well as the fix.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
net.stop();
|
||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - t0).count();
|
||||
|
||||
INFO("stop took " << ms << " ms");
|
||||
CHECK(ms < 2000);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user