some fixes

This commit is contained in:
Ayzen
2026-06-04 18:33:38 +03:00
parent eacea436a4
commit 22942d9dc9
26 changed files with 1352 additions and 153 deletions
@@ -127,12 +127,26 @@ void write_trace_block(BinaryWriter& writer, const SweepTraceBlock& trace) {
}
}
// Guard a wire-supplied element count before reserve(): a torn ring slot or buggy
// producer can present a count near 2^32, and reserve() of that many elements would
// request gigabytes and crash the process (length_error/bad_alloc/OOM-kill). The
// payload itself is already bounded by the ring slot size, so any count whose
// minimum encoding cannot fit in the bytes still available is structurally invalid.
void require_count_fits(std::uint32_t count, std::size_t min_bytes_each, BinaryReader& reader) {
if (min_bytes_each != 0U
&& static_cast<std::uint64_t>(count) * min_bytes_each > reader.remaining_bytes()) {
throw std::runtime_error("Declared element count exceeds remaining payload bytes");
}
}
[[nodiscard]] auto read_trace_block(BinaryReader& reader) -> SweepTraceBlock {
SweepTraceBlock trace{};
trace.combo.input_pos = reader.read<std::uint32_t>();
trace.combo.output_pos = reader.read<std::uint32_t>();
const auto point_count = reader.read<std::uint32_t>();
// Each point encodes frequency (4B) + s11 (8B) + s21 (8B) = 20 bytes.
require_count_fits(point_count, 20U, reader);
trace.frequency_hz.reserve(point_count);
trace.s11.reserve(point_count);
trace.s21.reserve(point_count);
@@ -183,6 +197,8 @@ void write_trace_collection(BinaryWriter& writer, std::uint32_t magic, const Raw
collection.monotonic_ns = reader.read<std::uint64_t>();
const auto trace_count = reader.read<std::uint32_t>();
// Each trace block is at least 12 bytes (combo 8B + point_count 4B, zero points).
require_count_fits(trace_count, 12U, reader);
collection.traces.reserve(trace_count);
for (std::uint32_t index = 0; index < trace_count; ++index) {
collection.traces.push_back(read_trace_block(reader));
@@ -3,6 +3,8 @@
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <span>
#include <stdexcept>
#include <string_view>
@@ -138,13 +140,24 @@ void DataPreprocessor::run(const std::atomic<bool>& stop_requested) {
std::vector<std::uint8_t> serialized_raw{};
ipc::RawSweepCollection raw_collection{};
std::uint64_t error_count = 0;
while (!stop_requested.load(std::memory_order_relaxed)) {
if (!try_pop_raw_collection(serialized_raw, &raw_collection)) {
continue;
try {
if (!try_pop_raw_collection(serialized_raw, &raw_collection)) {
continue;
}
const auto preprocessed_collection = preprocess_collection(raw_collection);
publish_preprocessed_collection(preprocessed_collection);
} catch (const std::exception& exc) {
// A single malformed or transiently-bad collection must not kill the
// long-running preprocessor: drop it and keep serving the next sweep.
// Logging is throttled so a persistent error cannot flood the log.
if (error_count % 100 == 0) {
std::cerr << "data_preprocessor: dropped collection after error (count="
<< (error_count + 1) << "): " << exc.what() << '\n';
}
++error_count;
}
const auto preprocessed_collection = preprocess_collection(raw_collection);
publish_preprocessed_collection(preprocessed_collection);
}
}
@@ -3,6 +3,7 @@
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <span>
#include <stdexcept>
#include <thread>
@@ -61,71 +62,84 @@ void DataProcessor::run(const std::atomic<bool>& stop_requested) {
const std::size_t history_limit = replay_history_limit(config_);
std::uint64_t last_replayed_revision = live_config_loader_.revision();
std::uint64_t last_applied_history_command_seq = 0;
std::uint64_t error_count = 0;
while (!stop_requested.load(std::memory_order_relaxed)) {
const auto live_config_raw = live_config_loader_.refresh_if_needed();
const auto live_config = resolve_effective_live_config(live_config_raw);
const auto live_revision = live_config_loader_.revision();
auto& processor = resolve_processor(live_config);
try {
const auto live_config_raw = live_config_loader_.refresh_if_needed();
const auto live_config = resolve_effective_live_config(live_config_raw);
const auto live_revision = live_config_loader_.revision();
auto& processor = resolve_processor(live_config);
if (live_revision != last_replayed_revision) {
if (live_config.history_command_seq > last_applied_history_command_seq) {
if (live_config.history_command == HistoryCommand::RemoveLast) {
if (!preprocessed_history.empty()) {
preprocessed_history.pop_back();
if (live_revision != last_replayed_revision) {
if (live_config.history_command_seq > last_applied_history_command_seq) {
if (live_config.history_command == HistoryCommand::RemoveLast) {
if (!preprocessed_history.empty()) {
preprocessed_history.pop_back();
}
} else if (live_config.history_command == HistoryCommand::ClearAll) {
preprocessed_history.clear();
}
} else if (live_config.history_command == HistoryCommand::ClearAll) {
preprocessed_history.clear();
last_applied_history_command_seq = live_config.history_command_seq;
}
last_applied_history_command_seq = live_config.history_command_seq;
}
if (!live_config.reprocess_current_result) {
// Socket-fed speed updates should affect only future preprocessed collections,
// not replay the current history entry.
} else if (should_replay_entire_history(live_config)) {
for (std::size_t index = 0; index < preprocessed_history.size(); ++index) {
if (!live_config.reprocess_current_result) {
// Socket-fed speed updates should affect only future preprocessed collections,
// not replay the current history entry.
} else if (should_replay_entire_history(live_config)) {
for (std::size_t index = 0; index < preprocessed_history.size(); ++index) {
const auto replay_result = process_collection(
preprocessed_history[index],
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), index),
processor,
live_config
);
publish_result_collection(replay_result, results_ring_);
publish_locator(replay_result, live_config);
}
} else if (!preprocessed_history.empty()) {
const auto replay_result = process_collection(
preprocessed_history[index],
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), index),
preprocessed_history.back(),
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), preprocessed_history.size() - 1U),
processor,
live_config
);
publish_result_collection(replay_result, results_ring_);
publish_locator(replay_result, live_config);
}
} else if (!preprocessed_history.empty()) {
const auto replay_result = process_collection(
last_replayed_revision = live_revision;
}
if (preprocessed_ring_.pop(bytes)) {
auto preprocessed = ipc::deserialize_preprocessed_collection(bytes);
preprocessed_history.push_back(std::move(preprocessed));
while (preprocessed_history.size() > history_limit) {
preprocessed_history.erase(preprocessed_history.begin());
}
const auto result_collection = process_collection(
preprocessed_history.back(),
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), preprocessed_history.size() - 1U),
processor,
live_config
);
publish_result_collection(replay_result, results_ring_);
publish_locator(replay_result, live_config);
}
last_replayed_revision = live_revision;
}
if (preprocessed_ring_.pop(bytes)) {
auto preprocessed = ipc::deserialize_preprocessed_collection(bytes);
preprocessed_history.push_back(std::move(preprocessed));
while (preprocessed_history.size() > history_limit) {
preprocessed_history.erase(preprocessed_history.begin());
publish_result_collection(result_collection, results_ring_);
publish_locator(result_collection, live_config);
continue;
}
const auto result_collection = process_collection(
preprocessed_history.back(),
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), preprocessed_history.size() - 1U),
processor,
live_config
);
publish_result_collection(result_collection, results_ring_);
publish_locator(result_collection, live_config);
continue;
std::this_thread::sleep_for(std::chrono::milliseconds(config_.runtime.idle_sleep_ms));
} catch (const std::exception& exc) {
// A single bad collection (torn ring slot, decode/processing error) must
// not kill the long-running processor: drop it and keep going. Throttle
// logging and pause briefly so a persistent error cannot busy-spin/flood.
if (error_count % 100 == 0) {
std::cerr << "data_processor: dropped collection after error (count="
<< (error_count + 1) << "): " << exc.what() << '\n';
}
++error_count;
std::this_thread::sleep_for(std::chrono::milliseconds(config_.runtime.idle_sleep_ms));
}
std::this_thread::sleep_for(std::chrono::milliseconds(config_.runtime.idle_sleep_ms));
}
}
@@ -131,9 +131,15 @@ ClientQueue::ClientQueue(std::size_t capacity) : capacity_(std::max<std::size_t>
auto ClientQueue::try_push(std::vector<std::uint8_t> packet) -> bool {
{
std::lock_guard<std::mutex> guard(mutex_);
if (closed_ || queue_.size() >= capacity_) {
if (closed_) {
return false;
}
// Latest-wins backpressure: never block or disconnect a slow client. When the
// queue is full, drop the oldest queued packet(s) so the client always advances
// toward the freshest result. Bounded memory; freshness over completeness.
while (queue_.size() >= capacity_) {
queue_.pop_front();
}
queue_.push_back(std::move(packet));
}
not_empty_.notify_one();
@@ -195,10 +201,9 @@ void ClientSession::enqueue(std::vector<std::uint8_t> packet) {
if (stop_requested_.load(std::memory_order_acquire)) {
return;
}
if (!queue_.try_push(std::move(packet))) {
log_warning("disconnecting client " + peer_name_ + " after outbound queue overflow");
request_stop();
}
// try_push only fails when the queue is closed (session already shutting down); a
// full queue now drops its oldest entry instead of disconnecting a slow client.
(void)queue_.try_push(std::move(packet));
}
void ClientSession::request_stop() {
@@ -424,7 +429,15 @@ void TcpServer::acceptor_loop() {
&peer_len
);
if (client_fd < 0) {
if (errno == EINTR) {
if (errno == EINTR || errno == ECONNABORTED) {
continue;
}
if (errno == EMFILE || errno == ENFILE || errno == ENOBUFS || errno == ENOMEM) {
// Transient resource exhaustion (often our own finished sessions
// still holding fds): reap them, back off briefly, and keep
// accepting. The acceptor must never die and silently stop serving.
reap_finished_clients();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
// Listening socket closed during shutdown produces EBADF/EINVAL; bail.