|
1 | 1 | #include "phlex/core/framework_graph.hpp" |
| 2 | +#include "phlex/utilities/max_allowed_parallelism.hpp" |
2 | 3 | #include "plugins/layer_generator.hpp" |
3 | 4 |
|
4 | 5 | #include "catch2/catch_test_macros.hpp" |
@@ -39,3 +40,47 @@ TEST_CASE("Make progress with one thread", "[graph]") |
39 | 40 | CHECK(g.execution_counts("provide_number") == 1000); |
40 | 41 | CHECK(g.execution_counts("observe_number") == 1000); |
41 | 42 | } |
| 43 | + |
| 44 | +TEST_CASE("Stop driver when workflow throws exception", "[graph]") |
| 45 | +{ |
| 46 | + experimental::layer_generator gen; |
| 47 | + gen.add_layer("spill", {"job", 1000}); |
| 48 | + |
| 49 | + experimental::framework_graph g{driver_for_test(gen)}; |
| 50 | + g.provide( |
| 51 | + "throw_exception", |
| 52 | + [](data_cell_index const&) -> unsigned int { |
| 53 | + throw std::runtime_error("Error to stop driver"); |
| 54 | + }, |
| 55 | + concurrency::unlimited) |
| 56 | + .output_product("number"_in("spill")); |
| 57 | + |
| 58 | + // Must have at least one downstream node that requires something of the |
| 59 | + // provider...otherwise provider will not be executed. |
| 60 | + g.observe( |
| 61 | + "downstream_of_exception", [](unsigned int) {}, concurrency::unlimited) |
| 62 | + .input_family("number"_in("spill")); |
| 63 | + |
| 64 | + CHECK_THROWS(g.execute()); |
| 65 | + |
| 66 | + // There are N + 1 potential existing threads for a framework job, where N corresponds |
| 67 | + // to the number configured by the user, and 1 corresponds to the separate std::jthread |
| 68 | + // created by the async_driver. Each "pull" from the async_driver happens in a |
| 69 | + // serialized way. However, once an index has been pulled from the async_driver by the |
| 70 | + // flow graph, that index is sent to downstream nodes for further processing. |
| 71 | + // |
| 72 | + // The first node that processes that index is a provider that immediately throws an |
| 73 | + // exception. This places the framework graph in an error state, where the async_driver |
| 74 | + // is short-circuited from doing further processing. |
| 75 | + // |
| 76 | + // We make the assumption that one of those threads will trigger the exception and the |
| 77 | + // remaining threads must be permitted to complete. |
| 78 | + CHECK(gen.emitted_cells("/job/spill") <= |
| 79 | + static_cast<std::size_t>(experimental::max_allowed_parallelism::active_value() + 1)); |
| 80 | + |
| 81 | + // A node has not "executed" until it has returned successfully. For that reason, |
| 82 | + // neither the "throw_exception" provider nor the "downstream_of_exception" observer |
| 83 | + // will have executed. |
| 84 | + CHECK(g.execution_counts("throw_exception") == 0ull); |
| 85 | + CHECK(g.execution_counts("downstream_of_exception") == 0ull); |
| 86 | +} |
0 commit comments