3#include <gemc/guts/gthreads.h>
5#include <condition_variable>
10#include <unordered_map>
18 std::shared_ptr<const GSROTiming> model = {}, std::size_t event_limit = 65536) {
21 throw std::invalid_argument(
"SRO run requires a resource factory and positive buffer limits");
23 std::lock_guard lock(mutex);
24 if (state == State::running || state == State::finishing) {
25 throw std::logic_error(
"Finish the previous SRO run before beginning another");
28 make_resources = std::move(factory);
29 on_delivery = std::move(delivered);
33 timing = std::move(model);
34 max_pending_events = event_limit;
36 first_undelivered_event = 0;
37 outstanding_payloads = 0;
38 progress_dirty =
true;
39 stop_progress =
false;
40 state = State::running;
42 if (timing) { progress_thread = jthread_alias([
this] { run_progress(); }); }
45 state = State::finished;
46 failure = std::current_exception();
52 std::unique_lock lock(mutex);
54 const auto found = crates.find(
id);
55 if (found != crates.end()) {
return found->second; }
57 auto [entry, inserted] = crates.try_emplace(
id);
59 entry->second = std::make_shared<GSROCrate>(
id,
60 [factory = make_resources,
id] {
return factory(
id); }, limits,
61 [
this, delivered = on_delivery, id](
GSROEventId event, std::uint64_t sequence) {
62 if (delivered) { delivered(
id,
event, sequence); }
63 acknowledge_payload(
event);
64 }, [
this](std::exception_ptr error) { fail_run(error); });
66 if (safe_time) { entry->second->advance_time(*safe_time); }
70 if (!entry->second) { crates.erase(entry); }
72 fail_run(std::current_exception());
78 if (!payload.
data) {
throw std::invalid_argument(
"SRO payload data is null"); }
80 std::lock_guard lock(mutex);
83 auto&
event = open_event(payload.
event_id);
86 ++outstanding_payloads;
92 crate->enqueue_payload(std::move(payload));
95 const auto error = std::current_exception();
96 std::lock_guard lock(mutex);
98 if (state == State::running) { fail_run_locked(error); }
99 std::rethrow_exception(failure ? failure : error);
104 std::lock_guard lock(mutex);
106 if (!timing) {
throw std::logic_error(
"SRO event completion requires automatic timing"); }
107 open_event(
id).closed =
true;
113 std::lock_guard lock(mutex);
115 if (timing) {
throw std::logic_error(
"SRO automatic timing owns safe-time advancement"); }
121 std::vector<std::shared_ptr<GSROCrate>> snapshot;
123 std::lock_guard lock(mutex);
124 if (failure) { std::rethrow_exception(failure); }
125 if (safe_time && boundary < *safe_time) {
126 throw std::invalid_argument(
"SRO service safe-time boundaries must not decrease");
128 safe_time = boundary;
129 for (
const auto& [
id, crate] : crates) { snapshot.push_back(crate); }
132 for (
const auto& crate : snapshot) { crate->advance_time(boundary); }
135 fail_run(std::current_exception());
141 std::vector<std::shared_ptr<GSROCrate>> snapshot;
143 std::unique_lock lock(mutex);
144 if (state == State::idle || state == State::finished) {
145 if (failure) { std::rethrow_exception(failure); }
150 throw std::invalid_argument(
"SRO automatic timing owns the final safe-time boundary");
152 state = State::finishing;
155 progress_changed.wait(lock, [&] {
return failure || outstanding_payloads == 0; });
156 stop_progress =
true;
157 progress_changed.notify_all();
159 if (progress_thread.joinable()) { progress_thread.join(); }
163 throw std::invalid_argument(
"SRO final boundary precedes the service boundary");
166 state = State::finishing;
167 for (
const auto& [
id, crate] : crates) {
168 crate->request_finish(context);
169 snapshot.push_back(crate);
173 for (
const auto& crate : snapshot) {
174 try { crate->finish_and_join(context); }
175 catch (...) { fail_run(std::current_exception()); }
178 std::lock_guard lock(mutex);
179 state = State::finished;
185 std::lock_guard lock(mutex);
186 if (failure) { std::rethrow_exception(failure); }
190 if (!error) {
throw std::invalid_argument(
"SRO cancellation requires an exception"); }
196 std::size_t outstanding = 0;
201 if (
id == std::numeric_limits<GSROEventId>::max()) {
202 throw std::invalid_argument(
"SRO event ID would overflow the completion prefix");
204 if (
id < first_undelivered_event) {
throw std::logic_error(
"SRO event is already complete"); }
205 auto found = events.find(
id);
206 if (found == events.end()) {
207 if (events.size() >= max_pending_events) {
208 const auto error = std::make_exception_ptr(
209 std::length_error(
"SRO pending event limit exceeded"));
210 fail_run_locked(error);
211 std::rethrow_exception(error);
213 found = events.try_emplace(
id).first;
215 if (found->second.closed) {
throw std::logic_error(
"SRO event is already complete"); }
216 return found->second;
220 std::lock_guard lock(mutex);
221 if (!timing) {
return; }
222 auto&
event = events.at(
id);
224 --outstanding_payloads;
226 progress_changed.notify_all();
229 void update_prefix() {
230 while (!events.empty()) {
231 const auto next = events.begin();
232 if (next->first != first_undelivered_event || !next->second.closed || next->second.outstanding) {
236 ++first_undelivered_event;
237 progress_dirty =
true;
239 if (progress_dirty) { progress_changed.notify_all(); }
242 void run_progress() noexcept {
247 std::unique_lock lock(mutex);
248 progress_changed.wait(lock, [&] {
return failure || stop_progress || progress_dirty; });
249 if (failure || (stop_progress && !progress_dirty)) {
return; }
250 prefix = first_undelivered_event;
251 progress_dirty =
false;
254 if (
const auto boundary = timing->earliest_remaining_time(prefix)) {
259 catch (...) { fail_run(std::current_exception()); }
262 void check_running()
const {
263 if (failure) { std::rethrow_exception(failure); }
264 if (state != State::running) {
throw std::logic_error(
"SRO run is not accepting input"); }
267 void fail_run(std::exception_ptr error) {
268 std::lock_guard lock(mutex);
269 fail_run_locked(error);
272 void fail_run_locked(std::exception_ptr error) {
273 if (failure) {
return; }
275 progress_changed.notify_all();
277 for (
const auto& [
id, crate] : crates) { crate->cancel(error); }
280 enum class State { idle, running, finishing, finished };
281 mutable std::mutex mutex;
282 State state = State::idle;
285 GSROCrateLimits limits;
286 std::unordered_map<GSROCrateId, std::shared_ptr<GSROCrate>> crates;
287 std::optional<GSROTime> safe_time;
288 std::exception_ptr failure;
289 std::shared_ptr<const GSROTiming> timing;
290 std::size_t max_pending_events = 65536;
291 std::map<GSROEventId, Event> events;
293 std::size_t outstanding_payloads = 0;
294 bool progress_dirty =
false;
295 bool stop_progress =
false;
296 std::condition_variable progress_changed;
297 jthread_alias progress_thread;
306 impl->begin_run(std::move(factory), limits, std::move(delivered));
310 if (!timing) {
throw std::invalid_argument(
"SRO automatic timing model is null"); }
311 impl->begin_run(std::move(factory), limits, {}, std::move(timing), max_pending_events);
315 impl->dispatch_payload_to_crate(std::move(payload));
void cancel_run(std::exception_ptr error)
void dispatch_payload_to_crate(GSROPayload payload)
void finish_run(GSROEndContext context)
void rethrow_if_failed() const
void complete_event(GSROEventId id)
std::shared_ptr< GSROCrate > create_crate_thread_if_needed(GSROCrateId id)
void broadcast_time(GSROTime boundary)
void advance_time(GSROTime boundary)
void begin_run(ResourceFactory factory, GSROCrateLimits bounds, DeliveryCallback delivered, std::shared_ptr< const GSROTiming > model={}, std::size_t event_limit=65536)
void begin_run(ResourceFactory make_resources, GSROCrateLimits limits={}, DeliveryCallback on_delivery={})
Begin a fresh invocation after the previous one was joined; clears its progress and failure state.
void finish_run(GSROEndContext context)
void advance_time(GSROTime safe_time)
Manual mode only: broadcast a proven bound after all earlier dispatches return. New crates inherit it...
void rethrow_if_failed() const
void complete_event(GSROEventId event_id)
Automatic mode only. Close once, after all dispatch calls for this event have returned.
void create_crate_thread_if_needed(GSROCrateId crate_id)
Also permits implementations to request output for empty crates. Concurrent requests create once.
std::function< void(GSROCrateId, GSROEventId, std::uint64_t)> DeliveryCallback
void cancel_run(std::exception_ptr error)
Report a worker-side failure and wake blocked producers. The run owner still calls finish_run to join...
std::function< GSROCrateResources(GSROCrateId)> ResourceFactory
void dispatch_payload_to_crate(GSROPayload payload)
Transfers ownership directly to the destination queue, creating its crate on first use.
std::chrono::duration< std::int64_t, std::nano > GSROTime
std::uint32_t GSROCrateId
std::uint64_t GSROEventId
Per-crate bounds preventing asynchronous delivery from consuming unlimited memory.
std::size_t queue_messages
Maximum queued payload/progress messages; producers wait when the queue is full.
std::size_t queue_bytes
Maximum accounted payload bytes in the input queue; producers wait until their payload fits.
std::size_t pending_bytes
Maximum accounted payload bytes awaiting time ordering; exceeding this fails the crate.
std::size_t pending_payloads
Maximum payloads awaiting time ordering; exceeding this fails the crate.
Input-delivery state passed to the implementation at orderly shutdown.
std::optional< GSROTime > safe_time
One worker-produced contribution, transferred by move to its destination crate.
std::unique_ptr< const GSROData > data