20 : crate_id(id), make_resources(std::move(factory)), limits(bounds),
21 on_delivery(std::move(delivered)), on_failure(std::move(failed)) {
22 if (!make_resources || !limits.queue_messages || !limits.queue_bytes ||
23 !limits.pending_payloads || !limits.pending_bytes) {
24 throw std::invalid_argument(
"SRO crate requires a resource factory and positive buffer limits");
31 throw std::invalid_argument(
"SRO payload is null or addressed to another crate");
33 const auto bytes = payload.
data->size_bytes();
35 throw std::length_error(
"SRO payload exceeds the crate input queue byte limit");
37 const auto total_bytes = bytes +
sizeof(
GSROPayload);
38 std::unique_lock lock(mutex);
39 space_available.wait(lock, [&] {
40 return closing || failure ||
41 (queue.size() < limits.queue_messages && total_bytes <= limits.queue_bytes - queue_bytes);
44 if (submitted_safe_time && payload.
time < *submitted_safe_time) {
45 throw std::invalid_argument(
"SRO payload precedes an already submitted safe-time boundary");
47 queue.emplace_back(QueuedPayload{std::move(payload), total_bytes});
48 queue_bytes += total_bytes;
50 input_available.notify_one();
54 std::unique_lock lock(mutex);
55 space_available.wait(lock, [&] {
return closing || failure || queue.size() < limits.queue_messages; });
57 if (submitted_safe_time && safe_time < *submitted_safe_time) {
58 throw std::invalid_argument(
"SRO safe-time boundaries must not decrease");
60 queue.emplace_back(safe_time);
61 submitted_safe_time = safe_time;
63 input_available.notify_one();
68 std::lock_guard lock(mutex);
69 if (!closing && !failure) {
70 if (context.
safe_time && submitted_safe_time && *context.
safe_time < *submitted_safe_time) {
71 throw std::invalid_argument(
"SRO final boundary precedes an already submitted boundary");
74 end_context = context;
78 input_available.notify_one();
79 space_available.notify_all();
82 void cancel(std::exception_ptr error) {
83 if (!error) {
throw std::invalid_argument(
"SRO cancellation requires an exception"); }
85 std::lock_guard lock(mutex);
86 if (!failure) { failure = error; }
89 input_available.notify_one();
90 space_available.notify_all();
95 if (thread.joinable()) { thread.join(); }
100 std::lock_guard lock(mutex);
101 if (failure) { std::rethrow_exception(failure); }
105 struct QueuedPayload {
109 using Message = std::variant<QueuedPayload, GSROTime>;
110 using Key = std::tuple<GSROTime, GSROEventId, std::uint64_t>;
111 using Pending = std::map<Key, QueuedPayload>;
113 void check_open()
const {
114 if (failure) { std::rethrow_exception(failure); }
115 if (closing) {
throw std::logic_error(
"SRO crate is closed to input"); }
118 void consume_before(Pending& pending, std::size_t& bytes, GSROCratePlugin& plugin,
119 std::optional<GSROTime> boundary) {
120 while (!pending.empty() && (!boundary || std::get<0>(pending.begin()->first) < *boundary)) {
122 auto entry = pending.extract(pending.begin());
123 bytes -= entry.mapped().bytes;
128 void run() noexcept {
131 auto resources = make_resources();
132 if (!resources.sink || !resources.plugin) {
133 throw std::invalid_argument(
"SRO resource factory returned a null plugin or sink");
136 std::size_t pending_bytes = 0;
137 std::optional<GSROTime> processed_safe_time;
142 std::unique_lock lock(mutex);
143 input_available.wait(lock, [&] {
return closing || !queue.empty(); });
144 if (failure) { std::rethrow_exception(failure); }
146 context = *end_context;
149 message = std::move(queue.front());
151 if (
const auto* item = std::get_if<QueuedPayload>(&message)) { queue_bytes -= item->bytes; }
153 space_available.notify_all();
154 if (
auto* item = std::get_if<QueuedPayload>(&message)) {
155 if (pending.size() >= limits.pending_payloads ||
156 item->bytes > limits.pending_bytes - pending_bytes) {
157 throw std::length_error(
"SRO pending input limit exceeded while waiting for safe time");
159 const auto event = item->payload.event_id;
160 const auto sequence = item->payload.sequence;
161 const Key key{item->payload.time,
event, sequence};
162 const auto bytes = item->bytes;
163 if (!pending.emplace(key, std::move(*item)).second) {
164 throw std::invalid_argument(
"Duplicate SRO payload ordering key");
166 pending_bytes += bytes;
167 if (on_delivery) { on_delivery(event, sequence); }
170 const auto boundary = std::get<GSROTime>(message);
171 consume_before(pending, pending_bytes, *resources.plugin, boundary);
172 resources.plugin->advance_time(boundary);
173 processed_safe_time = boundary;
176 if (context.
safe_time && (!processed_safe_time || *context.
safe_time > *processed_safe_time)) {
177 consume_before(pending, pending_bytes, *resources.plugin, context.
safe_time);
178 resources.plugin->advance_time(*context.
safe_time);
180 consume_before(pending, pending_bytes, *resources.plugin, std::nullopt);
182 resources.plugin->finish_run(context);
184 resources.sink->finish_output();
188 std::deque<Message> rejected;
189 std::exception_ptr error;
191 std::lock_guard lock(mutex);
192 if (!failure) { failure = std::current_exception(); }
195 rejected.swap(queue);
198 space_available.notify_all();
200 try { on_failure(error); }
208 const GSROCrateLimits limits;
211 mutable std::mutex mutex;
212 std::condition_variable input_available;
213 std::condition_variable space_available;
214 std::deque<Message> queue;
215 std::size_t queue_bytes = 0;
216 std::optional<GSROTime> submitted_safe_time;
217 std::optional<GSROEndContext> end_context;
218 std::exception_ptr failure;
219 bool closing =
false;
220 jthread_alias thread;
Input-delivery state passed to the implementation at orderly shutdown.
std::optional< GSROTime > safe_time