RPNX::QueryGraph
Typed, memoized, concurrent query evaluation for C++23
Loading...
Searching...
No Matches
querygraph.hpp
Go to the documentation of this file.
1// Copyright (c) 2025 Ryan P. Nicholl <rnicholl@protonmail.com>
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#ifndef NODEGRAPH2_NODEGRAPH_HPP
15#define NODEGRAPH2_NODEGRAPH_HPP
16
25
27#include "rpnx/result.hpp"
28#include "rpnx/typelist.hpp"
29
30#include <algorithm>
31#include <any>
32#include <array>
33#include <atomic>
34#include <cassert>
35#include <chrono>
36#include <condition_variable>
37#include <concepts>
38#include <coroutine>
39#include <cstddef>
40#include <deque>
41#include <exception>
42#include <filesystem>
43#include <format>
44#include <fstream>
45#include <functional>
46#include <iostream>
47#include <iterator>
48#include <map>
49#include <memory>
50#include <mutex>
51#include <optional>
52#include <print>
53#include <set>
54#include <shared_mutex>
55#include <source_location>
56#include <stdexcept>
57#include <string>
58#include <thread>
59#include <tuple>
60#include <type_traits>
61#include <typeindex>
62#include <variant>
63#include <vector>
64
67#include "rpnx/serialization4.hpp"
68
69#ifdef RPNX_QUERYGRAPH_USE_CONC_UNORDERED_MAP
70#include "rpnx/sharded_unordered_map.hpp"
71#endif
72
73#ifdef NDEBUG
74#define RPNX_DEBUGONLY(x)
75#define RPNX_RELEASEONLY(x) x
76#else
77#define RPNX_DEBUGONLY(x) x
78#define RPNX_RELEASEONLY(x)
79#endif
80
81
82// TODO: Attach estimated memory class to node spec, to allow for scheduling nodes.
83RPNX_ENUM(rpnx::querygraph, memory_class, std::uint8_t, unknown, tiny, small, medium, large, huge);
84
100
101
102namespace rpnx::querygraph
103{
105 using shared_mutex = std::shared_mutex;
106
107 // Win32 std::mutex is rather slow, std::shared_mutex was added later
108 // and is generally faster. However, the improvements couldn't be backported to std::mutex
109 // because it would break ABI compatibility.
110 // On most other platforms, std::mutex is generally faster than std::shared_mutex for exclusive locking.
111#ifdef _WIN32
113 using fast_mutex = std::shared_mutex;
115 using fast_cv = std::condition_variable_any;
116#else
118 using fast_mutex = std::mutex;
120 using fast_cv = std::condition_variable;
121#endif
122
132 template < typename T >
133 concept query_spec_c = requires {
134 typename T::input_type;
135 typename T::output_type;
136 { T::query_id } -> std::convertible_to< std::string >;
137 };
138
148 template < typename T >
149 concept subquery_spec_c = requires {
150 typename T::parent_query;
151 typename T::input_type;
152 typename T::output_type;
154 { T::subquery_id } -> std::convertible_to< std::string >;
155 };
156
157 namespace detail
158 {
159 template < query_spec_c QuerySpec, rpnx::typelist_c Deps, rpnx::typelist_c ProducedSubqueries = rpnx::typelist<> >
160 struct query_handler_spec
161 {
162 using query = QuerySpec;
163 using dependencies = Deps;
164 using produced_subqueries = ProducedSubqueries;
165 };
166 } // namespace detail
167
174 template < typename T >
175 concept query_handler_spec_struct_c = requires {
176 typename T::query;
177 typename T::dependencies;
178 } && query_spec_c< typename T::query > && rpnx::typelist_c< typename T::dependencies >;
179
181 template < typename T >
183 typename T::produced_subqueries;
184 } && rpnx::typelist_c< typename T::produced_subqueries >;
185
190 template < typename T >
192
194 template < typename T >
196
201 template < typename T >
203 {
205 using produced_subqueries = rpnx::typelist<>;
206 };
207
209 template < query_handler_produced_subqueries_struct_c T >
211 {
213 using produced_subqueries = typename T::produced_subqueries;
214 };
215
217 template < query_handler_spec_struct_c T >
219 {
221 using query = typename T::query;
223 using dependencies = typename T::dependencies;
226 };
227
229 template < query_handler_spec_c HandlerSpec >
231
233 template < query_handler_spec_c HandlerSpec >
235
237 template < query_handler_spec_c HandlerSpec >
239
241 template < query_handler_spec_c HandlerSpec >
243
245 template < query_handler_spec_c HandlerSpec >
247
249 template < typename T >
250 struct is_query_handler_spec : std::bool_constant< query_handler_spec_c< T > >
251 {
252 };
253
264 template < typename T >
266 {
272 static std::vector< std::byte > serialize_to_binary(T const& value)
273 {
274 std::vector< std::byte > buffer;
275 rpnx::serial4::serialize_iter(value, std::back_inserter(buffer));
276 return buffer;
277 }
278 };
279
280 template <>
282 struct binary_traits< std::monostate >
283 {
285 static std::vector< std::byte > serialize_to_binary(std::monostate const&)
286 {
287 return {};
288 }
289 };
290
295 template < typename T >
296 concept type_with_to_debug_string = requires(T const& t) {
297 { t.to_debug_string() } -> std::convertible_to< std::string >;
298 };
299
309 template < typename T >
311 {
313 static std::string to_debug_string(T const& value)
314 {
315 if constexpr (type_with_to_debug_string< T >)
316 {
317 return value.to_debug_string();
318 }
319 else if constexpr (std::formattable< T, char >)
320 {
321 return std::format("{}", value);
322 }
323 else
324 {
325 return "<no debug string available>";
326 }
327 }
328 };
329
330 template <>
332 struct debug_traits< std::string >
333 {
335 static std::string to_debug_string(std::string const& value)
336 {
337 return std::format("{:?}", value);
338 }
339 };
340
341 template <>
343 struct debug_traits< std::monostate >
344 {
346 static std::string to_debug_string(std::monostate const&)
347 {
348 return "std::monostate{}";
349 }
350 };
351
360 {
362 std::string message;
364 std::source_location location;
365
366 public:
372 template < typename... Ts >
373 debug_message(std::format_string< Ts... > format_string, Ts&&... args) : message(std::format(format_string, std::forward< Ts >(args)...))
374 {
375 }
376
378 debug_message(debug_message const& other) = default;
379 };
380
388
397 {
403 std::function< void(std::any const&) > m_throw_copy;
405 std::function< std::vector< std::byte >(std::any const&) > m_serialize;
407 std::function< std::string(std::any const&) > m_message;
408
410 void throw_copy() const;
411
413 std::exception_ptr make_exception_ptr() const;
414
416 std::string message() const;
417
420 };
421
428 template < typename Error >
430 {
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");
433
435 storage.m_error_typesig = rpnx::serial4::get_type_typesig_hash16< Error >();
436 storage.m_error_value.emplace< Error >(error);
437 storage.m_throw_copy = [](std::any const& value)
438 {
439 throw std::any_cast< Error const& >(value);
440 };
441 storage.m_serialize = [](std::any const& value)
442 {
443 return binary_traits< Error >::serialize_to_binary(std::any_cast< Error const& >(value));
444 };
445 storage.m_message = [](std::any const& value)
446 {
447 return std::string(std::any_cast< Error const& >(value).what());
448 };
449 return storage;
450 }
451
460 {
461 std::optional< canonical_error_storage > m_canonical_error;
462 std::exception_ptr m_unexpected_exception;
463
464 public:
466 error_result() = default;
467
470
472 static error_result unexpected(std::exception_ptr exception);
473
475 bool has_error() const;
476
479
482
485
487 std::exception_ptr unexpected_exception() const;
488
490 std::exception_ptr make_exception_ptr() const;
491
493 void throw_error() const;
494
496 std::string message() const;
497
499 std::optional< canonical_error_result_data > canonical_dump() const;
500 };
501
511 template < typename T >
512 class result
513 {
514 std::optional< T > m_value;
515 error_result m_error;
516
517 public:
519 result() = default;
520
522 result(T value) : m_value(std::move(value))
523 {
524 }
525
527 result(std::exception_ptr exception) : m_error(error_result::unexpected(exception))
528 {
529 }
530
532 void test() const
533 {
534 if (m_error.has_error())
535 {
536 m_error.throw_error();
537 }
538 }
539
541 T& get() &
542 {
543 test();
544 assert(m_value.has_value());
545 return m_value.value();
546 }
547
549 T const& get() const&
550 {
551 test();
552 assert(m_value.has_value());
553 return m_value.value();
554 }
555
557 T&& get() &&
558 {
559 test();
560 assert(m_value.has_value());
561 return std::move(m_value.value());
562 }
563
565 T const&& get() const&&
566 {
567 test();
568 assert(m_value.has_value());
569 return std::move(m_value.value());
570 }
571
573 T& value() &
574 {
575 return get();
576 }
577
579 T const& value() const&
580 {
581 return get();
582 }
583
585 T&& value() &&
586 {
587 return std::move(*this).get();
588 }
589
591 T const&& value() const&&
592 {
593 return std::move(*this).get();
594 }
595
598 {
599 m_error = error_result();
600 m_value = std::move(value);
601 }
602
605 {
606 m_value.reset();
607 m_error = std::move(error);
608 }
609
611 void set_error(std::exception_ptr exception)
612 {
614 }
615
617 void set_exception(std::exception_ptr exception)
618 {
619 set_error(exception);
620 }
621
623 bool has_result() const
624 {
625 return m_value.has_value() || m_error.has_error();
626 }
627
629 bool has_value() const
630 {
631 return m_value.has_value();
632 }
633
635 bool has_error() const
636 {
637 return m_error.has_error();
638 }
639
641 bool has_exception() const
642 {
643 return has_error();
644 }
645
648 {
649 return m_error.has_canonical_error();
650 }
651
654 {
655 return m_error.has_unexpected_exception();
656 }
657
660 {
661 return m_error;
662 }
663
665 std::exception_ptr get_error() const
666 {
667 return m_error.make_exception_ptr();
668 }
669
672 {
673 if (m_value.has_value())
674 {
675 return status::completed;
676 }
677 return m_error.result_status();
678 }
679
681 explicit operator bool() const
682 {
683 return has_result();
684 }
685 };
686
688 template <>
689 class result< void >
690 {
691 bool m_has_value = false;
692 error_result m_error;
693
694 public:
696 result() = default;
697
699 result(std::exception_ptr exception) : m_error(error_result::unexpected(exception))
700 {
701 }
702
704 void test() const
705 {
706 if (m_error.has_error())
707 {
708 m_error.throw_error();
709 }
710 }
711
713 void get() const
714 {
715 test();
716 if (!m_has_value)
717 {
718 throw std::logic_error("No value");
719 }
720 }
721
723 void value() const
724 {
725 get();
726 }
727
730 {
731 m_error = error_result();
732 m_has_value = true;
733 }
734
737 {
738 m_has_value = false;
739 m_error = std::move(error);
740 }
741
743 void set_error(std::exception_ptr exception)
744 {
746 }
747
749 void set_exception(std::exception_ptr exception)
750 {
751 set_error(exception);
752 }
753
755 bool has_result() const
756 {
757 return m_has_value || m_error.has_error();
758 }
759
761 bool has_value() const
762 {
763 return m_has_value;
764 }
765
767 bool has_error() const
768 {
769 return m_error.has_error();
770 }
771
773 bool has_exception() const
774 {
775 return has_error();
776 }
777
780 {
781 return m_error.has_canonical_error();
782 }
783
786 {
787 return m_error.has_unexpected_exception();
788 }
789
792 {
793 return m_error;
794 }
795
797 std::exception_ptr get_error() const
798 {
799 return m_error.make_exception_ptr();
800 }
801
804 {
805 if (m_has_value)
806 {
807 return status::completed;
808 }
809 return m_error.result_status();
810 }
811
813 explicit operator bool() const
814 {
815 return has_result();
816 }
817 };
818
819 inline namespace RPNX_DEBUGONLY(debug_abi) RPNX_RELEASEONLY(release_abi)
820 {
821 class graph;
822
833 template < query_spec_c QuerySpec >
834 struct request
835 {
837 typename QuerySpec::input_type m_input;
838
839 public:
841 request(typename QuerySpec::input_type input) : m_input(std::move(input))
842 {
843 }
844 };
845
854 template < query_spec_c QuerySpec >
856 {
859
860 public:
865 };
866
876 template < subquery_spec_c SubquerySpec >
878 {
880 typename SubquerySpec::parent_query::input_type m_parent_input;
882 typename SubquerySpec::input_type m_input;
883
884 public:
890 subquery_request(typename SubquerySpec::parent_query::input_type parent_input, typename SubquerySpec::input_type input) : m_parent_input(std::move(parent_input)), m_input(std::move(input))
891 {
892 }
893 };
894
904 template < subquery_spec_c SubquerySpec >
906 {
908 typename SubquerySpec::input_type m_input;
910 typename SubquerySpec::output_type m_output;
911
912 public:
914 subquery_result(typename SubquerySpec::input_type input, typename SubquerySpec::output_type output) : m_input(std::move(input)), m_output(std::move(output))
915 {
916 }
917 };
918
925 class subquery_does_not_exist : public std::logic_error
926 {
927 std::string m_subquery_id;
928 std::string m_message;
929
930 public:
931 RPNX_MEMBER_METADATA(subquery_does_not_exist, m_subquery_id);
932
934 subquery_does_not_exist() : std::logic_error("")
935 {
937 }
938
940 explicit subquery_does_not_exist(std::string subquery_id) : std::logic_error(""), m_subquery_id(std::move(subquery_id))
941 {
943 }
944
946 std::string const& subquery_id() const noexcept
947 {
948 return m_subquery_id;
949 }
950
953 {
954 m_message = std::format("Subquery does not exist: {}", m_subquery_id);
955 }
956
958 char const* what() const noexcept override
959 {
960 return m_message.c_str();
961 }
962 };
963
970 class subquery_parent_failed : public std::runtime_error
971 {
972 std::string m_subquery_id;
973
974 public:
975 RPNX_MEMBER_METADATA(subquery_parent_failed, m_subquery_id);
976
978 explicit subquery_parent_failed(std::string subquery_id) : std::runtime_error(std::format("Parent query failed before producing subquery: {}", subquery_id)), m_subquery_id(std::move(subquery_id))
979 {
980 }
981
983 std::string const& subquery_id() const noexcept
984 {
985 return m_subquery_id;
986 }
987 };
988
995 class recursive_dependency_error : public std::logic_error
996 {
997 public:
998 recursive_dependency_error() : std::logic_error("recursive dependency")
999 {
1000 }
1001 };
1002
1009 class bad_continuation : public std::logic_error
1010 {
1011 public:
1012 bad_continuation() : std::logic_error("no continuation")
1013 {
1014 }
1015 };
1016
1017 template < typename Result >
1018 struct query_result;
1019
1020 template < subquery_spec_c SubquerySpec >
1021 struct subquery_query_result;
1022
1023 template < query_handler_spec_c HandlerSpec >
1024 struct coroutine;
1025
1038 class graph
1039 {
1040
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;
1045
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);
1048
1049 public:
1057 explicit graph(std::chrono::nanoseconds long_running_node_threshold = std::chrono::milliseconds(100));
1058
1060 struct promise_base;
1061 struct query_node;
1062
1063 struct resumption
1064 {
1065 promise_base* m_promise = nullptr;
1066 std::coroutine_handle< void > handle;
1067 };
1068
1069 struct runner;
1070
1071 struct alignas(std::hardware_destructive_interference_size) executor
1072 {
1073 runner* m_runner = nullptr;
1074 RPNX_DEBUGONLY(std::thread::id m_id);
1075 fast_mutex m_mutex;
1076 std::deque< resumption > m_runnables;
1077 RPNX_DEBUGONLY(bool m_idle = false;)
1078
1079 static constexpr std::size_t workgroup_threshold = 128;
1080
1081 executor(runner* runner);
1082
1083 executor(executor const& other) = delete;
1084
1085 void check_legal_access();
1086
1087 void run();
1088
1089 void declare_runnable(std::coroutine_handle< void > h, promise_base* ptr, bool immediate);
1090
1091 void declare_runnable(resumption r, bool immediate);
1092
1093 bool try_exec_one();
1094
1095 std::vector< resumption > try_steal(std::size_t max_items);
1096 };
1097
1098 struct runner
1099 {
1100 static std::size_t default_executor_count();
1101
1105 std::size_t global_workitems_to_publish(std::size_t available_items) const;
1106
1110 std::size_t global_workitems_to_take(std::size_t queued_work) const;
1111
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;
1123 fast_mutex m_mutex;
1124 std::deque< executor > m_executors;
1125 std::vector< std::thread > m_executor_threads;
1126 std::deque< resumption > m_runnables;
1127 fast_cv m_runner_cv;
1128
1129 static constexpr std::size_t taskgroup_max_size = 1024;
1130
1131 runner(graph* g, std::size_t total_executors = default_executor_count(), std::size_t global_workitem_threshold = 0);
1132
1133 executor* initial_executor();
1134
1135 void run();
1136
1137 void push_group(std::vector< resumption > vals);
1138
1139 void try_steal_work();
1140
1141 std::vector< resumption > get_work(executor* ex);
1142 };
1144
1163 template < query_spec_c QuerySpec >
1164 auto make_request(typename QuerySpec::input_type input) -> typename QuerySpec::output_type
1165 {
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())
1170 {
1171 runner.run();
1172 if (!query_result.available())
1173 {
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()));
1175 }
1176 }
1177 query_result.wait();
1178 return query_result.await_resume();
1179 }
1180
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
1197 {
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())
1204 {
1205 runner.run();
1206 if (!query_result.available())
1207 {
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()));
1209 }
1210 }
1211 query_result.wait();
1212 return query_result.await_resume();
1213 }
1214
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
1231 {
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())
1236 {
1237 runner.run();
1238 if (!query_result.available())
1239 {
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()));
1241 }
1242 }
1243 query_result.wait();
1244
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);
1247 if (!out)
1248 {
1249 throw std::runtime_error(std::format("Failed to open dump file for writing: {}", output_path.string()));
1250 }
1251
1252 if (!marshaled_dump.empty())
1253 {
1254 out.write(reinterpret_cast< char const* >(marshaled_dump.data()), static_cast< std::streamsize >(marshaled_dump.size()));
1255 }
1256 if (!out)
1257 {
1258 throw std::runtime_error(std::format("Failed to write dump file: {}", output_path.string()));
1259 }
1260
1261 return output_path;
1262 }
1263
1264 // struct handler_spec_typeinfo
1265 // {
1266 // std::type_index question_type;
1267 // std::vector< std::type_index > dependencies;
1268 // };
1269
1270 // bindable_type_descriptor is a runtime type descriptor
1271 // for types that can be used as inputs or outputs
1272 // to queries.
1273 // struct bindable_type_descriptor
1274 // {
1275 // std::function< std::vector< std::byte >(std::any const&) > serialize_binary;
1276 // std::function< void(std::any&, std::vector< std::byte > const&) > deserialize_binary;
1277 // std::function< std::string(std::any const&) > to_string;
1278 // };
1279
1280 // struct handler_entry_any
1281 // {
1282 // std::type_index type;
1283 // std::any handler;
1284 // };
1285
1287 template < subquery_spec_c SubquerySpec >
1288 struct subquery_slot
1289 {
1290 std::any m_input;
1291 std::any m_result;
1292 std::string m_input_string;
1293 std::string m_output_string;
1294 std::vector< resumption > waiters;
1295 };
1296
1297 template < subquery_spec_c SubquerySpec >
1298 struct subquery_state
1299 {
1300 std::map< typename SubquerySpec::input_type, subquery_slot< SubquerySpec > > m_instances;
1301 };
1302
1303 struct subquery_state_any
1304 {
1305 std::any m_state;
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;
1312 };
1313
1314 struct query_node
1315 {
1316 std::shared_mutex m_mutex;
1317 std::condition_variable_any m_external_cv;
1318
1319 std::type_index m_query_type = typeid(void);
1320 std::string m_input_string;
1321 std::string m_output_string;
1322
1323 // Nodes store a pointer to their own input so the input can be reached from dependency pointers without doing a lookup.
1324 std::any m_input;
1325 // m_result stores the output of the node as a result<T> object where T is the output type.
1326 std::any m_result;
1327 // m_cort is used for lifetime management of the coroutine object.
1328 std::any m_cort;
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;
1338
1339 void unblock_waiters(executor* executor);
1340
1341 void unblock_waiters(executor* executor, std::unique_lock< std::shared_mutex >& lock);
1342
1343 void abort_waiters(executor* executor, std::unique_lock< std::shared_mutex >& lock, std::exception_ptr exception);
1344 };
1345
1346 static void declare_dependency(query_node* dependent, query_node* dependency);
1347
1348 template < query_spec_c HandlerSpec >
1349 struct query_descriptor;
1350
1351 struct promise_base
1352 {
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;
1362 };
1363
1364 template < query_spec_c QuerySpec >
1365 struct query_descriptor
1366 {
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;
1369 };
1370
1371 struct query_descriptor_any
1372 {
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;
1377 };
1378
1379 template < typename T >
1380 struct dependency_descriptor_type;
1381
1382 template < query_spec_c QuerySpec >
1383 struct dependency_descriptor_type< QuerySpec >
1384 {
1385 using type = query_descriptor< QuerySpec >*;
1386 };
1387
1388 template < subquery_spec_c SubquerySpec >
1389 struct dependency_descriptor_type< SubquerySpec >
1390 {
1391 using type = query_descriptor< typename SubquerySpec::parent_query >*;
1392 };
1393
1394 template < typename T >
1395 struct dependency_descriptor_table;
1396
1397 template < typename... Ts >
1398 struct dependency_descriptor_table< rpnx::typelist< Ts... > >
1399 {
1400 using type = std::tuple< typename dependency_descriptor_type< Ts >::type... >;
1401 };
1402
1403 template < rpnx::typelist_c Typelist >
1404 using dependency_descriptor_table_from_typelist = typename dependency_descriptor_table< Typelist >::type;
1405
1406 template < typename T >
1407 std::string datavalue_to_string(T const& input)
1408 {
1409 auto desc = this->m_text_descriptors.get_or_create(std::type_index(typeid(T)),
1410 []()
1411 {
1412 return text_descriptor{};
1413 });
1414
1415 if (desc.m_to_string == nullptr)
1416 {
1417 return "<no to_string available>";
1418 }
1419
1420 return desc.m_to_string(std::any(input), std::any());
1421 }
1422
1423 template < query_handler_spec_c HandlerSpec >
1424 struct query_handler_descriptor_memoized_impl
1425 {
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;
1428#else
1429 std::shared_mutex mutex;
1430 std::map< query_handler_input_t< HandlerSpec >, query_node > cache;
1431#endif
1432
1433 query_descriptor< query_handler_query_t< HandlerSpec > >* m_handler_entry = nullptr;
1434
1435 dependency_descriptor_table_from_typelist< query_handler_dependencies_t< HandlerSpec > > dependency_descriptors = {};
1436
1437 std::function< coroutine< HandlerSpec >(query_handler_input_t< HandlerSpec >) > m_startup;
1438
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)
1441 {
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)
1447 {
1448 // Identify the query type for the node so UI shows a proper name.
1449 node.m_query_type = std::type_index(typeid(query_handler_query_t< HandlerSpec >));
1450
1451 node.m_input.emplace< query_handler_input_t< HandlerSpec > const* >(&key);
1452
1453 if (exec->m_runner->m_graph->m_enable_node_debug_io_capture)
1454 {
1455 node.m_input_string = exec->m_runner->m_graph->datavalue_to_string(input_ask);
1456 }
1457 coroutine< HandlerSpec > co = m_startup(input_ask);
1458 node.m_result = result< query_handler_output_t< HandlerSpec > >();
1459
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));
1465
1466 started = true;
1467 });
1468 if (started)
1469 {
1470 exec->declare_runnable(cr, pbase, immediate);
1471 }
1472
1473 return query_result< query_handler_output_t< HandlerSpec > >(&node, started);
1474 }
1475
1476 void iterate_all(std::function< void(query_node& node) > func)
1477 {
1478 auto iter = m_mt_cache.range_exclusive();
1479 for (auto it = iter.begin(); it != iter.end(); ++it)
1480 {
1481 func(it->second);
1482 }
1483 }
1484#else
1485
1486 query_result< query_handler_output_t< HandlerSpec > > get(executor* exec, query_handler_input_t< HandlerSpec > const& input_ask)
1487 {
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())
1491 {
1492 return query_result< query_handler_output_t< HandlerSpec > >(&it->second, false);
1493 }
1494 lock.unlock();
1495 std::unique_lock ulock(mutex);
1496 // Double-check pattern
1497 it = cache.find(input_ask);
1498 if (it != cache.end())
1499 {
1500 return query_result< query_handler_output_t< HandlerSpec > >(&it->second, false);
1501 }
1502 auto [node_it, inserted] = cache.try_emplace(input_ask);
1503 assert(inserted);
1504 coroutine< HandlerSpec > co = m_startup(input_ask);
1505 query_node& node = node_it->second;
1506 // Identify the query type and record input string (for debug UI) similarly to the concurrent path.
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)
1510 {
1511 node.m_input_string = exec->m_runner->m_graph->datavalue_to_string(input_ask);
1512 }
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));
1519 // ulock.unlock();
1520 exec->declare_runnable(cr, pbase);
1521 return query_result< query_handler_output_t< HandlerSpec > >(&node, true);
1522 }
1523#endif
1524 template < query_spec_c QuerySpec >
1525 auto get_dependency_descriptor() -> query_descriptor< QuerySpec >&
1526 {
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");
1528
1529 constexpr std::size_t index = *query_handler_dependencies_t< HandlerSpec >::template index_of< QuerySpec >();
1530
1531 // Descriptor dependencies should be set during the binding stage, which should occur before
1532 // any queries are executed
1533 return *std::get< index >(dependency_descriptors);
1534 }
1535
1536 template < subquery_spec_c SubquerySpec >
1537 auto get_subquery_parent_descriptor() -> query_descriptor< typename SubquerySpec::parent_query >&
1538 {
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");
1541
1542 constexpr std::size_t index = *query_handler_dependencies_t< HandlerSpec >::template index_of< SubquerySpec >();
1543
1544 return *std::get< index >(dependency_descriptors);
1545 }
1546
1547 void bind(graph* graph)
1548 {
1549 bind_spec< 0 >(graph);
1550 }
1551
1552 template < size_t N >
1553 void bind_spec(graph* graph)
1554 {
1555 if constexpr (N < query_handler_dependencies_t< HandlerSpec >::size)
1556 {
1557 using dep = query_handler_dependencies_t< HandlerSpec >::template type_at< N >;
1558 if constexpr (query_spec_c< dep >)
1559 {
1560 query_descriptor< dep >& dep_descriptor = graph->get_query_descriptor< dep >();
1561 std::get< N >(dependency_descriptors) = &dep_descriptor;
1562 }
1563 else if constexpr (subquery_spec_c< dep >)
1564 {
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;
1568 }
1569 bind_spec< N + 1 >(graph);
1570 }
1571 }
1572 };
1573
1574 struct text_descriptor
1575 {
1576 std::function< std::string(std::any, std::any) > m_to_string;
1577 };
1578
1579 struct binary_descriptor
1580 {
1581 struct result_binary_data
1582 {
1583 std::vector< std::byte > m_output_data;
1584 status m_status = status::pending;
1585 std::optional< canonical_error_result_data > m_error_result;
1586 };
1587
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;
1590
1591 std::function< std::any(std::vector< std::byte > const&) > m_deserialize;
1592
1593 std::array< std::byte, 16 > m_typesig;
1594 };
1595
1596 struct canonical_error_descriptor
1597 {
1598 std::function< error_result(std::exception const&) > m_capture;
1599 };
1600
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;
1608
1610 auto long_running_node_description(promise_base* promise) -> std::string;
1611
1613 void print_long_running_node_progress_diagnostic(promise_base* promise);
1614
1616 void print_long_running_node_completion_diagnostic(promise_base* promise, std::chrono::nanoseconds self_time);
1617
1619 void print_noncanonical_exception_diagnostic(promise_base* promise, error_result const& error);
1620
1621 static void declare_resumptions(executor* executor, std::vector< resumption > waiters);
1622
1623 static void abort_resumptions(executor* executor, std::vector< resumption > waiters, std::exception_ptr exception);
1624
1625 template < subquery_spec_c SubquerySpec >
1626 static std::vector< resumption > take_subquery_waiters(subquery_slot< SubquerySpec >& slot)
1627 {
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();
1631 return waiters;
1632 }
1633
1634 static canonical_error_storage make_subquery_parent_failed_storage(std::string subquery_id, error_result parent_error);
1635
1636 static error_result make_subquery_does_not_exist_error(std::string subquery_id);
1637
1638 static error_result make_subquery_parent_failed_error(std::string subquery_id, error_result const& parent_error);
1639
1640 template < subquery_spec_c SubquerySpec >
1641 static auto make_subquery_state_any() -> subquery_state_any
1642 {
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 >
1649 {
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)
1653 {
1654 result< typename SubquerySpec::output_type >& res = std::any_cast< result< typename SubquerySpec::output_type >& >(slot.m_result);
1655 if (!res.has_result())
1656 {
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()));
1660 }
1661 }
1662 return waiters;
1663 };
1664 state_any.m_finalize_parent_failed = [](std::any& state_value, error_result const& parent_error) -> std::vector< resumption >
1665 {
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)
1669 {
1670 result< typename SubquerySpec::output_type >& res = std::any_cast< result< typename SubquerySpec::output_type >& >(slot.m_result);
1671 if (!res.has_result())
1672 {
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()));
1676 }
1677 }
1678 return waiters;
1679 };
1680 state_any.m_iterate_all = [](std::any& state_value, std::function< void(std::any&, std::any&) > callback)
1681 {
1682 subquery_state< SubquerySpec >& state = std::any_cast< subquery_state< SubquerySpec >& >(state_value);
1683 for (auto& [input, slot] : state.m_instances)
1684 {
1685 callback(slot.m_input, slot.m_result);
1686 }
1687 };
1688 return state_any;
1689 }
1690
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 >&
1693 {
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())
1697 {
1698 auto [inserted_it, inserted] = parent_node->m_subquery_states.emplace(state_key, make_subquery_state_any< SubquerySpec >());
1699 assert(inserted);
1700 state_it = inserted_it;
1701 }
1702
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;
1706 if (inserted)
1707 {
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())
1711 {
1712 slot.m_input_string = datavalue_to_string(input);
1713 }
1714 }
1715 return slot;
1716 }
1717
1718 template < subquery_spec_c SubquerySpec >
1719 std::vector< resumption > complete_subquery_from_parent_result_locked(query_node* parent_node, subquery_slot< SubquerySpec >& slot)
1720 {
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())
1724 {
1725 return waiters;
1726 }
1727
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())
1730 {
1731 subquery_res.set_error(make_subquery_does_not_exist_error(std::string(SubquerySpec::subquery_id)));
1732 waiters = take_subquery_waiters< SubquerySpec >(slot);
1733 }
1734 else if (parent_res.has_exception())
1735 {
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);
1738 }
1739 return waiters;
1740 }
1741
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 >
1744 {
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;
1750 lock.unlock();
1751 if (abort_waiters)
1752 {
1753 abort_resumptions(executor, std::move(waiters), unexpected_exception);
1754 }
1755 else
1756 {
1757 declare_resumptions(executor, std::move(waiters));
1758 }
1759 return subquery_query_result< SubquerySpec >(parent_node, &slot);
1760 }
1761
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)
1764 {
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())
1769 {
1770 throw std::logic_error(std::format("Subquery result already produced: {}", std::string(SubquerySpec::subquery_id)));
1771 }
1772 if (node_debug_io_capture_enabled())
1773 {
1774 slot.m_output_string = datavalue_to_string(output);
1775 }
1776 subquery_res.set_value(std::move(output));
1777 std::vector< resumption > waiters = take_subquery_waiters< SubquerySpec >(slot);
1778 lock.unlock();
1779 parent_node->m_external_cv.notify_all();
1780 declare_resumptions(executor, std::move(waiters));
1781 }
1782
1783 std::vector< resumption > finalize_missing_subqueries_locked(query_node* node);
1784
1785 std::vector< resumption > finalize_parent_failed_subqueries_locked(query_node* node, error_result const& parent_error);
1786
1787 template < subquery_spec_c SubquerySpec >
1788 void register_subquery_descriptors()
1789 {
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 >();
1794 }
1795
1796 template < rpnx::typelist_c Typelist, std::size_t N = 0 >
1797 void register_subquery_descriptors_from_typelist()
1798 {
1799 if constexpr (N < Typelist::size)
1800 {
1801 using dep = typename Typelist::template type_at< N >;
1802 if constexpr (subquery_spec_c< dep >)
1803 {
1804 register_subquery_descriptors< dep >();
1805 }
1806 register_subquery_descriptors_from_typelist< Typelist, N + 1 >();
1807 }
1808 }
1809
1810 std::optional< error_result > capture_subquery_parent_failed(subquery_parent_failed const& exception) const;
1811
1812 error_result capture_exception(std::exception_ptr exception) const;
1813
1814 error_result capture_current_exception() const;
1815
1816 template < typename Output >
1817 void abort_query_node_with_unexpected_exception(query_node* node, executor* executor, std::exception_ptr exception)
1818 {
1819 std::vector< resumption > subquery_waiters;
1820 {
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())
1824 {
1825 return;
1826 }
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);
1830 }
1831 abort_resumptions(executor, std::move(subquery_waiters), exception);
1832 }
1834
1835 public:
1847 template < typename Error >
1849 {
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");
1852
1853 canonical_error_descriptor descriptor;
1854 descriptor.m_capture = [](std::exception const& exception)
1855 {
1856 return error_result::canonical(make_canonical_error_storage(static_cast< Error const& >(exception)));
1857 };
1858 m_canonical_error_descriptors[std::type_index(typeid(Error))] = std::move(descriptor);
1859 }
1860
1870 template < typename T >
1872 {
1873 m_text_descriptors.get_or_init(std::type_index(typeid(T)),
1874 [](text_descriptor& desc)
1875 {
1876 desc.m_to_string = [](std::any val, std::any codec) -> std::string
1877 {
1878 return debug_traits< T >::to_debug_string(std::any_cast< T >(val));
1879 };
1880 });
1881 }
1882
1894 template < typename T >
1896 {
1897 binary_descriptor desc;
1898 desc.m_serialize_from_cptr = [](std::any& val) -> std::vector< std::byte >
1899 {
1900 T const* value = std::any_cast< T const* >(val);
1902 };
1903 desc.m_serialize_from_result = [](std::any& val) -> binary_descriptor::result_binary_data
1904 {
1905 result< T >& res = std::any_cast< result< T >& >(val);
1906 if (res.has_value())
1907 {
1908 std::vector< std::byte > data = binary_traits< T >::serialize_to_binary(res.value());
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,
1913 };
1914 }
1915 if (res.has_exception())
1916 {
1917 std::vector< std::byte > data;
1918 std::string str = "Exception: ";
1919 str += res.get_error_result().message();
1920 for (auto c : str)
1921 {
1922 data.push_back(std::byte(c));
1923 }
1924 return binary_descriptor::result_binary_data{
1925 .m_output_data = std::move(data),
1926 .m_status = res.result_status(),
1927 .m_error_result = res.get_error_result().canonical_dump(),
1928 };
1929 }
1930 return binary_descriptor::result_binary_data{
1931 .m_output_data = {},
1932 .m_status = status::pending,
1933 .m_error_result = std::nullopt,
1934 };
1935 };
1936 desc.m_deserialize = [](std::vector< std::byte > const& data) -> std::any
1937 {
1938 T value{};
1939 rpnx::serial4::deserialize_iter(value, data.begin(), data.end());
1940 return std::any(std::move(value));
1941 };
1942 desc.m_typesig = rpnx::serial4::get_type_typesig_hash16< T >();
1943
1944 m_binary_descriptors[std::type_index(typeid(T))] = desc;
1945 }
1946
1958 template < query_handler_spec_c QuerySpec >
1960 {
1961 // TODO: Validate that this works.
1963 [handler_map = std::make_shared< std::map< query_handler_input_t< QuerySpec >, query_handler_output_t< QuerySpec > > >(std::move(handler_map))](query_handler_input_t< QuerySpec > const& input) -> coroutine< QuerySpec >
1964 {
1965 auto it = handler_map->find(input);
1966 if (it == handler_map->end())
1967 {
1968 throw std::logic_error("No handler found for the given input in handler map");
1969 }
1970 co_return it->second;
1971 });
1972 }
1973
1979 template < query_handler_spec_c HandlerSpec >
1980 requires std::same_as< query_handler_input_t< HandlerSpec >, std::monostate >
1982 {
1983 using output_type = query_handler_output_t< HandlerSpec >;
1984
1986 [value = std::make_shared< output_type >(std::move(value))](std::monostate const&) -> coroutine< HandlerSpec >
1987 {
1988 co_return *value;
1989 });
1990 }
1991
2000 template < query_spec_c QuerySpec >
2001 requires std::same_as< typename QuerySpec::input_type, std::monostate >
2002 void register_handler_singleton(typename QuerySpec::output_type value)
2003 {
2005 }
2006
2022 template < query_handler_spec_c HandlerSpec, typename Handler >
2024 {
2025 auto query_index = std::type_index(typeid(query_handler_query_t< HandlerSpec >));
2026 if (query_handler_registry.find(query_index) != query_handler_registry.end())
2027 {
2028 throw std::logic_error("Handler already registered");
2029 }
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];
2032
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;
2038
2039 // This is safe because the memoized_impl lifetime is tied to the query_handler_descriptor lifetime
2040 descriptor.m_get = [&memoized_impl](executor* exec, query_handler_input_t< HandlerSpec > const& input_ask, bool immediate)
2041 {
2042 return memoized_impl.get(exec, input_ask, immediate);
2043 };
2044
2045 qd_any.m_iterate_all = [&memoized_impl](std::function< void(query_node&) > callback)
2046 {
2047 memoized_impl.iterate_all(std::move(callback));
2048 };
2049
2050 qd_any.m_input_type = std::type_index(typeid(query_handler_input_t< HandlerSpec >));
2051 qd_any.m_output_type = std::type_index(typeid(query_handler_output_t< HandlerSpec >));
2053
2058 register_subquery_descriptors_from_typelist< query_handler_dependencies_t< HandlerSpec > >();
2059 register_subquery_descriptors_from_typelist< query_handler_produced_subqueries_t< HandlerSpec > >();
2060
2062
2063 m_pending_binds.push_back(
2064 [&memoized_impl](graph* exec)
2065 {
2066 memoized_impl.bind(exec);
2067 });
2068 }
2069
2071 template < query_spec_c QuerySpec >
2072 auto get_query_descriptor() -> query_descriptor< QuerySpec >&
2073 {
2074 auto it = query_handler_registry.find(std::type_index(typeid(QuerySpec)));
2075 if (it == query_handler_registry.end())
2076 {
2077 throw std::logic_error("No handler registered for the requested query spec");
2078 }
2079 return std::any_cast< query_descriptor< QuerySpec >& >(it->second);
2080 }
2082
2093
2104 template < typename Visitor >
2106 {
2107 for (auto const& [type_index, binary_desc] : m_binary_descriptors)
2108 {
2109 auto text_desc = m_text_descriptors.get_or_create(type_index,
2110 []()
2111 {
2112 return text_descriptor{};
2113 });
2114
2115 visitor(binary_desc.m_typesig, binary_desc.m_deserialize, text_desc.m_to_string);
2116 }
2117 }
2118
2124
2126 graph_data dump(query_node* start_node);
2128
2133 std::vector< std::byte > marshall();
2134
2136 std::vector< std::byte > marshall(query_node* start_node);
2138
2144 bool node_debug_io_capture_enabled() const noexcept;
2145 };
2146
2147 template < typename Result >
2148 struct query_result
2149 {
2150 graph::query_node* m_node;
2151 bool m_started_by_current_request = false;
2152
2153 public:
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)
2155 {
2156 }
2157
2158 bool started_by_current_request() const noexcept
2159 {
2160 return m_started_by_current_request;
2161 }
2162
2163 bool available() const
2164 {
2165 std::shared_lock lock(m_node->m_mutex);
2166 result< Result > const& res = std::any_cast< result< Result > const& >(m_node->m_result);
2167 return res.has_result();
2168 }
2169
2170 void wait()
2171 {
2172 std::unique_lock lock(m_node->m_mutex);
2173 m_node->m_external_cv.wait(lock,
2174 [this]()
2175 {
2176 result< Result > const& res = std::any_cast< result< Result > const& >(m_node->m_result);
2177 return res.has_result();
2178 });
2179 }
2180
2181 bool await_ready()
2182 {
2183 std::shared_lock lock(m_node->m_mutex);
2184 result< Result > const& res = std::any_cast< result< Result > const& >(m_node->m_result);
2185 return res.has_result() && !res.has_unexpected_exception();
2186 }
2187
2188 bool await_ready([[maybe_unused]] std::unique_lock< std::shared_mutex >& lock)
2189 {
2190 return std::any_cast< result< Result >& >(m_node->m_result).has_result();
2191 }
2192
2193 bool await_ready([[maybe_unused]] std::shared_lock< std::shared_mutex >& lock)
2194 {
2195 return std::any_cast< result< Result > const& >(m_node->m_result).has_result();
2196 }
2197
2198 template < typename U >
2199 void await_suspend(std::coroutine_handle< U > h)
2200 {
2201 await_suspend_helper< typename U::coroutine_type >(h.promise(), h);
2202 }
2203
2204 template < typename C >
2205 void await_suspend_helper(typename C::promise_type& p, std::coroutine_handle< typename C::promise_type > h)
2206 {
2207 await_suspend_helper2< C, typename C::spec_type >(p, h);
2208 }
2209
2210 template < typename C, typename HandlerSpec >
2211 void await_suspend_helper2(typename C::promise_type& p, std::coroutine_handle< typename C::promise_type > h)
2212 {
2213 std::unique_lock lock(m_node->m_mutex);
2214
2215 graph::executor* current_executor = p.m_exec;
2216 assert(current_executor != nullptr);
2217 current_executor->check_legal_access();
2218
2219 if (await_ready(lock))
2220 {
2221 result< Result > const& res = std::any_cast< result< Result > const& >(m_node->m_result);
2222 if (res.has_unexpected_exception())
2223 {
2224 std::exception_ptr unexpected_exception = res.get_error_result().unexpected_exception();
2225 lock.unlock();
2226 p.abort_with_unexpected_exception(current_executor, unexpected_exception);
2227 }
2228 else
2229 {
2230 current_executor->declare_runnable(graph::resumption{.m_promise = &p, .handle = h}, false);
2231 }
2232 }
2233 else
2234 {
2235 m_node->waiters.push_back(graph::resumption{.m_promise = &p, .handle = h});
2236 }
2237 }
2238
2239 Result const& await_resume()
2240 {
2241 std::shared_lock lock(m_node->m_mutex);
2242 auto& res = std::any_cast< result< Result > const& >(m_node->m_result);
2243 return res.value();
2244 }
2245 };
2246
2247 template < subquery_spec_c SubquerySpec >
2248 struct subquery_query_result
2249 {
2250 graph::query_node* m_parent_node;
2251 graph::subquery_slot< SubquerySpec >* m_slot;
2252
2253 public:
2254 subquery_query_result(graph::query_node* parent_node, graph::subquery_slot< SubquerySpec >* slot) : m_parent_node(parent_node), m_slot(slot)
2255 {
2256 }
2257
2258 bool available() const
2259 {
2260 std::shared_lock lock(m_parent_node->m_mutex);
2261 result< typename SubquerySpec::output_type > const& res = std::any_cast< result< typename SubquerySpec::output_type > const& >(m_slot->m_result);
2262 return res.has_result();
2263 }
2264
2265 void wait()
2266 {
2267 std::unique_lock lock(m_parent_node->m_mutex);
2268 m_parent_node->m_external_cv.wait(lock,
2269 [this]()
2270 {
2271 result< typename SubquerySpec::output_type > const& res = std::any_cast< result< typename SubquerySpec::output_type > const& >(m_slot->m_result);
2272 return res.has_result();
2273 });
2274 }
2275
2276 bool await_ready()
2277 {
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();
2281 }
2282
2283 bool await_ready([[maybe_unused]] std::unique_lock< std::shared_mutex >& lock)
2284 {
2285 return std::any_cast< result< typename SubquerySpec::output_type >& >(m_slot->m_result).has_result();
2286 }
2287
2288 bool await_ready([[maybe_unused]] std::shared_lock< std::shared_mutex >& lock)
2289 {
2290 return std::any_cast< result< typename SubquerySpec::output_type > const& >(m_slot->m_result).has_result();
2291 }
2292
2293 template < typename U >
2294 void await_suspend(std::coroutine_handle< U > h)
2295 {
2296 await_suspend_helper< typename U::coroutine_type >(h.promise(), h);
2297 }
2298
2299 template < typename C >
2300 void await_suspend_helper(typename C::promise_type& p, std::coroutine_handle< typename C::promise_type > h)
2301 {
2302 std::unique_lock lock(m_parent_node->m_mutex);
2303
2304 graph::executor* current_executor = p.m_exec;
2305 assert(current_executor != nullptr);
2306 current_executor->check_legal_access();
2307
2308 if (await_ready(lock))
2309 {
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())
2312 {
2313 std::exception_ptr unexpected_exception = res.get_error_result().unexpected_exception();
2314 lock.unlock();
2315 p.abort_with_unexpected_exception(current_executor, unexpected_exception);
2316 }
2317 else
2318 {
2319 current_executor->declare_runnable(graph::resumption{.m_promise = &p, .handle = h}, false);
2320 }
2321 }
2322 else
2323 {
2324 m_slot->waiters.push_back(graph::resumption{.m_promise = &p, .handle = h});
2325 }
2326 }
2327
2328 typename SubquerySpec::output_type const& await_resume()
2329 {
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);
2332 return res.value();
2333 }
2334 };
2335
2337 template < query_handler_spec_c HandlerSpec, typename Ret >
2338 class cosubroutine_impl;
2340
2353 template < query_handler_spec_c HandlerSpec >
2355 {
2357 using spec_type = HandlerSpec;
2358
2367 template < typename Ret >
2368 using cosubroutine = cosubroutine_impl< HandlerSpec, Ret >;
2369
2371 struct promise_type : public graph::promise_base
2372 {
2373 using root_coroutine_type = coroutine< HandlerSpec >;
2374 using coroutine_type = coroutine< HandlerSpec >;
2375
2376 typename graph::template query_descriptor< query_handler_query_t< HandlerSpec > >* m_handler_entry = nullptr;
2377 coroutine< HandlerSpec >* m_coroutine = nullptr;
2378
2379 std::string query_name() const override
2380 {
2382 }
2383
2384 std::string input_text() const override
2385 {
2386 using input_type = query_handler_input_t< HandlerSpec >;
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);
2389 }
2390
2391 template < query_spec_c QuerySpec >
2392 auto await_transform(request< QuerySpec > a) -> query_result< typename QuerySpec::output_type >
2393 {
2394 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< QuerySpec >(), "Making a request to a query which is not listed in the HandlerSpec::dependencies");
2395 assert(m_exec != nullptr);
2396 assert(m_node != nullptr);
2397
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);
2399
2400 typename graph::template query_descriptor< QuerySpec >& dep_query_descriptor = handler_impl.template get_dependency_descriptor< QuerySpec >();
2401
2402 query_result< typename QuerySpec::output_type > await_obj = dep_query_descriptor.m_get(m_exec, a.m_input, false);
2403
2404 graph::query_node* await_node = await_obj.m_node;
2405 graph::declare_dependency(m_node, await_node);
2406 return await_obj;
2407 }
2408
2409 template < subquery_spec_c SubquerySpec >
2410 auto await_transform(subquery_request< SubquerySpec > a) -> subquery_query_result< SubquerySpec >
2411 {
2412 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< SubquerySpec >(), "Making a subquery request requires listing the subquery in query_handler_dependencies_t< HandlerSpec >");
2413 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 >");
2414 assert(m_exec != nullptr);
2415 assert(m_node != nullptr);
2416
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);
2418
2419 typename graph::template query_descriptor< typename SubquerySpec::parent_query >& parent_query_descriptor = handler_impl.template get_subquery_parent_descriptor< SubquerySpec >();
2420
2421 query_result< typename SubquerySpec::parent_query::output_type > parent_await_obj = parent_query_descriptor.m_get(m_exec, a.m_parent_input, false);
2422
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);
2426 }
2427
2428 template < query_spec_c QuerySpec >
2429 auto yield_value(dependency< QuerySpec > dep)
2430 {
2431 static_assert(query_handler_dependencies_t< HandlerSpec >::template contains< QuerySpec >(), "Making a request to a query which is not listed in the HandlerSpec::dependencies");
2432 assert(m_exec != nullptr);
2433 assert(m_node != nullptr);
2434
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{};
2441 }
2442
2443 template < subquery_spec_c SubquerySpec >
2444 auto yield_value(subquery_result< SubquerySpec > res)
2445 {
2446 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 >");
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);
2450
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{};
2453 }
2454
2455 template < typename Ret >
2456 auto await_transform(cosubroutine< Ret >&& sub)
2457 {
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;
2462 m_exec->declare_runnable(std::coroutine_handle< typename cosubroutine< Ret >::promise_type >::from_promise(*sub_promise), sub_promise, false);
2463 return typename cosubroutine< Ret >::awaitable(sub_promise);
2464 }
2465
2466 void return_value(query_handler_output_t< HandlerSpec > value)
2467 {
2468 assert(m_node != nullptr);
2469 std::unique_lock lock(m_node->m_mutex);
2470 result< query_handler_output_t< HandlerSpec > >& res = std::any_cast< result< query_handler_output_t< HandlerSpec > >& >(m_node->m_result);
2471 if (m_exec->m_runner->m_graph->node_debug_io_capture_enabled())
2472 {
2473 m_node->m_output_string = m_exec->m_runner->m_graph->datavalue_to_string(value);
2474 }
2475 res.set_value(std::move(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));
2479 }
2480
2481 auto initial_suspend()
2482 {
2483 return std::suspend_always{};
2484 }
2485
2486 auto final_suspend() noexcept
2487 {
2488 std::unique_lock lock(m_node->m_mutex);
2489 result< query_handler_output_t< HandlerSpec > >& res = std::any_cast< result< query_handler_output_t< HandlerSpec > >& >(m_node->m_result);
2490 if (!res.has_result() && m_node->dependencies.empty())
2491 {
2492 std::exception_ptr exception = std::make_exception_ptr(bad_continuation());
2493 res.set_error(error_result::unexpected(exception));
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);
2497 }
2498 return std::suspend_always{};
2499 }
2500
2501 auto get_return_object()
2502 {
2503 coroutine< HandlerSpec > co(this);
2504 assert(m_coroutine == &co);
2505 assert(co.m_promise == this);
2506 return co;
2507 }
2508
2509 auto yield_value(debug_message msg, std::source_location loc = std::source_location::current())
2510 {
2511 assert(m_node != nullptr);
2512 std::unique_lock lock(m_node->m_mutex);
2513 msg.location = loc;
2514 m_node->m_messages.push_back(std::move(msg));
2515 return std::suspend_never{};
2516 }
2517
2518 auto unhandled_exception()
2519 {
2520 assert(m_node != nullptr);
2521 assert(m_exec != nullptr);
2522 std::unique_lock lock(m_node->m_mutex);
2523 result< query_handler_output_t< HandlerSpec > >& res = std::any_cast< result< query_handler_output_t< HandlerSpec > >& >(m_node->m_result);
2524 error_result captured_error = m_exec->m_runner->m_graph->capture_current_exception();
2525 std::exception_ptr unexpected_exception = captured_error.unexpected_exception();
2526 if (unexpected_exception != nullptr)
2527 {
2528 m_exec->m_runner->m_graph->print_noncanonical_exception_diagnostic(this, captured_error);
2529 }
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)
2533 {
2534 m_node->abort_waiters(m_exec, lock, unexpected_exception);
2535 graph::abort_resumptions(m_exec, std::move(subquery_waiters), unexpected_exception);
2536 }
2537 else
2538 {
2539 m_node->unblock_waiters(m_exec, lock);
2540 graph::declare_resumptions(m_exec, std::move(subquery_waiters));
2541 }
2542 }
2543
2544 void abort_with_unexpected_exception(graph::executor* executor, std::exception_ptr exception) override
2545 {
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);
2549 }
2550
2551 template < typename Res >
2552 void await_suspend_on(query_result< Res > q)
2553 {
2554 std::unique_lock lock(q.m_node->m_mutex);
2555
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);
2560
2561 if (q.await_ready(lock))
2562 {
2563 result< Res > const& res = std::any_cast< result< Res > const& >(q.m_node->m_result);
2564 if (res.has_unexpected_exception())
2565 {
2566 std::exception_ptr unexpected_exception = res.get_error_result().unexpected_exception();
2567 lock.unlock();
2568 abort_with_unexpected_exception(current_executor, unexpected_exception);
2569 }
2570 else
2571 {
2572 lock.unlock();
2573 current_executor->declare_runnable(graph::resumption{.m_promise = this, .handle = h}, false);
2574 }
2575 }
2576 else
2577 {
2578 q.m_node->waiters.push_back(graph::resumption{.m_promise = this, .handle = h});
2579 }
2580 }
2581 };
2583
2585 promise_type* m_promise = nullptr;
2586
2587 coroutine() noexcept : m_promise(nullptr)
2588 {
2589 }
2590
2591 coroutine(coroutine&) = delete;
2592
2593 coroutine(coroutine&& other) noexcept : m_promise(other.m_promise)
2594 {
2595 m_promise = other.m_promise;
2596 other.m_promise = nullptr;
2597 if (m_promise)
2598 {
2599 m_promise->m_coroutine = this;
2600 }
2601 }
2602
2603 ~coroutine()
2604 {
2605 if (m_promise)
2606 {
2607 m_promise->m_coroutine = nullptr;
2608 std::coroutine_handle< promise_type >::from_promise(*m_promise).destroy();
2609 }
2610 }
2611
2612 coroutine(promise_type* p) : m_promise(p)
2613 {
2614 assert(p->m_coroutine == nullptr);
2615 p->m_coroutine = this;
2616 }
2618 };
2619
2621 template < query_handler_spec_c HandlerSpec, typename Ret >
2622 class cosubroutine_impl
2623 {
2624 friend struct promise_type;
2625 friend struct coroutine< HandlerSpec >;
2626 template < query_handler_spec_c OtherHandlerSpec, typename OtherRet >
2627 friend class cosubroutine_impl;
2628
2629 public:
2630 using spec_type = HandlerSpec;
2631 struct promise_type : public graph::promise_base
2632 {
2633 using root_coroutine_type = coroutine< HandlerSpec >;
2634 using coroutine_type = cosubroutine_impl< HandlerSpec, Ret >;
2635
2636 std::mutex m_mutex;
2637
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;
2642
2643 std::string query_name() const override
2644 {
2645 return std::string(query_handler_query_t< HandlerSpec >::query_id);
2646 }
2647
2648 std::string input_text() const override
2649 {
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);
2653 }
2654
2655 auto get_return_object()
2656 {
2657 return cosubroutine_impl(this);
2658 }
2659
2660 auto initial_suspend()
2661 {
2662 return std::suspend_always{};
2663 }
2664
2665 void return_value(Ret value)
2666 {
2667 assert(m_node != nullptr);
2668 assert(this->m_exec != nullptr);
2669
2670 {
2671 std::unique_lock lock(m_mutex);
2672 m_result.set_value(std::move(value));
2673 unblock_waiter(lock);
2674 }
2675 }
2676
2677 auto yield_value(debug_message msg, std::source_location loc = std::source_location::current())
2678 {
2679 assert(m_node != nullptr);
2680 std::unique_lock lock(m_node->m_mutex);
2681 msg.location = loc;
2682 m_node->m_messages.push_back(std::move(msg));
2683 return std::suspend_never{};
2684 }
2685
2687 template < query_spec_c QuerySpec >
2688 auto yield_value(dependency< QuerySpec > dep)
2689 {
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);
2693
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{};
2700 }
2701
2702 auto final_suspend() noexcept
2703 {
2704 std::unique_lock lock(m_mutex);
2705 assert(m_result.has_result());
2706 return std::suspend_always{};
2707 }
2708
2709 auto unhandled_exception()
2710 {
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);
2716 }
2717
2718 void abort_with_unexpected_exception(graph::executor* executor, std::exception_ptr exception) override
2719 {
2720 this->m_exec = executor;
2721 {
2722 std::unique_lock lock(m_mutex);
2723 if (!m_result.has_result())
2724 {
2725 m_result.set_error(error_result::unexpected(exception));
2726 }
2727 }
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);
2730 }
2731
2732 auto unblock_waiter(std::unique_lock< std::mutex >& lock)
2733 {
2734 if (m_waiter.has_value())
2735 {
2736 auto resumption = m_waiter.value();
2737 m_waiter.reset();
2738 m_exec->check_legal_access();
2739 m_exec->declare_runnable(resumption, false);
2740
2741 lock.unlock();
2742 }
2743 }
2744
2745 template < typename Ret2 >
2746 auto await_transform(typename coroutine< HandlerSpec >::template cosubroutine< Ret2 >&& sub)
2747 {
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);
2753 }
2754
2755 template < query_spec_c QuerySpec >
2756 auto await_transform(request< QuerySpec > a) -> query_result< typename QuerySpec::output_type >
2757 {
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);
2761
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);
2763
2764 typename graph::template query_descriptor< QuerySpec >& dep_query_descriptor = handler_impl.template get_dependency_descriptor< QuerySpec >();
2765
2766 query_result< typename QuerySpec::output_type > await_obj = dep_query_descriptor.m_get(m_exec, a.m_input, false);
2767
2768 graph::query_node* await_node = await_obj.m_node;
2769 graph::declare_dependency(m_node, await_node);
2770 return await_obj;
2771 }
2772
2773 template < subquery_spec_c SubquerySpec >
2774 auto await_transform(subquery_request< SubquerySpec > a) -> subquery_query_result< SubquerySpec >
2775 {
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);
2780
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);
2782
2783 typename graph::template query_descriptor< typename SubquerySpec::parent_query >& parent_query_descriptor = handler_impl.template get_subquery_parent_descriptor< SubquerySpec >();
2784
2785 query_result< typename SubquerySpec::parent_query::output_type > parent_await_obj = parent_query_descriptor.m_get(m_exec, a.m_parent_input, false);
2786
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);
2790 }
2791
2792 template < subquery_spec_c SubquerySpec >
2793 auto yield_value(subquery_result< SubquerySpec > res)
2794 {
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);
2799
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{};
2802 }
2803
2804 ~promise_type()
2805 {
2806 assert(m_srt == nullptr);
2807 }
2808 };
2809
2810 struct awaitable
2811 {
2812 promise_type* m_promise;
2813
2814 public:
2815 awaitable(promise_type* promise) : m_promise(promise)
2816 {
2817 }
2818
2819 bool await_ready()
2820 {
2821 std::unique_lock lck(m_promise->m_mutex);
2822 return await_ready(lck);
2823 }
2824
2825 bool await_ready([[maybe_unused]] std::unique_lock< std::mutex >& lock) noexcept
2826 {
2827 assert(m_promise != nullptr);
2828 return m_promise->m_result.has_result();
2829 }
2830
2831 Ret await_resume()
2832 {
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();
2837 }
2838
2839 template < typename U >
2840 void await_suspend(std::coroutine_handle< U > h)
2841 {
2842 await_suspend_helper< typename U::coroutine_type >(h.promise(), h);
2843 }
2844
2845 template < typename C >
2846 void await_suspend_helper(typename C::promise_type& p, std::coroutine_handle< typename C::promise_type > h)
2847 {
2848 assert(m_promise != nullptr);
2849
2850 auto current_executor = p.m_exec;
2851 assert(current_executor != nullptr);
2852 current_executor->check_legal_access();
2853
2854 graph::resumption r{.m_promise = &h.promise(), .handle = h};
2855 std::unique_lock lck(m_promise->m_mutex);
2856
2857 if (await_ready(lck))
2858 {
2859 p.m_exec->declare_runnable(r, false);
2860 }
2861 else
2862 {
2863 assert(m_promise->m_waiter.has_value() == false);
2864 m_promise->m_waiter = r;
2865 }
2866 }
2867 };
2868
2869 private:
2870 promise_type* m_promise;
2871
2872 public:
2873 cosubroutine_impl(promise_type* p) : m_promise(p)
2874 {
2875 assert(p->m_srt == nullptr);
2876 p->m_srt = this;
2877 }
2878
2879 cosubroutine_impl(const cosubroutine_impl&) = delete;
2880 cosubroutine_impl& operator=(const cosubroutine_impl&) = delete;
2881
2882 ~cosubroutine_impl()
2883 {
2884 if (m_promise)
2885 {
2886 std::unique_lock lock(m_promise->m_run_mutex);
2887 lock.unlock();
2888
2889 m_promise->m_srt = nullptr;
2890 std::coroutine_handle< promise_type >::from_promise(*m_promise).destroy();
2891 }
2892 }
2893 };
2894
2895 template < query_handler_spec_c HandlerSpec >
2896 class cosubroutine_impl< HandlerSpec, void >
2897 {
2898 friend struct promise_type;
2899 friend struct coroutine< HandlerSpec >;
2900 template < query_handler_spec_c OtherHandlerSpec, typename OtherRet >
2901 friend class cosubroutine_impl;
2902
2903 public:
2904 using spec_type = HandlerSpec;
2905 struct promise_type : public graph::promise_base
2906 {
2907 using root_coroutine_type = coroutine< HandlerSpec >;
2908 using coroutine_type = cosubroutine_impl< HandlerSpec, void >;
2909
2910 std::mutex m_mutex;
2911
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;
2916
2917 std::string query_name() const override
2918 {
2919 return std::string(query_handler_query_t< HandlerSpec >::query_id);
2920 }
2921
2922 std::string input_text() const override
2923 {
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);
2927 }
2928
2929 auto get_return_object()
2930 {
2931 return cosubroutine_impl(this);
2932 }
2933
2934 auto initial_suspend()
2935 {
2936 return std::suspend_always{};
2937 }
2938
2939 void return_void()
2940 {
2941 assert(m_node != nullptr);
2942 assert(this->m_exec != nullptr);
2943
2944 {
2945 std::unique_lock lock(m_mutex);
2946 m_result.set_value();
2947 unblock_waiter(lock);
2948 }
2949 }
2950
2951 auto yield_value(debug_message msg, std::source_location loc = std::source_location::current())
2952 {
2953 assert(m_node != nullptr);
2954 std::unique_lock lock(m_node->m_mutex);
2955 msg.location = loc;
2956 m_node->m_messages.push_back(std::move(msg));
2957 return std::suspend_never{};
2958 }
2959
2961 template < query_spec_c QuerySpec >
2962 auto yield_value(dependency< QuerySpec > dep)
2963 {
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);
2967
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{};
2974 }
2975
2976 auto final_suspend() noexcept
2977 {
2978 std::unique_lock lock(m_mutex);
2979 assert(m_result.has_result());
2980 return std::suspend_always{};
2981 }
2982
2983 auto unhandled_exception()
2984 {
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);
2990 }
2991
2992 void abort_with_unexpected_exception(graph::executor* executor, std::exception_ptr exception) override
2993 {
2994 this->m_exec = executor;
2995 {
2996 std::unique_lock lock(m_mutex);
2997 if (!m_result.has_result())
2998 {
2999 m_result.set_error(error_result::unexpected(exception));
3000 }
3001 }
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);
3004 }
3005
3006 auto unblock_waiter(std::unique_lock< std::mutex >& lock)
3007 {
3008 if (m_waiter.has_value())
3009 {
3010 auto resumption = m_waiter.value();
3011 m_waiter.reset();
3012 m_exec->check_legal_access();
3013 m_exec->declare_runnable(resumption, false);
3014
3015 lock.unlock();
3016 }
3017 }
3018
3019 template < typename Ret2 >
3020 auto await_transform(typename coroutine< HandlerSpec >::template cosubroutine< Ret2 >&& sub)
3021 {
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);
3027 }
3028
3029 template < query_spec_c QuerySpec >
3030 auto await_transform(request< QuerySpec > a) -> query_result< typename QuerySpec::output_type >
3031 {
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);
3035
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);
3037
3038 typename graph::template query_descriptor< QuerySpec >& dep_query_descriptor = handler_impl.template get_dependency_descriptor< QuerySpec >();
3039
3040 query_result< typename QuerySpec::output_type > await_obj = dep_query_descriptor.m_get(m_exec, a.m_input, false);
3041
3042 graph::query_node* await_node = await_obj.m_node;
3043 graph::declare_dependency(m_node, await_node);
3044 return await_obj;
3045 }
3046
3047 template < subquery_spec_c SubquerySpec >
3048 auto await_transform(subquery_request< SubquerySpec > a) -> subquery_query_result< SubquerySpec >
3049 {
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);
3054
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);
3056
3057 typename graph::template query_descriptor< typename SubquerySpec::parent_query >& parent_query_descriptor = handler_impl.template get_subquery_parent_descriptor< SubquerySpec >();
3058
3059 query_result< typename SubquerySpec::parent_query::output_type > parent_await_obj = parent_query_descriptor.m_get(m_exec, a.m_parent_input, false);
3060
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);
3064 }
3065
3066 template < subquery_spec_c SubquerySpec >
3067 auto yield_value(subquery_result< SubquerySpec > res)
3068 {
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);
3073
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{};
3076 }
3077
3078 ~promise_type()
3079 {
3080 assert(m_srt == nullptr);
3081 }
3082 };
3083
3084 struct awaitable
3085 {
3086 promise_type* m_promise;
3087
3088 public:
3089 awaitable(promise_type* promise) : m_promise(promise)
3090 {
3091 }
3092
3093 bool await_ready()
3094 {
3095 std::unique_lock lck(m_promise->m_mutex);
3096 return await_ready(lck);
3097 }
3098
3099 bool await_ready([[maybe_unused]] std::unique_lock< std::mutex >& lock) noexcept
3100 {
3101 assert(m_promise != nullptr);
3102 return m_promise->m_result.has_result();
3103 }
3104
3105 void await_resume()
3106 {
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();
3111 }
3112
3113 template < typename U >
3114 void await_suspend(std::coroutine_handle< U > h)
3115 {
3116 await_suspend_helper< typename U::coroutine_type >(h.promise(), h);
3117 }
3118
3119 template < typename C >
3120 void await_suspend_helper(typename C::promise_type& p, std::coroutine_handle< typename C::promise_type > h)
3121 {
3122 assert(m_promise != nullptr);
3123
3124 auto current_executor = p.m_exec;
3125 assert(current_executor != nullptr);
3126 current_executor->check_legal_access();
3127
3128 graph::resumption r{.m_promise = &h.promise(), .handle = h};
3129 std::unique_lock lck(m_promise->m_mutex);
3130
3131 if (await_ready(lck))
3132 {
3133 p.m_exec->declare_runnable(r, false);
3134 }
3135 else
3136 {
3137 assert(m_promise->m_waiter.has_value() == false);
3138 m_promise->m_waiter = r;
3139 }
3140 }
3141 };
3142
3143 private:
3144 promise_type* m_promise;
3145
3146 public:
3147 cosubroutine_impl(promise_type* p) : m_promise(p)
3148 {
3149 assert(p->m_srt == nullptr);
3150 p->m_srt = this;
3151 }
3152
3153 cosubroutine_impl(const cosubroutine_impl&) = delete;
3154 cosubroutine_impl& operator=(const cosubroutine_impl&) = delete;
3155
3156 ~cosubroutine_impl()
3157 {
3158 if (m_promise)
3159 {
3160 std::unique_lock lock(m_promise->m_run_mutex);
3161 lock.unlock();
3162
3163 m_promise->m_srt = nullptr;
3164 std::coroutine_handle< promise_type >::from_promise(*m_promise).destroy();
3165 }
3166 }
3167 };
3169
3170 } // namespace RPNX_DEBUGONLY(debug_abi)RPNX_RELEASEONLY(release_abi)
3171
3172} // namespace rpnx::querygraph
3173
3174#endif // NODEGRAPH2_NODEGRAPH_HPP
Human-readable formatting utilities used by QueryGraph diagnostics.
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.
Definition graphdata.hpp:42
status
Terminal or in-progress state recorded for a dumped result.
Definition graphdata.hpp:26
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.
Definition graphdata.hpp:46
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.