Added callbacks for node errors and fifo overflow
Add new doc system which should/might deploy to pages.
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include <kpn/interrupt_node.hpp>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
using namespace kpn;
|
||||
@@ -239,3 +240,225 @@ TEST_CASE("interrupt node: trigger after stop is ignored", "[interrupt_node]") {
|
||||
REQUIRE(out_ch.approx_size() == 0);
|
||||
pool->stop();
|
||||
}
|
||||
|
||||
// ── Overflow callback ─────────────────────────────────────────────────────────
|
||||
|
||||
TEST_CASE("pool node overflow callback fires on full output channel", "[pool_node][overflow]") {
|
||||
auto pool = std::make_shared<ThreadPool>(2);
|
||||
pool->start();
|
||||
|
||||
auto node = make_pool_node<double_it>(pool);
|
||||
// Pre-fill a tiny channel so every node push overflows.
|
||||
Channel<int> full_ch(1);
|
||||
full_ch.push(99);
|
||||
node.set_output_channel<0>(&full_ch);
|
||||
|
||||
std::atomic<int> overflow_count{0};
|
||||
node.set_overflow_callback([&](auto) { overflow_count.fetch_add(1); });
|
||||
|
||||
node.start();
|
||||
node.input_channel<0>().push(1);
|
||||
node.input_channel<0>().push(2);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
node.stop();
|
||||
pool->stop();
|
||||
|
||||
REQUIRE(overflow_count.load() > 0);
|
||||
}
|
||||
|
||||
TEST_CASE("pool node overflow callback is independent per instance", "[pool_node][overflow]") {
|
||||
auto pool = std::make_shared<ThreadPool>(2);
|
||||
pool->start();
|
||||
|
||||
auto nodeA = make_pool_node<double_it>(pool);
|
||||
auto nodeB = make_pool_node<double_it>(pool);
|
||||
|
||||
std::atomic<int> a_overflows{0}, b_overflows{0};
|
||||
nodeA.set_overflow_callback([&](auto) { a_overflows.fetch_add(1); });
|
||||
|
||||
Channel<int> full_ch(1);
|
||||
full_ch.push(0);
|
||||
nodeA.set_output_channel<0>(&full_ch);
|
||||
|
||||
Channel<int> ok_ch(20);
|
||||
nodeB.set_output_channel<0>(&ok_ch);
|
||||
|
||||
nodeA.start();
|
||||
nodeB.start();
|
||||
|
||||
nodeA.input_channel<0>().push(1);
|
||||
nodeA.input_channel<0>().push(2);
|
||||
nodeB.input_channel<0>().push(10);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
nodeA.stop();
|
||||
nodeB.stop();
|
||||
pool->stop();
|
||||
|
||||
REQUIRE(a_overflows.load() > 0);
|
||||
REQUIRE(b_overflows.load() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("interrupt node overflow callback fires on full output", "[interrupt_node][overflow]") {
|
||||
auto pool = std::make_shared<ThreadPool>(2);
|
||||
pool->start();
|
||||
|
||||
g_interrupt_counter.store(0);
|
||||
auto node = make_interrupt_node<interrupt_produce>(pool, out<>{});
|
||||
|
||||
Channel<int> full_ch(1);
|
||||
full_ch.push(99);
|
||||
node.set_output_channel<0>(&full_ch);
|
||||
|
||||
std::atomic<int> overflow_count{0};
|
||||
node.set_overflow_callback([&](auto) { overflow_count.fetch_add(1); });
|
||||
|
||||
node.start();
|
||||
auto trigger = node.get_trigger();
|
||||
trigger(); trigger(); trigger();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
node.stop();
|
||||
pool->stop();
|
||||
|
||||
REQUIRE(overflow_count.load() > 0);
|
||||
}
|
||||
|
||||
// ── self_stop: disable inputs + outputs on crash ──────────────────────────────
|
||||
|
||||
static int always_throw(int) { throw std::runtime_error("node crashed"); return 0; }
|
||||
|
||||
TEST_CASE("pool node self_stop disables output on crash so downstream sees closed", "[pool_node][self_stop]") {
|
||||
auto pool = std::make_shared<ThreadPool>(2);
|
||||
pool->start();
|
||||
|
||||
auto node = make_pool_node<always_throw>(pool, 5);
|
||||
Channel<int> out_ch(10);
|
||||
node.set_output_channel<0>(&out_ch);
|
||||
|
||||
node.start();
|
||||
node.input_channel<0>().push(1);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
REQUIRE_FALSE(out_ch.is_accepting());
|
||||
|
||||
node.stop();
|
||||
pool->stop();
|
||||
}
|
||||
|
||||
TEST_CASE("pool node self_stop disables input on crash", "[pool_node][self_stop]") {
|
||||
auto pool = std::make_shared<ThreadPool>(2);
|
||||
pool->start();
|
||||
|
||||
auto node = make_pool_node<always_throw>(pool, 5);
|
||||
Channel<int> out_ch(5);
|
||||
node.set_output_channel<0>(&out_ch);
|
||||
|
||||
node.start();
|
||||
node.input_channel<0>().push(1);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
REQUIRE_FALSE(node.input_channel<0>().is_accepting());
|
||||
|
||||
node.stop();
|
||||
pool->stop();
|
||||
}
|
||||
|
||||
TEST_CASE("pool node closed callback fires on self_stop from crash", "[pool_node][self_stop]") {
|
||||
auto pool = std::make_shared<ThreadPool>(2);
|
||||
pool->start();
|
||||
|
||||
auto node = make_pool_node<always_throw>(pool, 5);
|
||||
Channel<int> out_ch(5);
|
||||
node.set_output_channel<0>(&out_ch);
|
||||
|
||||
std::atomic<bool> closed_fired{false};
|
||||
node.set_closed_callback([&](auto) { closed_fired.store(true); });
|
||||
|
||||
node.start();
|
||||
node.input_channel<0>().push(1);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
REQUIRE(closed_fired.load());
|
||||
|
||||
node.stop();
|
||||
pool->stop();
|
||||
}
|
||||
|
||||
// ── Network-level event callbacks ─────────────────────────────────────────────
|
||||
|
||||
TEST_CASE("network_overflow_callback fires on overflow", "[pool_node][network]") {
|
||||
auto pool = std::make_shared<ThreadPool>(2);
|
||||
pool->start();
|
||||
|
||||
auto node = make_pool_node<double_it>(pool);
|
||||
|
||||
Channel<int> full_ch(1);
|
||||
full_ch.push(0);
|
||||
node.set_output_channel<0>(&full_ch);
|
||||
|
||||
std::atomic<int> net_overflows{0};
|
||||
node.set_network_overflow_callback([&](auto) { net_overflows.fetch_add(1); });
|
||||
|
||||
node.start();
|
||||
node.input_channel<0>().push(1);
|
||||
node.input_channel<0>().push(2);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
node.stop();
|
||||
pool->stop();
|
||||
|
||||
REQUIRE(net_overflows.load() > 0);
|
||||
}
|
||||
|
||||
TEST_CASE("network_closed_callback fires on crash", "[pool_node][network]") {
|
||||
auto pool = std::make_shared<ThreadPool>(2);
|
||||
pool->start();
|
||||
|
||||
auto node = make_pool_node<always_throw>(pool);
|
||||
Channel<int> out_ch(5);
|
||||
node.set_output_channel<0>(&out_ch);
|
||||
|
||||
std::atomic<bool> net_closed{false};
|
||||
node.set_network_closed_callback([&](auto) { net_closed.store(true); });
|
||||
|
||||
node.start();
|
||||
node.input_channel<0>().push(1);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
REQUIRE(net_closed.load());
|
||||
|
||||
node.stop();
|
||||
pool->stop();
|
||||
}
|
||||
|
||||
TEST_CASE("per-node and network overflow callbacks both fire independently", "[pool_node][network]") {
|
||||
auto pool = std::make_shared<ThreadPool>(2);
|
||||
pool->start();
|
||||
|
||||
auto node = make_pool_node<double_it>(pool);
|
||||
|
||||
Channel<int> full_ch(1);
|
||||
full_ch.push(0);
|
||||
node.set_output_channel<0>(&full_ch);
|
||||
|
||||
std::atomic<int> per_node{0}, network{0};
|
||||
node.set_overflow_callback([&](auto) { per_node.fetch_add(1); });
|
||||
node.set_network_overflow_callback([&](auto) { network.fetch_add(1); });
|
||||
|
||||
node.start();
|
||||
node.input_channel<0>().push(1);
|
||||
node.input_channel<0>().push(2);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
node.stop();
|
||||
pool->stop();
|
||||
|
||||
REQUIRE(per_node.load() > 0);
|
||||
REQUIRE(network.load() > 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user