gstreamer
Loading...
Searching...
No Matches
gSROService.cc
Go to the documentation of this file.
1#include "gSROService.h"
2
3#include <gemc/guts/gthreads.h>
4
5#include <condition_variable>
6#include <limits>
7#include <map>
8#include <mutex>
9#include <stdexcept>
10#include <unordered_map>
11#include <utility>
12#include <vector>
13
15{
16public:
18 std::shared_ptr<const GSROTiming> model = {}, std::size_t event_limit = 65536) {
19 if (!factory || !bounds.queue_messages || !bounds.queue_bytes ||
20 !bounds.pending_payloads || !bounds.pending_bytes || !event_limit) {
21 throw std::invalid_argument("SRO run requires a resource factory and positive buffer limits");
22 }
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");
26 }
27 crates.clear(); // All previous threads have already been joined.
28 make_resources = std::move(factory);
29 on_delivery = std::move(delivered);
30 limits = bounds;
31 safe_time.reset();
32 failure = nullptr;
33 timing = std::move(model);
34 max_pending_events = event_limit;
35 events.clear();
36 first_undelivered_event = 0;
37 outstanding_payloads = 0;
38 progress_dirty = true;
39 stop_progress = false;
40 state = State::running;
41 try {
42 if (timing) { progress_thread = jthread_alias([this] { run_progress(); }); }
43 }
44 catch (...) {
45 state = State::finished;
46 failure = std::current_exception();
47 throw;
48 }
49 }
50
51 std::shared_ptr<GSROCrate> create_crate_thread_if_needed(GSROCrateId id) {
52 std::unique_lock lock(mutex);
53 check_running();
54 const auto found = crates.find(id);
55 if (found != crates.end()) { return found->second; }
56 // Allocate the registry slot before starting a thread; insertion failure must not join under this lock.
57 auto [entry, inserted] = crates.try_emplace(id);
58 try {
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); });
65 // This queue is new and empty; seeding its boundary cannot wait for queue space.
66 if (safe_time) { entry->second->advance_time(*safe_time); }
67 return entry->second;
68 }
69 catch (...) {
70 if (!entry->second) { crates.erase(entry); }
71 lock.unlock();
72 fail_run(std::current_exception());
73 throw;
74 }
75 }
76
78 if (!payload.data) { throw std::invalid_argument("SRO payload data is null"); }
79 {
80 std::lock_guard lock(mutex);
81 check_running();
82 if (timing) {
83 auto& event = open_event(payload.event_id);
84 // Register before enqueue: the crate may acknowledge before this worker returns.
85 ++event.outstanding;
86 ++outstanding_payloads;
87 }
88 }
89 try {
90 auto crate = create_crate_thread_if_needed(payload.crate_id);
91 // The local shared_ptr keeps the crate alive if shutdown races with this dispatch.
92 crate->enqueue_payload(std::move(payload));
93 }
94 catch (...) {
95 const auto error = std::current_exception();
96 std::lock_guard lock(mutex);
97 // An in-flight producer woken by normal shutdown is rejected, without cancelling accepted data.
98 if (state == State::running) { fail_run_locked(error); }
99 std::rethrow_exception(failure ? failure : error);
100 }
101 }
102
104 std::lock_guard lock(mutex);
105 check_running();
106 if (!timing) { throw std::logic_error("SRO event completion requires automatic timing"); }
107 open_event(id).closed = true;
108 update_prefix();
109 }
110
111 void advance_time(GSROTime boundary) {
112 {
113 std::lock_guard lock(mutex);
114 check_running();
115 if (timing) { throw std::logic_error("SRO automatic timing owns safe-time advancement"); }
116 }
117 broadcast_time(boundary);
118 }
119
120 void broadcast_time(GSROTime boundary) {
121 std::vector<std::shared_ptr<GSROCrate>> snapshot;
122 {
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");
127 }
128 safe_time = boundary;
129 for (const auto& [id, crate] : crates) { snapshot.push_back(crate); }
130 }
131 try {
132 for (const auto& crate : snapshot) { crate->advance_time(boundary); }
133 }
134 catch (...) {
135 fail_run(std::current_exception());
137 }
138 }
139
141 std::vector<std::shared_ptr<GSROCrate>> snapshot;
142 {
143 std::unique_lock lock(mutex);
144 if (state == State::idle || state == State::finished) {
145 if (failure) { std::rethrow_exception(failure); }
146 return;
147 }
148 if (timing) {
149 if (context.safe_time) {
150 throw std::invalid_argument("SRO automatic timing owns the final safe-time boundary");
151 }
152 state = State::finishing;
153 // Workers have returned. Keep crate inputs open while the last acknowledgments and
154 // progress messages run; closing them now would lose the final proven prefix.
155 progress_changed.wait(lock, [&] { return failure || outstanding_payloads == 0; });
156 stop_progress = true;
157 progress_changed.notify_all();
158 lock.unlock();
159 if (progress_thread.joinable()) { progress_thread.join(); }
160 lock.lock();
161 }
162 if (context.safe_time && safe_time && *context.safe_time < *safe_time) {
163 throw std::invalid_argument("SRO final boundary precedes the service boundary");
164 }
165 if (!context.safe_time) { context.safe_time = safe_time; }
166 state = State::finishing;
167 for (const auto& [id, crate] : crates) {
168 crate->request_finish(context);
169 snapshot.push_back(crate);
170 }
171 }
172 // All producer waiters have been woken before any join. A failed crate must not skip other joins.
173 for (const auto& crate : snapshot) {
174 try { crate->finish_and_join(context); }
175 catch (...) { fail_run(std::current_exception()); }
176 }
177 {
178 std::lock_guard lock(mutex);
179 state = State::finished;
180 }
182 }
183
184 void rethrow_if_failed() const {
185 std::lock_guard lock(mutex);
186 if (failure) { std::rethrow_exception(failure); }
187 }
188
189 void cancel_run(std::exception_ptr error) {
190 if (!error) { throw std::invalid_argument("SRO cancellation requires an exception"); }
191 fail_run(error);
192 }
193
194private:
195 struct Event {
196 std::size_t outstanding = 0;
197 bool closed = false;
198 };
199
200 Event& open_event(GSROEventId id) {
201 if (id == std::numeric_limits<GSROEventId>::max()) {
202 throw std::invalid_argument("SRO event ID would overflow the completion prefix");
203 }
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);
212 }
213 found = events.try_emplace(id).first;
214 }
215 if (found->second.closed) { throw std::logic_error("SRO event is already complete"); }
216 return found->second;
217 }
218
219 void acknowledge_payload(GSROEventId id) {
220 std::lock_guard lock(mutex);
221 if (!timing) { return; }
222 auto& event = events.at(id);
223 --event.outstanding;
224 --outstanding_payloads;
225 update_prefix();
226 progress_changed.notify_all();
227 }
228
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) {
233 break;
234 }
235 events.erase(next);
236 ++first_undelivered_event;
237 progress_dirty = true;
238 }
239 if (progress_dirty) { progress_changed.notify_all(); }
240 }
241
242 void run_progress() noexcept {
243 try {
244 for (;;) {
245 GSROEventId prefix;
246 {
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;
252 }
253 // Neither implementation calls nor queue-space waits hold the registry mutex.
254 if (const auto boundary = timing->earliest_remaining_time(prefix)) {
255 broadcast_time(*boundary);
256 }
257 }
258 }
259 catch (...) { fail_run(std::current_exception()); }
260 }
261
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"); }
265 }
266
267 void fail_run(std::exception_ptr error) {
268 std::lock_guard lock(mutex);
269 fail_run_locked(error);
270 }
271
272 void fail_run_locked(std::exception_ptr error) {
273 if (failure) { return; }
274 failure = error;
275 progress_changed.notify_all();
276 // cancel only takes a crate's queue mutex. Crate failure notifications never hold that mutex.
277 for (const auto& [id, crate] : crates) { crate->cancel(error); }
278 }
279
280 enum class State { idle, running, finishing, finished };
281 mutable std::mutex mutex;
282 State state = State::idle;
283 ResourceFactory make_resources;
284 DeliveryCallback on_delivery;
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;
292 GSROEventId first_undelivered_event = 0;
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;
298};
299
300GSROService::GSROService() : impl(std::make_unique<Impl>()) {}
302 try { impl->finish_run({GSROEndReason::interrupted, std::nullopt}); }
303 catch (...) { /* Explicit finish_run reports failures. All crate threads have still been joined. */ }
304}
306 impl->begin_run(std::move(factory), limits, std::move(delivered));
307}
308void GSROService::begin_run(ResourceFactory factory, std::shared_ptr<const GSROTiming> timing,
309 GSROCrateLimits limits, std::size_t max_pending_events) {
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);
312}
313void GSROService::create_crate_thread_if_needed(GSROCrateId id) { impl->create_crate_thread_if_needed(id); }
315 impl->dispatch_payload_to_crate(std::move(payload));
316}
317void GSROService::advance_time(GSROTime safe_time) { impl->advance_time(safe_time); }
318void GSROService::complete_event(GSROEventId id) { impl->complete_event(id); }
319void GSROService::finish_run(GSROEndContext context) { impl->finish_run(context); }
320void GSROService::rethrow_if_failed() const { impl->rethrow_if_failed(); }
321void GSROService::cancel_run(std::exception_ptr error) { impl->cancel_run(error); }
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
Definition gSROService.h:31
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
Definition gSROService.h:30
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
Definition gSROData.h:20
std::uint32_t GSROCrateId
Definition gSROData.h:18
std::uint64_t GSROEventId
Definition gSROData.h:19
event
Per-crate bounds preventing asynchronous delivery from consuming unlimited memory.
Definition gSROCrate.h:29
std::size_t queue_messages
Maximum queued payload/progress messages; producers wait when the queue is full.
Definition gSROCrate.h:31
std::size_t queue_bytes
Maximum accounted payload bytes in the input queue; producers wait until their payload fits.
Definition gSROCrate.h:33
std::size_t pending_bytes
Maximum accounted payload bytes awaiting time ordering; exceeding this fails the crate.
Definition gSROCrate.h:37
std::size_t pending_payloads
Maximum payloads awaiting time ordering; exceeding this fails the crate.
Definition gSROCrate.h:35
Input-delivery state passed to the implementation at orderly shutdown.
Definition gSROData.h:94
std::optional< GSROTime > safe_time
Definition gSROData.h:96
One worker-produced contribution, transferred by move to its destination crate.
Definition gSROData.h:58
GSROEventId event_id
Definition gSROData.h:60
std::unique_ptr< const GSROData > data
Definition gSROData.h:63
GSROCrateId crate_id
Definition gSROData.h:59