14#ifndef NODEGRAPH2_NODEGRAPH_HPP
15#define NODEGRAPH2_NODEGRAPH_HPP
27#include "rpnx/result.hpp"
28#include "rpnx/typelist.hpp"
36#include <condition_variable>
54#include <shared_mutex>
55#include <source_location>
67#include "rpnx/serialization4.hpp"
69#ifdef RPNX_QUERYGRAPH_USE_CONC_UNORDERED_MAP
70#include "rpnx/sharded_unordered_map.hpp"
74#define RPNX_DEBUGONLY(x)
75#define RPNX_RELEASEONLY(x) x
77#define RPNX_DEBUGONLY(x) x
78#define RPNX_RELEASEONLY(x)
83RPNX_ENUM(rpnx::querygraph,
memory_class, std::uint8_t, unknown, tiny, small, medium, large, huge);
102namespace rpnx::querygraph
115 using fast_cv = std::condition_variable_any;
132 template <
typename T >
134 typename T::input_type;
135 typename T::output_type;
136 { T::query_id } -> std::convertible_to< std::string >;
148 template <
typename T >
150 typename T::parent_query;
151 typename T::input_type;
152 typename T::output_type;
154 { T::subquery_id } -> std::convertible_to< std::string >;
159 template < query_spec_c QuerySpec, rpnx::typelist_c Deps, rpnx::typelist_c ProducedSubqueries = rpnx::typelist<> >
160 struct query_handler_spec
162 using query = QuerySpec;
163 using dependencies = Deps;
164 using produced_subqueries = ProducedSubqueries;
174 template <
typename T >
177 typename T::dependencies;
181 template <
typename T >
183 typename T::produced_subqueries;
184 } && rpnx::typelist_c< typename T::produced_subqueries >;
190 template <
typename T >
194 template <
typename T >
201 template <
typename T >
209 template < query_handler_produced_subqueries_struct_c T >
217 template < query_handler_spec_struct_c T >
229 template < query_handler_spec_c HandlerSpec >
233 template < query_handler_spec_c HandlerSpec >
237 template < query_handler_spec_c HandlerSpec >
241 template < query_handler_spec_c HandlerSpec >
245 template < query_handler_spec_c HandlerSpec >
249 template <
typename T >
264 template <
typename T >
274 std::vector< std::byte > buffer;
275 rpnx::serial4::serialize_iter(value, std::back_inserter(buffer));
295 template <
typename T >
297 { t.to_debug_string() } -> std::convertible_to< std::string >;
309 template <
typename T >
317 return value.to_debug_string();
319 else if constexpr (std::formattable< T, char >)
321 return std::format(
"{}", value);
325 return "<no debug string available>";
337 return std::format(
"{:?}", value);
348 return "std::monostate{}";
372 template <
typename... Ts >
373 debug_message(std::format_string< Ts... > format_string, Ts&&... args) :
message(std::format(format_string, std::forward< Ts >(args)...))
405 std::function< std::vector< std::byte >(std::any
const&) >
m_serialize;
407 std::function< std::string(std::any
const&) >
m_message;
428 template <
typename Error >
431 static_assert(std::derived_from< Error, std::exception >,
"Canonical errors must derive from std::exception");
432 static_assert(std::copy_constructible< Error >,
"Canonical errors must be copy constructible");
435 storage.
m_error_typesig = rpnx::serial4::get_type_typesig_hash16< Error >();
439 throw std::any_cast< Error const& >(value);
445 storage.
m_message = [](std::any
const& value)
447 return std::string(std::any_cast< Error const& >(value).what());
461 std::optional< canonical_error_storage > m_canonical_error;
462 std::exception_ptr m_unexpected_exception;
511 template <
typename T >
514 std::optional< T > m_value;
534 if (m_error.has_error())
536 m_error.throw_error();
544 assert(m_value.has_value());
545 return m_value.value();
552 assert(m_value.has_value());
553 return m_value.value();
560 assert(m_value.has_value());
561 return std::move(m_value.value());
568 assert(m_value.has_value());
569 return std::move(m_value.value());
587 return std::move(*this).get();
593 return std::move(*this).get();
600 m_value = std::move(
value);
607 m_error = std::move(error);
625 return m_value.has_value() || m_error.has_error();
631 return m_value.has_value();
637 return m_error.has_error();
649 return m_error.has_canonical_error();
655 return m_error.has_unexpected_exception();
667 return m_error.make_exception_ptr();
673 if (m_value.has_value())
675 return status::completed;
677 return m_error.result_status();
681 explicit operator bool()
const
691 bool m_has_value =
false;
706 if (m_error.has_error())
708 m_error.throw_error();
718 throw std::logic_error(
"No value");
739 m_error = std::move(error);
757 return m_has_value || m_error.has_error();
769 return m_error.has_error();
781 return m_error.has_canonical_error();
787 return m_error.has_unexpected_exception();
799 return m_error.make_exception_ptr();
807 return status::completed;
809 return m_error.result_status();
813 explicit operator bool()
const
819 inline namespace RPNX_DEBUGONLY(debug_abi) RPNX_RELEASEONLY(release_abi)
833 template < query_spec_c QuerySpec >
854 template < query_spec_c QuerySpec >
876 template < subquery_spec_c SubquerySpec >
904 template < subquery_spec_c SubquerySpec >
927 std::string m_subquery_id;
928 std::string m_message;
948 return m_subquery_id;
954 m_message = std::format(
"Subquery does not exist: {}", m_subquery_id);
958 char const*
what() const noexcept
override
960 return m_message.c_str();
972 std::string m_subquery_id;
985 return m_subquery_id;
995 class recursive_dependency_error :
public std::logic_error
998 recursive_dependency_error() : std::logic_error(
"recursive dependency")
1017 template <
typename Result >
1018 struct query_result;
1020 template < subquery_spec_c SubquerySpec >
1021 struct subquery_query_result;
1023 template < query_handler_spec_c HandlerSpec >
1041 bool m_enable_node_debug_io_capture =
false;
1042 bool m_enable_node_serialization =
true;
1043 std::chrono::nanoseconds m_long_running_node_threshold;
1044 std::mutex m_diagnostic_mutex;
1046 static constexpr std::size_t long_running_input_max_length = 200;
1047 static constexpr std::chrono::seconds long_running_progress_minimum = std::chrono::seconds(1);
1057 explicit graph(std::chrono::nanoseconds long_running_node_threshold = std::chrono::milliseconds(100));
1060 struct promise_base;
1065 promise_base* m_promise =
nullptr;
1066 std::coroutine_handle< void > handle;
1071 struct alignas(std::hardware_destructive_interference_size) executor
1073 runner* m_runner =
nullptr;
1074 RPNX_DEBUGONLY(std::thread::id m_id);
1076 std::deque< resumption > m_runnables;
1077 RPNX_DEBUGONLY(
bool m_idle =
false;)
1079 static constexpr std::size_t workgroup_threshold = 128;
1081 executor(runner* runner);
1083 executor(executor
const& other) =
delete;
1085 void check_legal_access();
1089 void declare_runnable(std::coroutine_handle< void > h, promise_base* ptr,
bool immediate);
1091 void declare_runnable(resumption r,
bool immediate);
1093 bool try_exec_one();
1095 std::vector< resumption > try_steal(std::size_t max_items);
1100 static std::size_t default_executor_count();
1105 std::size_t global_workitems_to_publish(std::size_t available_items)
const;
1110 std::size_t global_workitems_to_take(std::size_t queued_work)
const;
1112 graph* m_graph =
nullptr;
1113 std::size_t m_total_executors = 0;
1114 std::atomic< std::size_t > m_idle_executors = 0;
1118 std::atomic< std::size_t > m_global_workitems = 0;
1122 std::size_t m_global_workitem_threshold = 0;
1124 std::deque< executor > m_executors;
1125 std::vector< std::thread > m_executor_threads;
1126 std::deque< resumption > m_runnables;
1129 static constexpr std::size_t taskgroup_max_size = 1024;
1131 runner(
graph* g, std::size_t total_executors = default_executor_count(), std::size_t global_workitem_threshold = 0);
1133 executor* initial_executor();
1137 void push_group(std::vector< resumption > vals);
1139 void try_steal_work();
1141 std::vector< resumption > get_work(executor* ex);
1163 template < query_spec_c QuerySpec >
1164 auto make_request(
typename QuerySpec::input_type input) ->
typename QuerySpec::output_type
1166 runner runner(
this);
1167 query_descriptor< QuerySpec >& desc = get_query_descriptor< QuerySpec >();
1168 query_result< typename QuerySpec::output_type > query_result = desc.m_get(runner.initial_executor(), input,
false);
1169 if (query_result.started_by_current_request())
1172 if (!query_result.available())
1174 abort_query_node_with_unexpected_exception< typename QuerySpec::output_type >(query_result.m_node, runner.initial_executor(), std::make_exception_ptr(
recursive_dependency_error()));
1177 query_result.wait();
1178 return query_result.await_resume();
1195 template < subquery_spec_c SubquerySpec >
1196 auto make_subquery_request(
typename SubquerySpec::parent_query::input_type parent_input,
typename SubquerySpec::input_type input) ->
typename SubquerySpec::output_type
1198 register_subquery_descriptors< SubquerySpec >();
1199 runner runner(
this);
1200 query_descriptor< typename SubquerySpec::parent_query >& desc = get_query_descriptor< typename SubquerySpec::parent_query >();
1201 query_result< typename SubquerySpec::parent_query::output_type > parent_result = desc.m_get(runner.initial_executor(), parent_input,
false);
1202 subquery_query_result< SubquerySpec > query_result = get_subquery_result< SubquerySpec >(runner.initial_executor(), parent_result.m_node, input);
1203 if (parent_result.started_by_current_request())
1206 if (!query_result.available())
1208 abort_query_node_with_unexpected_exception< typename SubquerySpec::parent_query::output_type >(parent_result.m_node, runner.initial_executor(), std::make_exception_ptr(
recursive_dependency_error()));
1211 query_result.wait();
1212 return query_result.await_resume();
1229 template < query_spec_c QuerySpec >
1230 auto dump_query_to_file(std::filesystem::path output_path,
typename QuerySpec::input_type input) -> std::filesystem::path
1232 runner runner(
this);
1233 query_descriptor< QuerySpec >& desc = get_query_descriptor< QuerySpec >();
1234 query_result< typename QuerySpec::output_type > query_result = desc.m_get(runner.initial_executor(), input,
false);
1235 if (query_result.started_by_current_request())
1238 if (!query_result.available())
1240 abort_query_node_with_unexpected_exception< typename QuerySpec::output_type >(query_result.m_node, runner.initial_executor(), std::make_exception_ptr(
recursive_dependency_error()));
1243 query_result.wait();
1245 std::vector< std::byte >
const marshaled_dump =
marshall(query_result.m_node);
1246 std::ofstream out(output_path, std::ios::binary | std::ios::trunc);
1249 throw std::runtime_error(std::format(
"Failed to open dump file for writing: {}", output_path.string()));
1252 if (!marshaled_dump.empty())
1254 out.write(
reinterpret_cast< char const*
>(marshaled_dump.data()),
static_cast< std::streamsize
>(marshaled_dump.size()));
1258 throw std::runtime_error(std::format(
"Failed to write dump file: {}", output_path.string()));
1287 template < subquery_spec_c SubquerySpec >
1288 struct subquery_slot
1292 std::string m_input_string;
1293 std::string m_output_string;
1294 std::vector< resumption > waiters;
1297 template < subquery_spec_c SubquerySpec >
1298 struct subquery_state
1300 std::map< typename SubquerySpec::input_type, subquery_slot< SubquerySpec > > m_instances;
1303 struct subquery_state_any
1306 std::type_index m_input_type = std::type_index(
typeid(
void));
1307 std::type_index m_output_type = std::type_index(
typeid(
void));
1308 std::string m_subquery_id;
1309 std::function< std::vector< resumption >(std::any&) > m_finalize_missing;
1310 std::function< std::vector< resumption >(std::any&, error_result
const&) > m_finalize_parent_failed;
1311 std::function< void(std::any&, std::function<
void(std::any&, std::any&) >) > m_iterate_all;
1316 std::shared_mutex m_mutex;
1317 std::condition_variable_any m_external_cv;
1319 std::type_index m_query_type =
typeid(void);
1320 std::string m_input_string;
1321 std::string m_output_string;
1329 std::vector< resumption > waiters;
1330 std::vector< debug_message > m_messages;
1331 std::set< query_node* > dependencies;
1332 std::set< query_node* > dependents;
1333 std::map< std::type_index, subquery_state_any > m_subquery_states;
1334 std::atomic< std::chrono::nanoseconds::rep > m_self_time = 0;
1335 std::atomic< bool > m_long_running_threshold_reported =
false;
1336 std::atomic< bool > m_long_running_completion_reported =
false;
1337 std::atomic< bool > m_finished =
false;
1339 void unblock_waiters(executor* executor);
1341 void unblock_waiters(executor* executor, std::unique_lock< std::shared_mutex >& lock);
1343 void abort_waiters(executor* executor, std::unique_lock< std::shared_mutex >& lock, std::exception_ptr exception);
1346 static void declare_dependency(query_node* dependent, query_node* dependency);
1348 template < query_spec_c HandlerSpec >
1349 struct query_descriptor;
1353 executor* m_exec =
nullptr;
1354 query_node* m_node =
nullptr;
1355 std::mutex m_run_mutex;
1357 virtual std::string query_name()
const = 0;
1359 virtual std::string input_text()
const = 0;
1360 virtual void abort_with_unexpected_exception(executor* executor, std::exception_ptr exception) = 0;
1361 virtual ~promise_base() =
default;
1364 template < query_spec_c QuerySpec >
1365 struct query_descriptor
1367 std::function< query_result< typename QuerySpec::output_type >(executor*,
typename QuerySpec::input_type
const&,
bool) > m_get;
1368 std::any m_query_handler_impl;
1371 struct query_descriptor_any
1373 std::function< void(std::function<
void(query_node& node) >) > m_iterate_all;
1374 std::type_index m_input_type = std::type_index(
typeid(
void));
1375 std::type_index m_output_type = std::type_index(
typeid(
void));
1376 std::string m_query_id;
1379 template <
typename T >
1380 struct dependency_descriptor_type;
1382 template < query_spec_c QuerySpec >
1383 struct dependency_descriptor_type< QuerySpec >
1385 using type = query_descriptor< QuerySpec >*;
1388 template < subquery_spec_c SubquerySpec >
1389 struct dependency_descriptor_type< SubquerySpec >
1391 using type = query_descriptor< typename SubquerySpec::parent_query >*;
1394 template <
typename T >
1395 struct dependency_descriptor_table;
1397 template <
typename... Ts >
1398 struct dependency_descriptor_table< rpnx::typelist< Ts... > >
1400 using type = std::tuple< typename dependency_descriptor_type< Ts >::type... >;
1403 template < rpnx::typelist_c Typelist >
1404 using dependency_descriptor_table_from_typelist =
typename dependency_descriptor_table< Typelist >::type;
1406 template <
typename T >
1407 std::string datavalue_to_string(T
const& input)
1409 auto desc = this->m_text_descriptors.get_or_create(std::type_index(
typeid(T)),
1412 return text_descriptor{};
1415 if (desc.m_to_string ==
nullptr)
1417 return "<no to_string available>";
1420 return desc.m_to_string(std::any(input), std::any());
1423 template < query_handler_spec_c HandlerSpec >
1424 struct query_handler_descriptor_memoized_impl
1426#ifdef RPNX_QUERYGRAPH_USE_CONC_UNORDERED_MAP
1427 rpnx::conc_sharded_unordered_map< query_handler_input_t< HandlerSpec >, query_node, hasher< query_handler_input_t< HandlerSpec > > > m_mt_cache;
1429 std::shared_mutex mutex;
1430 std::map< query_handler_input_t< HandlerSpec >, query_node > cache;
1433 query_descriptor< query_handler_query_t< HandlerSpec > >* m_handler_entry =
nullptr;
1435 dependency_descriptor_table_from_typelist< query_handler_dependencies_t< HandlerSpec > > dependency_descriptors = {};
1437 std::function< coroutine< HandlerSpec >(query_handler_input_t< HandlerSpec >) > m_startup;
1439#ifdef RPNX_QUERYGRAPH_USE_CONC_UNORDERED_MAP
1440 query_result< query_handler_output_t< HandlerSpec > > get(executor* exec, query_handler_input_t< HandlerSpec >
const& input_ask,
bool immediate)
1442 std::coroutine_handle< void > cr;
1443 bool started =
false;
1444 promise_base* pbase =
nullptr;
1445 auto& node = m_mt_cache.get_or_init_iter(input_ask,
1446 [&](query_handler_input_t< HandlerSpec >
const& key, query_node& node)
1449 node.m_query_type = std::type_index(
typeid(query_handler_query_t< HandlerSpec >));
1451 node.m_input.emplace< query_handler_input_t< HandlerSpec >
const* >(&key);
1453 if (exec->m_runner->m_graph->m_enable_node_debug_io_capture)
1455 node.m_input_string = exec->m_runner->m_graph->datavalue_to_string(input_ask);
1457 coroutine< HandlerSpec > co = m_startup(input_ask);
1458 node.m_result = result< query_handler_output_t< HandlerSpec > >();
1460 co.m_promise->m_node = &node;
1461 co.m_promise->m_handler_entry = m_handler_entry;
1462 cr = std::coroutine_handle< typename coroutine< HandlerSpec >::promise_type >::from_promise(*co.m_promise);
1463 pbase = co.m_promise;
1464 node.m_cort = std::make_shared< coroutine< HandlerSpec > >(std::move(co));
1470 exec->declare_runnable(cr, pbase, immediate);
1473 return query_result< query_handler_output_t< HandlerSpec > >(&node, started);
1476 void iterate_all(std::function<
void(query_node& node) > func)
1478 auto iter = m_mt_cache.range_exclusive();
1479 for (
auto it = iter.begin(); it != iter.end(); ++it)
1486 query_result< query_handler_output_t< HandlerSpec > > get(executor* exec, query_handler_input_t< HandlerSpec >
const& input_ask)
1488 std::shared_lock lock(mutex);
1489 typename std::map< query_handler_input_t< HandlerSpec >, query_node >::iterator it = cache.find(input_ask);
1490 if (it != cache.end())
1492 return query_result< query_handler_output_t< HandlerSpec > >(&it->second,
false);
1495 std::unique_lock ulock(mutex);
1497 it = cache.find(input_ask);
1498 if (it != cache.end())
1500 return query_result< query_handler_output_t< HandlerSpec > >(&it->second,
false);
1502 auto [node_it, inserted] = cache.try_emplace(input_ask);
1504 coroutine< HandlerSpec > co = m_startup(input_ask);
1505 query_node& node = node_it->second;
1507 node.m_query_type = std::type_index(
typeid(query_handler_query_t< HandlerSpec >));
1508 node.m_input.emplace< query_handler_input_t< HandlerSpec >
const* >(&node_it->first);
1509 if (exec->m_runner->m_graph->m_enable_node_debug_io_capture)
1511 node.m_input_string = exec->m_runner->m_graph->datavalue_to_string(input_ask);
1513 node.m_result = result< query_handler_output_t< HandlerSpec > >();
1514 co.m_promise->m_node = &node;
1515 co.m_promise->m_handler_entry = m_handler_entry;
1516 std::coroutine_handle< void > cr = std::coroutine_handle< typename coroutine< HandlerSpec >::promise_type >::from_promise(*co.m_promise);
1517 promise_base* pbase = co.m_promise;
1518 node.m_cort = std::make_shared< coroutine< HandlerSpec > >(std::move(co));
1520 exec->declare_runnable(cr, pbase);
1521 return query_result< query_handler_output_t< HandlerSpec > >(&node,
true);
1524 template < query_spec_c QuerySpec >
1525 auto get_dependency_descriptor() -> query_descriptor< QuerySpec >&
1527 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< QuerySpec >(),
"Failed to resolve index of dependency descriptor: QuerySpec is not listed in the HandlerSpec dependencies, no entry in dependency_descriptors exists");
1529 constexpr std::size_t index = *query_handler_dependencies_t< HandlerSpec >::template index_of< QuerySpec >();
1533 return *std::get< index >(dependency_descriptors);
1536 template < subquery_spec_c SubquerySpec >
1537 auto get_subquery_parent_descriptor() -> query_descriptor< typename SubquerySpec::parent_query >&
1539 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< SubquerySpec >(),
"Failed to resolve index of subquery descriptor: SubquerySpec is not listed in HandlerSpec dependencies");
1540 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< typename SubquerySpec::parent_query >(),
"Making a subquery request requires declaring the subquery's parent query in HandlerSpec dependencies");
1542 constexpr std::size_t index = *query_handler_dependencies_t< HandlerSpec >::template index_of< SubquerySpec >();
1544 return *std::get< index >(dependency_descriptors);
1547 void bind(graph* graph)
1549 bind_spec< 0 >(graph);
1552 template <
size_t N >
1553 void bind_spec(graph* graph)
1555 if constexpr (N < query_handler_dependencies_t< HandlerSpec >::size)
1557 using dep = query_handler_dependencies_t< HandlerSpec >::template type_at< N >;
1558 if constexpr (query_spec_c< dep >)
1560 query_descriptor< dep >& dep_descriptor = graph->get_query_descriptor< dep >();
1561 std::get< N >(dependency_descriptors) = &dep_descriptor;
1563 else if constexpr (subquery_spec_c< dep >)
1565 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< typename dep::parent_query >(),
"A handler that depends on a subquery must also list the subquery's parent query in HandlerSpec dependencies");
1566 query_descriptor< typename dep::parent_query >& dep_descriptor = graph->get_query_descriptor<
typename dep::parent_query >();
1567 std::get< N >(dependency_descriptors) = &dep_descriptor;
1569 bind_spec< N + 1 >(graph);
1574 struct text_descriptor
1576 std::function< std::string(std::any, std::any) > m_to_string;
1579 struct binary_descriptor
1581 struct result_binary_data
1583 std::vector< std::byte > m_output_data;
1584 status m_status = status::pending;
1585 std::optional< canonical_error_result_data > m_error_result;
1588 std::function< std::vector< std::byte >(std::any&) > m_serialize_from_cptr;
1589 std::function< result_binary_data(std::any&) > m_serialize_from_result;
1591 std::function< std::any(std::vector< std::byte >
const&) > m_deserialize;
1593 std::array< std::byte, 16 > m_typesig;
1596 struct canonical_error_descriptor
1598 std::function< error_result(std::exception
const&) > m_capture;
1601 std::map< std::type_index, std::any > query_handler_registry;
1602 std::map< std::type_index, query_descriptor_any > query_descriptor_any_registry;
1603 std::vector< std::function< void(graph*) > > m_pending_binds;
1604 rpnx::conc_sharded_unordered_map< std::type_index, text_descriptor, hasher< std::type_index > > m_text_descriptors;
1605 std::map< std::type_index, binary_descriptor > m_binary_descriptors;
1606 std::map< std::type_index, canonical_error_descriptor > m_canonical_error_descriptors;
1607 std::map< std::type_index, std::string > m_query_names;
1610 auto long_running_node_description(promise_base* promise) -> std::string;
1613 void print_long_running_node_progress_diagnostic(promise_base* promise);
1616 void print_long_running_node_completion_diagnostic(promise_base* promise, std::chrono::nanoseconds self_time);
1619 void print_noncanonical_exception_diagnostic(promise_base* promise, error_result
const& error);
1621 static void declare_resumptions(executor* executor, std::vector< resumption > waiters);
1623 static void abort_resumptions(executor* executor, std::vector< resumption > waiters, std::exception_ptr exception);
1625 template < subquery_spec_c SubquerySpec >
1626 static std::vector< resumption > take_subquery_waiters(subquery_slot< SubquerySpec >& slot)
1628 std::vector< resumption > waiters;
1629 waiters.assign(std::make_move_iterator(slot.waiters.begin()), std::make_move_iterator(slot.waiters.end()));
1630 slot.waiters.clear();
1634 static canonical_error_storage make_subquery_parent_failed_storage(std::string subquery_id, error_result parent_error);
1636 static error_result make_subquery_does_not_exist_error(std::string subquery_id);
1638 static error_result make_subquery_parent_failed_error(std::string subquery_id, error_result
const& parent_error);
1640 template < subquery_spec_c SubquerySpec >
1641 static auto make_subquery_state_any() -> subquery_state_any
1643 subquery_state_any state_any;
1644 state_any.m_state.emplace< subquery_state< SubquerySpec > >();
1645 state_any.m_input_type = std::type_index(
typeid(
typename SubquerySpec::input_type));
1646 state_any.m_output_type = std::type_index(
typeid(
typename SubquerySpec::output_type));
1647 state_any.m_subquery_id = std::string(SubquerySpec::subquery_id);
1648 state_any.m_finalize_missing = [](std::any& state_value) -> std::vector< resumption >
1650 std::vector< resumption > waiters;
1651 subquery_state< SubquerySpec >& state = std::any_cast< subquery_state< SubquerySpec >& >(state_value);
1652 for (
auto& [input, slot] : state.m_instances)
1654 result< typename SubquerySpec::output_type >& res = std::any_cast< result< typename SubquerySpec::output_type >& >(slot.m_result);
1655 if (!res.has_result())
1657 res.set_error(make_subquery_does_not_exist_error(std::string(SubquerySpec::subquery_id)));
1658 std::vector< resumption > slot_waiters = take_subquery_waiters< SubquerySpec >(slot);
1659 waiters.insert(waiters.end(), std::make_move_iterator(slot_waiters.begin()), std::make_move_iterator(slot_waiters.end()));
1664 state_any.m_finalize_parent_failed = [](std::any& state_value, error_result
const& parent_error) -> std::vector< resumption >
1666 std::vector< resumption > waiters;
1667 subquery_state< SubquerySpec >& state = std::any_cast< subquery_state< SubquerySpec >& >(state_value);
1668 for (
auto& [input, slot] : state.m_instances)
1670 result< typename SubquerySpec::output_type >& res = std::any_cast< result< typename SubquerySpec::output_type >& >(slot.m_result);
1671 if (!res.has_result())
1673 res.set_error(make_subquery_parent_failed_error(std::string(SubquerySpec::subquery_id), parent_error));
1674 std::vector< resumption > slot_waiters = take_subquery_waiters< SubquerySpec >(slot);
1675 waiters.insert(waiters.end(), std::make_move_iterator(slot_waiters.begin()), std::make_move_iterator(slot_waiters.end()));
1680 state_any.m_iterate_all = [](std::any& state_value, std::function< void(std::any&, std::any&) > callback)
1682 subquery_state< SubquerySpec >& state = std::any_cast< subquery_state< SubquerySpec >& >(state_value);
1683 for (
auto& [input, slot] : state.m_instances)
1685 callback(slot.m_input, slot.m_result);
1691 template < subquery_spec_c SubquerySpec >
1692 auto get_or_create_subquery_slot_locked(query_node* parent_node,
typename SubquerySpec::input_type
const& input) -> subquery_slot< SubquerySpec >&
1694 std::type_index
const state_key = std::type_index(
typeid(SubquerySpec));
1695 auto state_it = parent_node->m_subquery_states.find(state_key);
1696 if (state_it == parent_node->m_subquery_states.end())
1698 auto [inserted_it, inserted] = parent_node->m_subquery_states.emplace(state_key, make_subquery_state_any< SubquerySpec >());
1700 state_it = inserted_it;
1703 subquery_state< SubquerySpec >& state = std::any_cast< subquery_state< SubquerySpec >& >(state_it->second.m_state);
1704 auto [slot_it, inserted] = state.m_instances.try_emplace(input);
1705 subquery_slot< SubquerySpec >& slot = slot_it->second;
1708 slot.m_input.template emplace< typename SubquerySpec::input_type const* >(&slot_it->first);
1709 slot.m_result.template emplace< result< typename SubquerySpec::output_type > >();
1710 if (node_debug_io_capture_enabled())
1712 slot.m_input_string = datavalue_to_string(input);
1718 template < subquery_spec_c SubquerySpec >
1719 std::vector< resumption > complete_subquery_from_parent_result_locked(query_node* parent_node, subquery_slot< SubquerySpec >& slot)
1721 std::vector< resumption > waiters;
1722 result< typename SubquerySpec::output_type >& subquery_res = std::any_cast< result< typename SubquerySpec::output_type >& >(slot.m_result);
1723 if (subquery_res.has_result())
1728 result< typename SubquerySpec::parent_query::output_type >
const& parent_res = std::any_cast< result< typename SubquerySpec::parent_query::output_type >
const& >(parent_node->m_result);
1729 if (parent_res.has_value())
1731 subquery_res.set_error(make_subquery_does_not_exist_error(std::string(SubquerySpec::subquery_id)));
1732 waiters = take_subquery_waiters< SubquerySpec >(slot);
1734 else if (parent_res.has_exception())
1736 subquery_res.set_error(make_subquery_parent_failed_error(std::string(SubquerySpec::subquery_id), parent_res.get_error_result()));
1737 waiters = take_subquery_waiters< SubquerySpec >(slot);
1742 template < subquery_spec_c SubquerySpec >
1743 auto get_subquery_result(executor* executor, query_node* parent_node,
typename SubquerySpec::input_type
const& input) -> subquery_query_result< SubquerySpec >
1745 std::unique_lock lock(parent_node->m_mutex);
1746 subquery_slot< SubquerySpec >& slot = get_or_create_subquery_slot_locked< SubquerySpec >(parent_node, input);
1747 std::vector< resumption > waiters = complete_subquery_from_parent_result_locked< SubquerySpec >(parent_node, slot);
1748 bool const abort_waiters = std::any_cast< result< typename SubquerySpec::output_type >
const& >(slot.m_result).has_unexpected_exception();
1749 std::exception_ptr
const unexpected_exception = abort_waiters ? std::any_cast< result< typename SubquerySpec::output_type >
const& >(slot.m_result).get_error_result().unexpected_exception() :
nullptr;
1753 abort_resumptions(executor, std::move(waiters), unexpected_exception);
1757 declare_resumptions(executor, std::move(waiters));
1759 return subquery_query_result< SubquerySpec >(parent_node, &slot);
1762 template < subquery_spec_c SubquerySpec >
1763 void set_subquery_result(executor* executor, query_node* parent_node,
typename SubquerySpec::input_type input,
typename SubquerySpec::output_type output)
1765 std::unique_lock lock(parent_node->m_mutex);
1766 subquery_slot< SubquerySpec >& slot = get_or_create_subquery_slot_locked< SubquerySpec >(parent_node, input);
1767 result< typename SubquerySpec::output_type >& subquery_res = std::any_cast< result< typename SubquerySpec::output_type >& >(slot.m_result);
1768 if (subquery_res.has_result())
1770 throw std::logic_error(std::format(
"Subquery result already produced: {}", std::string(SubquerySpec::subquery_id)));
1772 if (node_debug_io_capture_enabled())
1774 slot.m_output_string = datavalue_to_string(output);
1776 subquery_res.set_value(std::move(output));
1777 std::vector< resumption > waiters = take_subquery_waiters< SubquerySpec >(slot);
1779 parent_node->m_external_cv.notify_all();
1780 declare_resumptions(executor, std::move(waiters));
1783 std::vector< resumption > finalize_missing_subqueries_locked(query_node* node);
1785 std::vector< resumption > finalize_parent_failed_subqueries_locked(query_node* node, error_result
const& parent_error);
1787 template < subquery_spec_c SubquerySpec >
1788 void register_subquery_descriptors()
1790 register_inputoutput_text_descriptor< typename SubquerySpec::input_type >();
1791 register_inputoutput_text_descriptor< typename SubquerySpec::output_type >();
1792 register_binary_descriptors< typename SubquerySpec::input_type >();
1793 register_binary_descriptors< typename SubquerySpec::output_type >();
1796 template < rpnx::typelist_c Typelist, std::
size_t N = 0 >
1797 void register_subquery_descriptors_from_typelist()
1799 if constexpr (N < Typelist::size)
1801 using dep =
typename Typelist::template type_at< N >;
1802 if constexpr (subquery_spec_c< dep >)
1804 register_subquery_descriptors< dep >();
1806 register_subquery_descriptors_from_typelist< Typelist, N + 1 >();
1810 std::optional< error_result > capture_subquery_parent_failed(subquery_parent_failed
const& exception)
const;
1812 error_result capture_exception(std::exception_ptr exception)
const;
1814 error_result capture_current_exception()
const;
1816 template <
typename Output >
1817 void abort_query_node_with_unexpected_exception(query_node* node, executor* executor, std::exception_ptr exception)
1819 std::vector< resumption > subquery_waiters;
1821 std::unique_lock lock(node->m_mutex);
1822 result< Output >& res = std::any_cast< result< Output >& >(node->m_result);
1823 if (res.has_result())
1827 res.set_error(error_result::unexpected(exception));
1828 subquery_waiters = finalize_parent_failed_subqueries_locked(node, res.get_error_result());
1829 node->abort_waiters(executor, lock, exception);
1831 abort_resumptions(executor, std::move(subquery_waiters), exception);
1847 template <
typename Error >
1850 static_assert(std::derived_from< Error, std::exception >,
"Canonical errors must derive from std::exception");
1851 static_assert(std::copy_constructible< Error >,
"Canonical errors must be copy constructible");
1853 canonical_error_descriptor descriptor;
1854 descriptor.m_capture = [](std::exception
const& exception)
1858 m_canonical_error_descriptors[std::type_index(
typeid(Error))] = std::move(descriptor);
1870 template <
typename T >
1873 m_text_descriptors.get_or_init(std::type_index(
typeid(T)),
1874 [](text_descriptor& desc)
1876 desc.m_to_string = [](std::any val, std::any codec) -> std::string
1894 template <
typename T >
1897 binary_descriptor desc;
1898 desc.m_serialize_from_cptr = [](std::any& val) -> std::vector< std::byte >
1900 T
const* value = std::any_cast< T const* >(val);
1903 desc.m_serialize_from_result = [](std::any& val) -> binary_descriptor::result_binary_data
1905 result< T >& res = std::any_cast< result< T >& >(val);
1909 return binary_descriptor::result_binary_data{
1910 .m_output_data = std::move(data),
1911 .m_status = status::completed,
1912 .m_error_result = std::nullopt,
1917 std::vector< std::byte > data;
1918 std::string str =
"Exception: ";
1922 data.push_back(std::byte(c));
1924 return binary_descriptor::result_binary_data{
1925 .m_output_data = std::move(data),
1930 return binary_descriptor::result_binary_data{
1931 .m_output_data = {},
1932 .m_status = status::pending,
1933 .m_error_result = std::nullopt,
1936 desc.m_deserialize = [](std::vector< std::byte >
const& data) -> std::any
1939 rpnx::serial4::deserialize_iter(value, data.begin(), data.end());
1940 return std::any(std::move(value));
1942 desc.m_typesig = rpnx::serial4::get_type_typesig_hash16< T >();
1944 m_binary_descriptors[std::type_index(
typeid(T))] = desc;
1958 template < query_handler_spec_c QuerySpec >
1965 auto it = handler_map->find(input);
1966 if (it == handler_map->end())
1968 throw std::logic_error(
"No handler found for the given input in handler map");
1970 co_return it->second;
1979 template < query_handler_spec_c HandlerSpec >
1980 requires std::same_as< query_handler_input_t< HandlerSpec >, std::monostate >
2000 template < query_spec_c QuerySpec >
2001 requires std::same_as< typename QuerySpec::input_type, std::monostate >
2022 template < query_handler_spec_c HandlerSpec,
typename Handler >
2026 if (query_handler_registry.find(query_index) != query_handler_registry.end())
2028 throw std::logic_error(
"Handler already registered");
2030 query_handler_registry[query_index].emplace< query_descriptor< query_handler_query_t< HandlerSpec > > >();
2031 query_descriptor_any& qd_any = query_descriptor_any_registry[query_index];
2033 query_descriptor< query_handler_query_t< HandlerSpec > >& descriptor = std::any_cast< query_descriptor< query_handler_query_t< HandlerSpec > >& >(query_handler_registry[query_index]);
2034 descriptor.m_query_handler_impl = std::make_shared< query_handler_descriptor_memoized_impl< HandlerSpec > >();
2035 query_handler_descriptor_memoized_impl< HandlerSpec >& memoized_impl = *std::any_cast< std::shared_ptr< query_handler_descriptor_memoized_impl< HandlerSpec > >& >(descriptor.m_query_handler_impl);
2036 memoized_impl.m_startup = std::move(h);
2037 memoized_impl.m_handler_entry = &descriptor;
2042 return memoized_impl.get(exec, input_ask, immediate);
2045 qd_any.m_iterate_all = [&memoized_impl](std::function< void(query_node&) > callback)
2047 memoized_impl.iterate_all(std::move(callback));
2058 register_subquery_descriptors_from_typelist< query_handler_dependencies_t< HandlerSpec > >();
2059 register_subquery_descriptors_from_typelist< query_handler_produced_subqueries_t< HandlerSpec > >();
2063 m_pending_binds.push_back(
2064 [&memoized_impl](
graph* exec)
2066 memoized_impl.bind(exec);
2071 template < query_spec_c QuerySpec >
2072 auto get_query_descriptor() -> query_descriptor< QuerySpec >&
2074 auto it = query_handler_registry.find(std::type_index(
typeid(QuerySpec)));
2075 if (it == query_handler_registry.end())
2077 throw std::logic_error(
"No handler registered for the requested query spec");
2079 return std::any_cast< query_descriptor< QuerySpec >& >(it->second);
2104 template <
typename Visitor >
2107 for (
auto const& [type_index, binary_desc] : m_binary_descriptors)
2109 auto text_desc = m_text_descriptors.get_or_create(type_index,
2112 return text_descriptor{};
2115 visitor(binary_desc.m_typesig, binary_desc.m_deserialize, text_desc.m_to_string);
2136 std::vector< std::byte >
marshall(query_node* start_node);
2147 template < typename Result >
2150 graph::query_node* m_node;
2151 bool m_started_by_current_request =
false;
2154 explicit query_result(graph::query_node* node_ptr,
bool started_by_current_request =
false) : m_node(node_ptr), m_started_by_current_request(started_by_current_request)
2158 bool started_by_current_request()
const noexcept
2160 return m_started_by_current_request;
2163 bool available()
const
2165 std::shared_lock lock(m_node->m_mutex);
2166 result< Result > const& res = std::any_cast< result< Result >
const& >(m_node->m_result);
2172 std::unique_lock lock(m_node->m_mutex);
2173 m_node->m_external_cv.wait(lock,
2176 result< Result > const& res = std::any_cast< result< Result >
const& >(m_node->m_result);
2183 std::shared_lock lock(m_node->m_mutex);
2184 result< Result > const& res = std::any_cast< result< Result >
const& >(m_node->m_result);
2188 bool await_ready([[maybe_unused]] std::unique_lock< std::shared_mutex >& lock)
2190 return std::any_cast< result< Result >& >(m_node->m_result).has_result();
2193 bool await_ready([[maybe_unused]] std::shared_lock< std::shared_mutex >& lock)
2195 return std::any_cast< result< Result >
const& >(m_node->m_result).has_result();
2198 template <
typename U >
2199 void await_suspend(std::coroutine_handle< U > h)
2201 await_suspend_helper< typename U::coroutine_type >(h.promise(), h);
2204 template <
typename C >
2205 void await_suspend_helper(
typename C::promise_type& p, std::coroutine_handle< typename C::promise_type > h)
2207 await_suspend_helper2< C, typename C::spec_type >(p, h);
2210 template <
typename C,
typename HandlerSpec >
2211 void await_suspend_helper2(
typename C::promise_type& p, std::coroutine_handle< typename C::promise_type > h)
2213 std::unique_lock lock(m_node->m_mutex);
2215 graph::executor* current_executor = p.m_exec;
2216 assert(current_executor !=
nullptr);
2217 current_executor->check_legal_access();
2219 if (await_ready(lock))
2221 result< Result > const& res = std::any_cast< result< Result >
const& >(m_node->m_result);
2226 p.abort_with_unexpected_exception(current_executor, unexpected_exception);
2230 current_executor->declare_runnable(graph::resumption{.m_promise = &p, .handle = h},
false);
2235 m_node->waiters.push_back(graph::resumption{.m_promise = &p, .handle = h});
2239 Result
const& await_resume()
2241 std::shared_lock lock(m_node->m_mutex);
2242 auto& res = std::any_cast< result< Result >
const& >(m_node->m_result);
2247 template < subquery_spec_c SubquerySpec >
2248 struct subquery_query_result
2250 graph::query_node* m_parent_node;
2251 graph::subquery_slot< SubquerySpec >* m_slot;
2254 subquery_query_result(graph::query_node* parent_node, graph::subquery_slot< SubquerySpec >* slot) : m_parent_node(parent_node), m_slot(slot)
2258 bool available()
const
2260 std::shared_lock lock(m_parent_node->m_mutex);
2267 std::unique_lock lock(m_parent_node->m_mutex);
2268 m_parent_node->m_external_cv.wait(lock,
2271 result< typename SubquerySpec::output_type >
const& res = std::any_cast< result< typename SubquerySpec::output_type >
const& >(m_slot->m_result);
2278 std::shared_lock lock(m_parent_node->m_mutex);
2279 result< typename SubquerySpec::output_type >
const& res = std::any_cast< result< typename SubquerySpec::output_type >
const& >(m_slot->m_result);
2280 return res.
has_result() && !res.has_unexpected_exception();
2283 bool await_ready([[maybe_unused]] std::unique_lock< std::shared_mutex >& lock)
2285 return std::any_cast< result< typename SubquerySpec::output_type >& >(m_slot->m_result).has_result();
2288 bool await_ready([[maybe_unused]] std::shared_lock< std::shared_mutex >& lock)
2290 return std::any_cast< result< typename SubquerySpec::output_type >
const& >(m_slot->m_result).has_result();
2293 template <
typename U >
2294 void await_suspend(std::coroutine_handle< U > h)
2296 await_suspend_helper< typename U::coroutine_type >(h.promise(), h);
2299 template <
typename C >
2300 void await_suspend_helper(
typename C::promise_type& p, std::coroutine_handle< typename C::promise_type > h)
2302 std::unique_lock lock(m_parent_node->m_mutex);
2304 graph::executor* current_executor = p.m_exec;
2305 assert(current_executor !=
nullptr);
2306 current_executor->check_legal_access();
2308 if (await_ready(lock))
2310 result< typename SubquerySpec::output_type >
const& res = std::any_cast< result< typename SubquerySpec::output_type >
const& >(m_slot->m_result);
2311 if (res.has_unexpected_exception())
2313 std::exception_ptr unexpected_exception = res.get_error_result().unexpected_exception();
2315 p.abort_with_unexpected_exception(current_executor, unexpected_exception);
2319 current_executor->declare_runnable(graph::resumption{.m_promise = &p, .handle = h},
false);
2324 m_slot->waiters.push_back(graph::resumption{.m_promise = &p, .handle = h});
2328 typename SubquerySpec::output_type
const& await_resume()
2330 std::shared_lock lock(m_parent_node->m_mutex);
2331 result< typename SubquerySpec::output_type >
const& res = std::any_cast< result< typename SubquerySpec::output_type >
const& >(m_slot->m_result);
2337 template < query_handler_spec_c HandlerSpec,
typename Ret >
2338 class cosubroutine_impl;
2353 template < query_handler_spec_c HandlerSpec >
2367 template <
typename Ret >
2371 struct promise_type :
public graph::promise_base
2376 typename graph::template query_descriptor< query_handler_query_t< HandlerSpec > >* m_handler_entry =
nullptr;
2379 std::string query_name()
const override
2384 std::string input_text()
const override
2387 input_type
const* input = std::any_cast< input_type const* >(m_node->m_input);
2388 return m_exec->m_runner->m_graph->datavalue_to_string(*input);
2391 template < query_spec_c QuerySpec >
2392 auto await_transform(
request< QuerySpec > a) -> query_result< typename QuerySpec::output_type >
2395 assert(m_exec !=
nullptr);
2396 assert(m_node !=
nullptr);
2398 typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec >& handler_impl = *std::any_cast< std::shared_ptr< typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec > >& >(m_handler_entry->m_query_handler_impl);
2400 typename graph::template query_descriptor< QuerySpec >& dep_query_descriptor = handler_impl.template get_dependency_descriptor< QuerySpec >();
2402 query_result< typename QuerySpec::output_type > await_obj = dep_query_descriptor.m_get(m_exec, a.
m_input,
false);
2404 graph::query_node* await_node = await_obj.m_node;
2405 graph::declare_dependency(m_node, await_node);
2409 template < subquery_spec_c SubquerySpec >
2414 assert(m_exec !=
nullptr);
2415 assert(m_node !=
nullptr);
2417 typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec >& handler_impl = *std::any_cast< std::shared_ptr< typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec > >& >(m_handler_entry->m_query_handler_impl);
2419 typename graph::template query_descriptor< typename SubquerySpec::parent_query >& parent_query_descriptor = handler_impl.template get_subquery_parent_descriptor< SubquerySpec >();
2421 query_result< typename SubquerySpec::parent_query::output_type > parent_await_obj = parent_query_descriptor.m_get(m_exec, a.m_parent_input,
false);
2423 graph::query_node* parent_node = parent_await_obj.m_node;
2424 graph::declare_dependency(m_node, parent_node);
2425 return m_exec->m_runner->m_graph->template get_subquery_result< SubquerySpec >(m_exec, parent_node, a.
m_input);
2428 template < query_spec_c QuerySpec >
2432 assert(m_exec !=
nullptr);
2433 assert(m_node !=
nullptr);
2435 typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec >& handler_impl = *std::any_cast< std::shared_ptr< typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec > >& >(m_handler_entry->m_query_handler_impl);
2436 typename graph::template query_descriptor< QuerySpec >& dep_query_descriptor = handler_impl.template get_dependency_descriptor< QuerySpec >();
2437 query_result< typename QuerySpec::output_type > await_obj = dep_query_descriptor.m_get(m_exec, dep.
m_request.m_input,
true);
2438 graph::query_node* await_node = await_obj.m_node;
2439 graph::declare_dependency(m_node, await_node);
2440 return std::suspend_never{};
2443 template < subquery_spec_c SubquerySpec >
2447 static_assert(std::same_as< typename SubquerySpec::parent_query, query_handler_query_t< HandlerSpec > >,
"A handler can only yield subqueries whose parent_query is the handler query");
2448 assert(m_exec !=
nullptr);
2449 assert(m_node !=
nullptr);
2451 m_exec->m_runner->m_graph->template set_subquery_result< SubquerySpec >(m_exec, m_node, std::move(res.
m_input), std::move(res.
m_output));
2452 return std::suspend_never{};
2455 template <
typename Ret >
2458 assert(m_node !=
nullptr);
2459 auto* sub_promise = sub.m_promise;
2460 sub_promise->m_node = m_node;
2461 sub_promise->m_handler_entry = m_handler_entry;
2468 assert(m_node !=
nullptr);
2469 std::unique_lock lock(m_node->m_mutex);
2471 if (m_exec->m_runner->m_graph->node_debug_io_capture_enabled())
2473 m_node->m_output_string = m_exec->m_runner->m_graph->datavalue_to_string(value);
2476 std::vector< graph::resumption > subquery_waiters = m_exec->m_runner->m_graph->finalize_missing_subqueries_locked(m_node);
2477 m_node->unblock_waiters(m_exec, lock);
2478 graph::declare_resumptions(m_exec, std::move(subquery_waiters));
2481 auto initial_suspend()
2483 return std::suspend_always{};
2486 auto final_suspend()
noexcept
2488 std::unique_lock lock(m_node->m_mutex);
2490 if (!res.
has_result() && m_node->dependencies.empty())
2492 std::exception_ptr exception = std::make_exception_ptr(
bad_continuation());
2494 std::vector< graph::resumption > subquery_waiters = m_exec->m_runner->m_graph->finalize_parent_failed_subqueries_locked(m_node, res.
get_error_result());
2495 m_node->abort_waiters(m_exec, lock, exception);
2496 graph::abort_resumptions(m_exec, std::move(subquery_waiters), exception);
2498 return std::suspend_always{};
2501 auto get_return_object()
2504 assert(m_coroutine == &co);
2505 assert(co.m_promise ==
this);
2509 auto yield_value(
debug_message msg, std::source_location loc = std::source_location::current())
2511 assert(m_node !=
nullptr);
2512 std::unique_lock lock(m_node->m_mutex);
2514 m_node->m_messages.push_back(std::move(msg));
2515 return std::suspend_never{};
2518 auto unhandled_exception()
2520 assert(m_node !=
nullptr);
2521 assert(m_exec !=
nullptr);
2522 std::unique_lock lock(m_node->m_mutex);
2524 error_result captured_error = m_exec->m_runner->m_graph->capture_current_exception();
2526 if (unexpected_exception !=
nullptr)
2528 m_exec->m_runner->m_graph->print_noncanonical_exception_diagnostic(
this, captured_error);
2530 res.
set_error(std::move(captured_error));
2531 std::vector< graph::resumption > subquery_waiters = m_exec->m_runner->m_graph->finalize_parent_failed_subqueries_locked(m_node, res.
get_error_result());
2532 if (unexpected_exception !=
nullptr)
2534 m_node->abort_waiters(m_exec, lock, unexpected_exception);
2535 graph::abort_resumptions(m_exec, std::move(subquery_waiters), unexpected_exception);
2539 m_node->unblock_waiters(m_exec, lock);
2540 graph::declare_resumptions(m_exec, std::move(subquery_waiters));
2544 void abort_with_unexpected_exception(graph::executor* executor, std::exception_ptr exception)
override
2546 this->m_exec = executor;
2547 assert(m_node !=
nullptr);
2548 m_exec->m_runner->m_graph->template abort_query_node_with_unexpected_exception< query_handler_output_t< HandlerSpec > >(m_node, executor, exception);
2551 template <
typename Res >
2552 void await_suspend_on(query_result< Res > q)
2554 std::unique_lock lock(q.m_node->m_mutex);
2556 graph::executor* current_executor = m_exec;
2557 assert(current_executor !=
nullptr);
2558 current_executor->check_legal_access();
2559 auto h = std::coroutine_handle< promise_type >::from_promise(*
this);
2561 if (q.await_ready(lock))
2563 result< Res > const& res = std::any_cast< result< Res >
const& >(q.m_node->m_result);
2568 abort_with_unexpected_exception(current_executor, unexpected_exception);
2573 current_executor->declare_runnable(graph::resumption{.m_promise =
this, .handle = h},
false);
2578 q.m_node->waiters.push_back(graph::resumption{.m_promise =
this, .handle = h});
2585 promise_type* m_promise =
nullptr;
2587 coroutine() noexcept : m_promise(
nullptr)
2595 m_promise = other.m_promise;
2596 other.m_promise =
nullptr;
2599 m_promise->m_coroutine =
this;
2607 m_promise->m_coroutine =
nullptr;
2608 std::coroutine_handle< promise_type >::from_promise(*m_promise).destroy();
2612 coroutine(promise_type* p) : m_promise(p)
2614 assert(p->m_coroutine ==
nullptr);
2615 p->m_coroutine =
this;
2621 template < query_handler_spec_c HandlerSpec,
typename Ret >
2622 class cosubroutine_impl
2624 friend struct promise_type;
2625 friend struct coroutine< HandlerSpec >;
2626 template < query_handler_spec_c OtherHandlerSpec,
typename OtherRet >
2627 friend class cosubroutine_impl;
2630 using spec_type = HandlerSpec;
2631 struct promise_type :
public graph::promise_base
2633 using root_coroutine_type = coroutine< HandlerSpec >;
2634 using coroutine_type = cosubroutine_impl< HandlerSpec, Ret >;
2638 typename graph::template query_descriptor< query_handler_query_t< HandlerSpec > >* m_handler_entry =
nullptr;
2639 cosubroutine_impl* m_srt =
nullptr;
2640 result< Ret > m_result;
2641 std::optional< graph::resumption > m_waiter;
2643 std::string query_name()
const override
2645 return std::string(query_handler_query_t< HandlerSpec >::query_id);
2648 std::string input_text()
const override
2650 using input_type = query_handler_input_t< HandlerSpec >;
2651 input_type
const* input = std::any_cast< input_type const* >(m_node->m_input);
2652 return m_exec->m_runner->m_graph->datavalue_to_string(*input);
2655 auto get_return_object()
2657 return cosubroutine_impl(
this);
2660 auto initial_suspend()
2662 return std::suspend_always{};
2665 void return_value(Ret value)
2667 assert(m_node !=
nullptr);
2668 assert(this->m_exec !=
nullptr);
2671 std::unique_lock lock(m_mutex);
2672 m_result.set_value(std::move(value));
2673 unblock_waiter(lock);
2677 auto yield_value(debug_message msg, std::source_location loc = std::source_location::current())
2679 assert(m_node !=
nullptr);
2680 std::unique_lock lock(m_node->m_mutex);
2682 m_node->m_messages.push_back(std::move(msg));
2683 return std::suspend_never{};
2687 template < query_spec_c QuerySpec >
2688 auto yield_value(dependency< QuerySpec > dep)
2690 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< QuerySpec >(),
"Making a request to a query which is not listed in the HandlerSpec::dependencies");
2691 assert(m_exec !=
nullptr);
2692 assert(m_node !=
nullptr);
2694 typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec >& handler_impl = *std::any_cast< std::shared_ptr< typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec > >& >(m_handler_entry->m_query_handler_impl);
2695 typename graph::template query_descriptor< QuerySpec >& dep_query_descriptor = handler_impl.template get_dependency_descriptor< QuerySpec >();
2696 query_result< typename QuerySpec::output_type > await_obj = dep_query_descriptor.m_get(m_exec, dep.m_request.m_input,
true);
2697 graph::query_node* await_node = await_obj.m_node;
2698 graph::declare_dependency(m_node, await_node);
2699 return std::suspend_never{};
2702 auto final_suspend() noexcept
2704 std::unique_lock lock(m_mutex);
2705 assert(m_result.has_result());
2706 return std::suspend_always{};
2709 auto unhandled_exception()
2711 std::unique_lock lock(m_mutex);
2712 assert(m_node !=
nullptr);
2713 assert(this->m_exec !=
nullptr);
2714 m_result.set_error(m_exec->m_runner->m_graph->capture_current_exception());
2715 unblock_waiter(lock);
2718 void abort_with_unexpected_exception(graph::executor* executor, std::exception_ptr exception)
override
2720 this->m_exec = executor;
2722 std::unique_lock lock(m_mutex);
2723 if (!m_result.has_result())
2725 m_result.set_error(error_result::unexpected(exception));
2728 assert(m_node !=
nullptr);
2729 m_exec->m_runner->m_graph->template abort_query_node_with_unexpected_exception< query_handler_output_t< HandlerSpec > >(m_node, executor, exception);
2732 auto unblock_waiter(std::unique_lock< std::mutex >& lock)
2734 if (m_waiter.has_value())
2736 auto resumption = m_waiter.value();
2738 m_exec->check_legal_access();
2739 m_exec->declare_runnable(resumption,
false);
2745 template <
typename Ret2 >
2746 auto await_transform(
typename coroutine< HandlerSpec >::template cosubroutine< Ret2 >&& sub)
2748 assert(m_node !=
nullptr);
2749 sub.m_promise->m_node = m_node;
2750 sub.m_promise->m_handler_entry = m_handler_entry;
2751 m_exec->declare_runnable(std::coroutine_handle<
typename coroutine< HandlerSpec >::template cosubroutine< Ret2 >::promise_type >::from_promise(*sub.m_promise), sub.m_promise,
false);
2752 return typename coroutine< HandlerSpec >::template cosubroutine< Ret2 >::awaitable(sub.m_promise);
2755 template < query_spec_c QuerySpec >
2756 auto await_transform(request< QuerySpec > a) -> query_result< typename QuerySpec::output_type >
2758 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< QuerySpec >(),
"Making a request to a query which is not listed in the HandlerSpec::dependencies");
2759 assert(m_exec !=
nullptr);
2760 assert(m_node !=
nullptr);
2762 typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec >& handler_impl = *std::any_cast< std::shared_ptr< typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec > >& >(m_handler_entry->m_query_handler_impl);
2764 typename graph::template query_descriptor< QuerySpec >& dep_query_descriptor = handler_impl.template get_dependency_descriptor< QuerySpec >();
2766 query_result< typename QuerySpec::output_type > await_obj = dep_query_descriptor.m_get(m_exec, a.m_input,
false);
2768 graph::query_node* await_node = await_obj.m_node;
2769 graph::declare_dependency(m_node, await_node);
2773 template < subquery_spec_c SubquerySpec >
2774 auto await_transform(subquery_request< SubquerySpec > a) -> subquery_query_result< SubquerySpec >
2776 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< SubquerySpec >(),
"Making a subquery request requires listing the subquery in query_handler_dependencies_t< HandlerSpec >");
2777 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< typename SubquerySpec::parent_query >(),
"Making a subquery request requires listing the subquery parent query in query_handler_dependencies_t< HandlerSpec >");
2778 assert(m_exec !=
nullptr);
2779 assert(m_node !=
nullptr);
2781 typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec >& handler_impl = *std::any_cast< std::shared_ptr< typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec > >& >(m_handler_entry->m_query_handler_impl);
2783 typename graph::template query_descriptor< typename SubquerySpec::parent_query >& parent_query_descriptor = handler_impl.template get_subquery_parent_descriptor< SubquerySpec >();
2785 query_result< typename SubquerySpec::parent_query::output_type > parent_await_obj = parent_query_descriptor.m_get(m_exec, a.m_parent_input,
false);
2787 graph::query_node* parent_node = parent_await_obj.m_node;
2788 graph::declare_dependency(m_node, parent_node);
2789 return m_exec->m_runner->m_graph->template get_subquery_result< SubquerySpec >(m_exec, parent_node, a.m_input);
2792 template < subquery_spec_c SubquerySpec >
2793 auto yield_value(subquery_result< SubquerySpec > res)
2795 static_assert(query_handler_produced_subqueries_t< HandlerSpec >::template contains< SubquerySpec >(),
"Yielding a subquery result requires listing the subquery in query_handler_produced_subqueries_t< HandlerSpec >");
2796 static_assert(std::same_as< typename SubquerySpec::parent_query, query_handler_query_t< HandlerSpec > >,
"A handler can only yield subqueries whose parent_query is the handler query");
2797 assert(m_exec !=
nullptr);
2798 assert(m_node !=
nullptr);
2800 m_exec->m_runner->m_graph->template set_subquery_result< SubquerySpec >(m_exec, m_node, std::move(res.m_input), std::move(res.m_output));
2801 return std::suspend_never{};
2806 assert(m_srt ==
nullptr);
2812 promise_type* m_promise;
2815 awaitable(promise_type* promise) : m_promise(promise)
2821 std::unique_lock lck(m_promise->m_mutex);
2822 return await_ready(lck);
2825 bool await_ready([[maybe_unused]] std::unique_lock< std::mutex >& lock)
noexcept
2827 assert(m_promise !=
nullptr);
2828 return m_promise->m_result.has_result();
2833 std::unique_lock lck(m_promise->m_mutex);
2834 assert(m_promise !=
nullptr);
2835 assert(m_promise->m_result.has_result());
2836 return m_promise->m_result.value();
2839 template <
typename U >
2840 void await_suspend(std::coroutine_handle< U > h)
2842 await_suspend_helper< typename U::coroutine_type >(h.promise(), h);
2845 template <
typename C >
2846 void await_suspend_helper(
typename C::promise_type& p, std::coroutine_handle< typename C::promise_type > h)
2848 assert(m_promise !=
nullptr);
2850 auto current_executor = p.m_exec;
2851 assert(current_executor !=
nullptr);
2852 current_executor->check_legal_access();
2854 graph::resumption r{.m_promise = &h.promise(), .handle = h};
2855 std::unique_lock lck(m_promise->m_mutex);
2857 if (await_ready(lck))
2859 p.m_exec->declare_runnable(r,
false);
2863 assert(m_promise->m_waiter.has_value() ==
false);
2864 m_promise->m_waiter = r;
2870 promise_type* m_promise;
2873 cosubroutine_impl(promise_type* p) : m_promise(p)
2875 assert(p->m_srt ==
nullptr);
2879 cosubroutine_impl(
const cosubroutine_impl&) =
delete;
2880 cosubroutine_impl& operator=(
const cosubroutine_impl&) =
delete;
2882 ~cosubroutine_impl()
2886 std::unique_lock lock(m_promise->m_run_mutex);
2889 m_promise->m_srt =
nullptr;
2890 std::coroutine_handle< promise_type >::from_promise(*m_promise).destroy();
2895 template < query_handler_spec_c HandlerSpec >
2896 class cosubroutine_impl< HandlerSpec, void >
2898 friend struct promise_type;
2899 friend struct coroutine< HandlerSpec >;
2900 template < query_handler_spec_c OtherHandlerSpec,
typename OtherRet >
2901 friend class cosubroutine_impl;
2904 using spec_type = HandlerSpec;
2905 struct promise_type :
public graph::promise_base
2907 using root_coroutine_type = coroutine< HandlerSpec >;
2908 using coroutine_type = cosubroutine_impl< HandlerSpec, void >;
2912 typename graph::template query_descriptor< query_handler_query_t< HandlerSpec > >* m_handler_entry =
nullptr;
2913 cosubroutine_impl* m_srt =
nullptr;
2914 result< void > m_result;
2915 std::optional< graph::resumption > m_waiter;
2917 std::string query_name()
const override
2919 return std::string(query_handler_query_t< HandlerSpec >::query_id);
2922 std::string input_text()
const override
2924 using input_type = query_handler_input_t< HandlerSpec >;
2925 input_type
const* input = std::any_cast< input_type const* >(m_node->m_input);
2926 return m_exec->m_runner->m_graph->datavalue_to_string(*input);
2929 auto get_return_object()
2931 return cosubroutine_impl(
this);
2934 auto initial_suspend()
2936 return std::suspend_always{};
2941 assert(m_node !=
nullptr);
2942 assert(this->m_exec !=
nullptr);
2945 std::unique_lock lock(m_mutex);
2946 m_result.set_value();
2947 unblock_waiter(lock);
2951 auto yield_value(debug_message msg, std::source_location loc = std::source_location::current())
2953 assert(m_node !=
nullptr);
2954 std::unique_lock lock(m_node->m_mutex);
2956 m_node->m_messages.push_back(std::move(msg));
2957 return std::suspend_never{};
2961 template < query_spec_c QuerySpec >
2962 auto yield_value(dependency< QuerySpec > dep)
2964 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< QuerySpec >(),
"Making a request to a query which is not listed in the HandlerSpec::dependencies");
2965 assert(m_exec !=
nullptr);
2966 assert(m_node !=
nullptr);
2968 typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec >& handler_impl = *std::any_cast< std::shared_ptr< typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec > >& >(m_handler_entry->m_query_handler_impl);
2969 typename graph::template query_descriptor< QuerySpec >& dep_query_descriptor = handler_impl.template get_dependency_descriptor< QuerySpec >();
2970 query_result< typename QuerySpec::output_type > await_obj = dep_query_descriptor.m_get(m_exec, dep.m_request.m_input,
true);
2971 graph::query_node* await_node = await_obj.m_node;
2972 graph::declare_dependency(m_node, await_node);
2973 return std::suspend_never{};
2976 auto final_suspend() noexcept
2978 std::unique_lock lock(m_mutex);
2979 assert(m_result.has_result());
2980 return std::suspend_always{};
2983 auto unhandled_exception()
2985 std::unique_lock lock(m_mutex);
2986 assert(m_node !=
nullptr);
2987 assert(this->m_exec !=
nullptr);
2988 m_result.set_error(m_exec->m_runner->m_graph->capture_current_exception());
2989 unblock_waiter(lock);
2992 void abort_with_unexpected_exception(graph::executor* executor, std::exception_ptr exception)
override
2994 this->m_exec = executor;
2996 std::unique_lock lock(m_mutex);
2997 if (!m_result.has_result())
2999 m_result.set_error(error_result::unexpected(exception));
3002 assert(m_node !=
nullptr);
3003 m_exec->m_runner->m_graph->template abort_query_node_with_unexpected_exception< query_handler_output_t< HandlerSpec > >(m_node, executor, exception);
3006 auto unblock_waiter(std::unique_lock< std::mutex >& lock)
3008 if (m_waiter.has_value())
3010 auto resumption = m_waiter.value();
3012 m_exec->check_legal_access();
3013 m_exec->declare_runnable(resumption,
false);
3019 template <
typename Ret2 >
3020 auto await_transform(
typename coroutine< HandlerSpec >::template cosubroutine< Ret2 >&& sub)
3022 assert(m_node !=
nullptr);
3023 sub.m_promise->m_node = m_node;
3024 sub.m_promise->m_handler_entry = m_handler_entry;
3025 m_exec->declare_runnable(std::coroutine_handle<
typename coroutine< HandlerSpec >::template cosubroutine< Ret2 >::promise_type >::from_promise(*sub.m_promise), sub.m_promise,
false);
3026 return typename coroutine< HandlerSpec >::template cosubroutine< Ret2 >::awaitable(sub.m_promise);
3029 template < query_spec_c QuerySpec >
3030 auto await_transform(request< QuerySpec > a) -> query_result< typename QuerySpec::output_type >
3032 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< QuerySpec >(),
"Making a request to a query which is not listed in the HandlerSpec::dependencies");
3033 assert(m_exec !=
nullptr);
3034 assert(m_node !=
nullptr);
3036 typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec >& handler_impl = *std::any_cast< std::shared_ptr< typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec > >& >(m_handler_entry->m_query_handler_impl);
3038 typename graph::template query_descriptor< QuerySpec >& dep_query_descriptor = handler_impl.template get_dependency_descriptor< QuerySpec >();
3040 query_result< typename QuerySpec::output_type > await_obj = dep_query_descriptor.m_get(m_exec, a.m_input,
false);
3042 graph::query_node* await_node = await_obj.m_node;
3043 graph::declare_dependency(m_node, await_node);
3047 template < subquery_spec_c SubquerySpec >
3048 auto await_transform(subquery_request< SubquerySpec > a) -> subquery_query_result< SubquerySpec >
3050 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< SubquerySpec >(),
"Making a subquery request requires listing the subquery in query_handler_dependencies_t< HandlerSpec >");
3051 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< typename SubquerySpec::parent_query >(),
"Making a subquery request requires listing the subquery parent query in query_handler_dependencies_t< HandlerSpec >");
3052 assert(m_exec !=
nullptr);
3053 assert(m_node !=
nullptr);
3055 typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec >& handler_impl = *std::any_cast< std::shared_ptr< typename graph::template query_handler_descriptor_memoized_impl< HandlerSpec > >& >(m_handler_entry->m_query_handler_impl);
3057 typename graph::template query_descriptor< typename SubquerySpec::parent_query >& parent_query_descriptor = handler_impl.template get_subquery_parent_descriptor< SubquerySpec >();
3059 query_result< typename SubquerySpec::parent_query::output_type > parent_await_obj = parent_query_descriptor.m_get(m_exec, a.m_parent_input,
false);
3061 graph::query_node* parent_node = parent_await_obj.m_node;
3062 graph::declare_dependency(m_node, parent_node);
3063 return m_exec->m_runner->m_graph->template get_subquery_result< SubquerySpec >(m_exec, parent_node, a.m_input);
3066 template < subquery_spec_c SubquerySpec >
3067 auto yield_value(subquery_result< SubquerySpec > res)
3069 static_assert(query_handler_produced_subqueries_t< HandlerSpec >::template contains< SubquerySpec >(),
"Yielding a subquery result requires listing the subquery in query_handler_produced_subqueries_t< HandlerSpec >");
3070 static_assert(std::same_as< typename SubquerySpec::parent_query, query_handler_query_t< HandlerSpec > >,
"A handler can only yield subqueries whose parent_query is the handler query");
3071 assert(m_exec !=
nullptr);
3072 assert(m_node !=
nullptr);
3074 m_exec->m_runner->m_graph->template set_subquery_result< SubquerySpec >(m_exec, m_node, std::move(res.m_input), std::move(res.m_output));
3075 return std::suspend_never{};
3080 assert(m_srt ==
nullptr);
3086 promise_type* m_promise;
3089 awaitable(promise_type* promise) : m_promise(promise)
3095 std::unique_lock lck(m_promise->m_mutex);
3096 return await_ready(lck);
3099 bool await_ready([[maybe_unused]] std::unique_lock< std::mutex >& lock)
noexcept
3101 assert(m_promise !=
nullptr);
3102 return m_promise->m_result.has_result();
3107 std::unique_lock lck(m_promise->m_mutex);
3108 assert(m_promise !=
nullptr);
3109 assert(m_promise->m_result.has_result());
3110 m_promise->m_result.test();
3113 template <
typename U >
3114 void await_suspend(std::coroutine_handle< U > h)
3116 await_suspend_helper< typename U::coroutine_type >(h.promise(), h);
3119 template <
typename C >
3120 void await_suspend_helper(
typename C::promise_type& p, std::coroutine_handle< typename C::promise_type > h)
3122 assert(m_promise !=
nullptr);
3124 auto current_executor = p.m_exec;
3125 assert(current_executor !=
nullptr);
3126 current_executor->check_legal_access();
3128 graph::resumption r{.m_promise = &h.promise(), .handle = h};
3129 std::unique_lock lck(m_promise->m_mutex);
3131 if (await_ready(lck))
3133 p.m_exec->declare_runnable(r,
false);
3137 assert(m_promise->m_waiter.has_value() ==
false);
3138 m_promise->m_waiter = r;
3144 promise_type* m_promise;
3147 cosubroutine_impl(promise_type* p) : m_promise(p)
3149 assert(p->m_srt ==
nullptr);
3153 cosubroutine_impl(
const cosubroutine_impl&) =
delete;
3154 cosubroutine_impl& operator=(
const cosubroutine_impl&) =
delete;
3156 ~cosubroutine_impl()
3160 std::unique_lock lock(m_promise->m_run_mutex);
3163 m_promise->m_srt =
nullptr;
3164 std::coroutine_handle< promise_type >::from_promise(*m_promise).destroy();
Indicates that a handler completed without a continuation outcome.
Owns handler registrations, memoized nodes, and query execution.
graph(std::chrono::nanoseconds long_running_node_threshold=std::chrono::milliseconds(100))
Construct an empty graph with built-in subquery errors registered.
bool node_debug_io_capture_enabled() const noexcept
Return whether human-readable node input/output capture is enabled.
void register_handler_singleton(query_handler_output_t< HandlerSpec > value)
Register a constant result for a handler with monostate input.
void register_binary_descriptors()
Register serialization operations and a type signature.
void register_handler_map(std::map< query_handler_input_t< QuerySpec >, query_handler_output_t< QuerySpec > > handler_map)
Register a finite map as a query handler.
void register_canonical_error()
Register an exception type for canonical capture and dumping.
void register_handler_singleton(typename QuerySpec::output_type value)
Register a constant result directly from a query spec.
auto make_subquery_request(typename SubquerySpec::parent_query::input_type parent_input, typename SubquerySpec::input_type input) -> typename SubquerySpec::output_type
Execute or reuse a subquery request from an external thread.
void for_each_marshaled_type_descriptor(Visitor visitor)
Visit the serialization and text descriptors of registered types.
void register_handler_function(Handler h)
Register the coroutine function implementing a query.
void register_inputoutput_text_descriptor()
Register the diagnostic text conversion for a value type.
std::vector< std::byte > marshall()
Serialize a snapshot of every memoized node.
auto make_request(typename QuerySpec::input_type input) -> typename QuerySpec::output_type
Execute or reuse a top-level query and return its value.
void bind_handlers()
Resolve every registered handler's declared dependencies.
graph_data dump()
Snapshot every currently memoized query node.
auto dump_query_to_file(std::filesystem::path output_path, typename QuerySpec::input_type input) -> std::filesystem::path
Evaluate a root query and write its reachable graph to disk.
Indicates that graph execution quiesced before a request completed.
subquery_does_not_exist()
Construct an empty instance for deserialization.
subquery_does_not_exist(std::string subquery_id)
Construct an error naming the missing subquery.
void post_serialize()
Rebuild the derived human-readable message after deserialization.
std::string const & subquery_id() const noexcept
Return the stable identifier of the missing subquery.
char const * what() const noexcept override
Return the derived error message.
std::string const & subquery_id() const noexcept
Return the stable identifier of the affected subquery.
subquery_parent_failed(std::string subquery_id)
Construct an error naming the affected subquery.
Discriminated error state used by query and subquery results.
std::exception_ptr make_exception_ptr() const
Materialize either error representation as an exception pointer.
std::exception_ptr unexpected_exception() const
Return the unexpected exception pointer, if present.
status result_status() const
Return the dump status represented by this error state.
std::string message() const
Return the error message, or an empty string for no error.
bool has_error() const
Return whether either error representation is present.
std::optional< canonical_error_result_data > canonical_dump() const
Return serializable data only when the error is canonical.
static error_result canonical(canonical_error_storage error)
Construct an error state from canonical storage.
bool has_unexpected_exception() const
Return whether an unexpected exception is present.
static error_result unexpected(std::exception_ptr exception)
Construct an error state from an unexpected exception.
void throw_error() const
Rethrow the stored error; do nothing when the state is empty.
bool has_canonical_error() const
Return whether a serializable canonical error is present.
error_result()=default
Construct an empty error state.
void get() const
Verify successful completion or throw the stored error.
result()=default
Construct a pending result.
bool has_exception() const
Compatibility spelling for has_error().
status result_status() const
Return the status used when serializing this result.
void test() const
Rethrow the stored error, if one exists.
bool has_result() const
Return whether evaluation produced success or an error.
void set_exception(std::exception_ptr exception)
Compatibility spelling for set_error(exception).
void set_value()
Replace any prior state with successful completion.
bool has_canonical_error() const
Return whether the result contains a canonical error.
bool has_unexpected_exception() const
Return whether the result contains an unexpected exception.
void set_error(error_result error)
Replace any prior state with error.
error_result const & get_error_result() const
Return the stored error state without throwing.
bool has_value() const
Return whether evaluation completed successfully.
void set_error(std::exception_ptr exception)
Replace any prior state with an unexpected exception.
std::exception_ptr get_error() const
Materialize the stored error as an exception pointer.
void value() const
Alias for get().
bool has_error() const
Return whether evaluation completed with an error.
result(std::exception_ptr exception)
Construct a failed result from an unexpected exception.
Internal value-or-error state shared by query awaitables and dumps.
T & get() &
Return the value or throw the stored error.
bool has_exception() const
Compatibility spelling for has_error().
void set_error(error_result error)
Replace any prior state with error.
void set_error(std::exception_ptr exception)
Replace any prior state with an unexpected exception.
T && get() &&
Return the value or throw the stored error.
status result_status() const
Return the status used when serializing this result.
T const & value() const &
Alias for get() preserving the value category.
void set_exception(std::exception_ptr exception)
Compatibility spelling for set_error(exception).
result()=default
Construct a pending result.
T const & get() const &
Return the value or throw the stored error.
bool has_unexpected_exception() const
Return whether the result contains an unexpected exception.
bool has_value() const
Return whether evaluation completed successfully.
T const && value() const &&
Alias for get() preserving the value category.
void test() const
Rethrow the stored error, if one exists.
T & value() &
Alias for get() preserving the value category.
void set_value(T value)
Replace any prior state with a completed value.
error_result const & get_error_result() const
Return the stored error state without throwing.
T && value() &&
Alias for get() preserving the value category.
bool has_canonical_error() const
Return whether the result contains a canonical error.
std::exception_ptr get_error() const
Materialize the stored error as an exception pointer.
bool has_result() const
Return whether evaluation produced either a value or an error.
bool has_error() const
Return whether evaluation completed with an error.
result(std::exception_ptr exception)
Construct a failed result from an unexpected exception.
result(T value)
Construct a completed result from value.
T const && get() const &&
Return the value or throw the stored error.
Identifies handler specs that explicitly list produced subqueries.
Identifies a complete query handler specification.
Identifies the required structural portion of a handler spec.
Identifies a type that describes a memoized query.
Identifies a result class produced within a parent query node.
Detects a value-provided to_debug_string() operation.
Serializable data model for QueryGraph diagnostic dumps.
std::array< std::byte, 16 > tsig
Fixed-size RPNXSerialization type-signature digest.
status
Terminal or in-progress state recorded for a dumped result.
Default hashing customization point for memoized query inputs.
std::condition_variable fast_cv
Condition variable compatible with fast_mutex.
std::mutex fast_mutex
Platform-selected mutex used by scheduler queues.
typename query_handler_query_t< HandlerSpec >::output_type query_handler_output_t
Output type of the query implemented by HandlerSpec.
std::string format_debug_message_with_source(debug_message const &msg)
Prefix a diagnostic message with its source file and line.
typename query_handler_query_t< HandlerSpec >::input_type query_handler_input_t
Input type of the query implemented by HandlerSpec.
typename query_handler_traits< HandlerSpec >::query query_handler_query_t
Normalized query type implemented by HandlerSpec.
typename query_handler_traits< HandlerSpec >::produced_subqueries query_handler_produced_subqueries_t
Subquery result classes that HandlerSpec may yield.
memory_class
Coarse estimate of a query node's peak memory requirement.
std::shared_mutex shared_mutex
Shared mutex type used by query result awaitables.
typename query_handler_traits< HandlerSpec >::dependencies query_handler_dependencies_t
Declared query and subquery dependencies of HandlerSpec.
canonical_error_storage make_canonical_error_storage(Error const &error)
Capture a concrete exception in canonical type-erased storage.
static std::vector< std::byte > serialize_to_binary(std::monostate const &)
Return the unique empty encoding of a monostate value.
Binary serialization customization point for dumpable values.
static std::vector< std::byte > serialize_to_binary(T const &value)
Serialize one value into a byte vector.
Serializable representation of a registered canonical error.
Type-erased, copyable storage for a registered exception value.
std::function< void(std::any const &) > m_throw_copy
Operation that throws a copy of m_error_value.
void throw_copy() const
Throw a copy of the stored concrete exception.
std::any m_error_value
Type-erased concrete exception value.
tsig m_error_typesig
Type signature of the concrete exception type.
std::function< std::string(std::any const &) > m_message
Operation that obtains the concrete exception message.
canonical_error_result_data dump() const
Convert the error to its serializable dump representation.
std::string message() const
Return the stored exception's human-readable message.
std::function< std::vector< std::byte >(std::any const &) > m_serialize
Operation that serializes m_error_value.
std::exception_ptr make_exception_ptr() const
Return an exception_ptr holding a copy of the error.
Owning coroutine return object for a registered query handler.
HandlerSpec spec_type
Handler specification associated with this coroutine.
cosubroutine_impl< HandlerSpec, Ret > cosubroutine
Coroutine-aware, non-memoized sub-operation return type.
Coroutine yield value that schedules a dependency without waiting.
dependency(request< QuerySpec > request)
Wrap a request for use with co_yield.
request< QuerySpec > m_request
Request to schedule without immediately awaiting it.
Awaitable request for a memoized query value.
QuerySpec::input_type m_input
Input value used to locate or create the requested node.
request(typename QuerySpec::input_type input)
Construct a request by taking ownership of its input.
Awaitable request for a subquery value produced by a parent node.
subquery_request(typename SubquerySpec::parent_query::input_type parent_input, typename SubquerySpec::input_type input)
Construct a request scoped to one parent input.
SubquerySpec::parent_query::input_type m_parent_input
Input identifying the parent query node.
SubquerySpec::input_type m_input
Input identifying the subquery slot within the parent.
Coroutine yield value that publishes one subquery result.
subquery_result(typename SubquerySpec::input_type input, typename SubquerySpec::output_type output)
Construct a subquery input/output pair to publish.
SubquerySpec::input_type m_input
Input identifying the produced subquery slot.
SubquerySpec::output_type m_output
Output to publish for the identified slot.
A diagnostic emitted by a running query handler.
debug_message(std::format_string< Ts... > format_string, Ts &&... args)
Construct and format a diagnostic message.
std::string message
Already-formatted diagnostic text.
debug_message(debug_message const &other)=default
Copy a message and its recorded location.
std::source_location location
Source location assigned at the handler's co_yield site.
static std::string to_debug_string(std::monostate const &)
Return the literal std::monostate{}.
static std::string to_debug_string(std::string const &value)
Return the C++ debug-format representation of value.
Human-readable formatting customization point for query values.
static std::string to_debug_string(T const &value)
Convert value to diagnostic text.
Complete serializable snapshot of a QueryGraph instance.
Boolean trait equivalent of query_handler_spec_c.
typename T::produced_subqueries produced_subqueries
Subquery specifications the handler may publish.
Supplies an empty produced-subquery list when a handler omits it.
rpnx::typelist<> produced_subqueries
Default empty list used when T has no declaration.
typename T::query query
Query specification implemented by the handler.
typename query_handler_produced_subquery_traits< T >::produced_subqueries produced_subqueries
Declared produced subqueries, or an empty typelist.
typename T::dependencies dependencies
Declared query and subquery dependencies.
Normalizes the nested types of a handler specification.