Refactor radar system
This commit is contained in:
@@ -95,7 +95,7 @@ class BinaryReader {
|
||||
|
||||
private:
|
||||
void ensure_available(std::size_t size) const {
|
||||
if (offset_ + size > bytes_.size()) {
|
||||
if (size > bytes_.size() - offset_) {
|
||||
throw std::runtime_error("Unexpected end of serialized payload");
|
||||
}
|
||||
}
|
||||
@@ -132,9 +132,8 @@ void write_trace_block(BinaryWriter& writer, const SweepTraceBlock& trace) {
|
||||
// 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()) {
|
||||
void require_count_fits(std::uint64_t count, std::size_t min_bytes_each, BinaryReader& reader) {
|
||||
if (min_bytes_each != 0U && count > reader.remaining_bytes() / min_bytes_each) {
|
||||
throw std::runtime_error("Declared element count exceeds remaining payload bytes");
|
||||
}
|
||||
}
|
||||
@@ -257,6 +256,7 @@ void write_trace_result_payload(BinaryWriter& writer, const ResultPayload& paylo
|
||||
|
||||
auto read_trace_result_payload(BinaryReader& reader, ResultPayload* payload) -> void {
|
||||
const auto point_count = reader.read<std::uint32_t>();
|
||||
require_count_fits(point_count, 12U, reader);
|
||||
|
||||
payload->frequency_hz.reserve(point_count);
|
||||
payload->trace.reserve(point_count);
|
||||
@@ -296,10 +296,12 @@ void write_image_result_payload(BinaryWriter& writer, const ResultPayload& paylo
|
||||
auto read_image_result_payload(BinaryReader& reader, ResultPayload* payload) -> void {
|
||||
const auto x_count = reader.read<std::uint32_t>();
|
||||
const auto y_count = reader.read<std::uint32_t>();
|
||||
const auto value_count = static_cast<std::uint64_t>(x_count) * y_count;
|
||||
require_count_fits(value_count + x_count + y_count, sizeof(float), reader);
|
||||
|
||||
payload->image_x_axis.reserve(x_count);
|
||||
payload->image_y_axis.reserve(y_count);
|
||||
payload->image_values.reserve(static_cast<std::size_t>(x_count) * static_cast<std::size_t>(y_count));
|
||||
payload->image_values.reserve(static_cast<std::size_t>(value_count));
|
||||
|
||||
for (std::uint32_t index = 0; index < x_count; ++index) {
|
||||
payload->image_x_axis.push_back(reader.read<float>());
|
||||
@@ -307,7 +309,7 @@ auto read_image_result_payload(BinaryReader& reader, ResultPayload* payload) ->
|
||||
for (std::uint32_t index = 0; index < y_count; ++index) {
|
||||
payload->image_y_axis.push_back(reader.read<float>());
|
||||
}
|
||||
for (std::size_t index = 0; index < static_cast<std::size_t>(x_count) * static_cast<std::size_t>(y_count); ++index) {
|
||||
for (std::size_t index = 0; index < value_count; ++index) {
|
||||
payload->image_values.push_back(reader.read<float>());
|
||||
}
|
||||
}
|
||||
@@ -334,9 +336,14 @@ void write_table_result_payload(BinaryWriter& writer, const ResultPayload& paylo
|
||||
auto read_table_result_payload(BinaryReader& reader, ResultPayload* payload) -> void {
|
||||
const auto column_count = reader.read<std::uint32_t>();
|
||||
const auto row_count = reader.read<std::uint32_t>();
|
||||
if (column_count == 0U && row_count != 0U) {
|
||||
throw std::runtime_error("Result table cannot have rows without columns");
|
||||
}
|
||||
const auto value_count = static_cast<std::uint64_t>(column_count) * row_count;
|
||||
require_count_fits(value_count, sizeof(float), reader);
|
||||
payload->table_columns = column_count;
|
||||
payload->table_values.reserve(static_cast<std::size_t>(column_count) * static_cast<std::size_t>(row_count));
|
||||
for (std::size_t index = 0; index < static_cast<std::size_t>(column_count) * static_cast<std::size_t>(row_count); ++index) {
|
||||
payload->table_values.reserve(static_cast<std::size_t>(value_count));
|
||||
for (std::size_t index = 0; index < value_count; ++index) {
|
||||
payload->table_values.push_back(reader.read<float>());
|
||||
}
|
||||
}
|
||||
@@ -402,6 +409,7 @@ void write_result_block(BinaryWriter& writer, const ResultBlock& block) {
|
||||
block.combo.output_pos = reader.read<std::uint32_t>();
|
||||
|
||||
const auto payload_count = reader.read<std::uint32_t>();
|
||||
require_count_fits(payload_count, 3U, reader);
|
||||
block.payloads.reserve(payload_count);
|
||||
for (std::uint32_t index = 0; index < payload_count; ++index) {
|
||||
block.payloads.push_back(read_result_payload(reader));
|
||||
@@ -478,11 +486,13 @@ auto deserialize_result_collection(std::span<const std::uint8_t> bytes) -> Resul
|
||||
|
||||
const auto collection_payload_count = reader.read<std::uint32_t>();
|
||||
const auto block_count = reader.read<std::uint32_t>();
|
||||
require_count_fits(collection_payload_count, 3U, reader);
|
||||
collection.collection_payloads.reserve(collection_payload_count);
|
||||
for (std::uint32_t index = 0; index < collection_payload_count; ++index) {
|
||||
collection.collection_payloads.push_back(read_result_payload(reader));
|
||||
}
|
||||
|
||||
require_count_fits(block_count, 12U, reader);
|
||||
collection.blocks.reserve(block_count);
|
||||
for (std::uint32_t index = 0; index < block_count; ++index) {
|
||||
collection.blocks.push_back(read_result_block(reader));
|
||||
|
||||
@@ -30,7 +30,7 @@ struct alignas(64) ShmRing::Header {
|
||||
std::atomic<std::uint64_t> dropped{};
|
||||
};
|
||||
|
||||
struct alignas(16) ShmRing::SlotHeader {
|
||||
struct alignas(8) ShmRing::SlotHeader {
|
||||
std::uint32_t payload_size = 0;
|
||||
std::uint32_t reserved = 0;
|
||||
std::uint64_t sequence = 0;
|
||||
@@ -42,6 +42,7 @@ constexpr std::string_view kRingMagic = "RDRRING2";
|
||||
constexpr std::uint32_t kRingVersion = 1;
|
||||
constexpr auto kHeaderInitWaitTimeout = std::chrono::milliseconds(1000);
|
||||
constexpr auto kHeaderInitPollInterval = std::chrono::milliseconds(2);
|
||||
constexpr std::uint64_t kMaxMappedSize = 1ULL << 40U;
|
||||
|
||||
[[nodiscard]] auto checked_u64_diff(std::uint64_t left, std::uint64_t right) -> std::uint64_t {
|
||||
return left >= right ? left - right : 0;
|
||||
@@ -134,6 +135,22 @@ auto ShmRing::open_or_create(
|
||||
if (slot_size_bytes == 0U) {
|
||||
throw std::runtime_error("Ring slot size must be > 0");
|
||||
}
|
||||
if (slot_size_bytes % alignof(SlotHeader) != 0U) {
|
||||
throw std::runtime_error("Ring slot size must be a multiple of 8");
|
||||
}
|
||||
|
||||
const auto stride = sizeof(SlotHeader) + static_cast<std::uint64_t>(slot_size_bytes);
|
||||
if (stride > (kMaxMappedSize - sizeof(Header)) / capacity) {
|
||||
throw std::runtime_error("Shared memory ring exceeds 1 TiB for " + name);
|
||||
}
|
||||
const auto mapped_size_u64 = sizeof(Header) + stride * capacity;
|
||||
if (mapped_size_u64 > kMaxMappedSize
|
||||
|| mapped_size_u64 > std::numeric_limits<std::size_t>::max()
|
||||
|| mapped_size_u64 > static_cast<std::uint64_t>(std::numeric_limits<off_t>::max())) {
|
||||
throw std::runtime_error("Shared memory ring geometry overflows for " + name);
|
||||
}
|
||||
const auto mapped_size = static_cast<std::size_t>(mapped_size_u64);
|
||||
|
||||
|
||||
bool created = false;
|
||||
int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0660);
|
||||
@@ -149,13 +166,28 @@ auto ShmRing::open_or_create(
|
||||
|
||||
ScopedFd scoped_fd(fd);
|
||||
|
||||
const auto slot_stride = sizeof(SlotHeader) + static_cast<std::size_t>(slot_size_bytes);
|
||||
const auto mapped_size = sizeof(Header) + slot_stride * capacity;
|
||||
|
||||
if (created) {
|
||||
if (ftruncate(fd, static_cast<off_t>(mapped_size)) != 0) {
|
||||
throw errno_message("Failed to resize shared memory ring", name);
|
||||
}
|
||||
} else {
|
||||
// shm_open publishes the name before the creator calls ftruncate. Mapping
|
||||
// that zero/short file and reading its header would terminate us with SIGBUS.
|
||||
const auto deadline = std::chrono::steady_clock::now() + kHeaderInitWaitTimeout;
|
||||
while (true) {
|
||||
struct stat info {};
|
||||
if (fstat(fd, &info) != 0) {
|
||||
throw errno_message("Failed to stat shared memory ring", name);
|
||||
}
|
||||
if (info.st_size >= 0 && static_cast<std::size_t>(info.st_size) >= mapped_size) {
|
||||
break;
|
||||
}
|
||||
if (std::chrono::steady_clock::now() >= deadline) {
|
||||
throw std::runtime_error("Shared memory ring size is too small for " + name);
|
||||
}
|
||||
std::this_thread::sleep_for(kHeaderInitPollInterval);
|
||||
}
|
||||
}
|
||||
|
||||
void* mapped = mmap(nullptr, mapped_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
@@ -167,7 +199,9 @@ auto ShmRing::open_or_create(
|
||||
auto* header = static_cast<Header*>(mapped);
|
||||
|
||||
if (created) {
|
||||
std::memset(mapped, 0, mapped_size);
|
||||
// ftruncate already supplies zero-filled slots; leave them demand-paged
|
||||
// instead of touching the full ring before any payload is published.
|
||||
std::memset(mapped, 0, sizeof(Header));
|
||||
std::memcpy(header->magic, kRingMagic.data(), kRingMagic.size());
|
||||
header->version = kRingVersion;
|
||||
header->capacity = capacity;
|
||||
@@ -319,14 +353,13 @@ auto ShmRing::push(std::span<const std::uint8_t> payload) -> bool {
|
||||
}
|
||||
|
||||
auto* slot = slot_header(write_seq);
|
||||
// Seqlock publish: write the payload + size FIRST, then store the slot sequence
|
||||
// LAST (after a release fence). A consumer that observes sequence == write_seq+1
|
||||
// is therefore guaranteed a fully-written payload. (Publishing the sequence before
|
||||
// the copy, as before, let a consumer copy a half-written slot.)
|
||||
// Invalidate the old publication before overwriting its bytes: a slow consumer
|
||||
// can otherwise accept a mixture of old and new data under the old sequence.
|
||||
std::atomic_ref(slot->sequence).store(0U, std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
slot->payload_size = static_cast<std::uint32_t>(payload.size());
|
||||
std::memcpy(slot_payload(slot), payload.data(), payload.size());
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
slot->sequence = write_seq + 1U;
|
||||
std::atomic_ref(slot->sequence).store(write_seq + 1U, std::memory_order_release);
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
header_->write_seq.store(write_seq + 1U, std::memory_order_release);
|
||||
@@ -345,9 +378,9 @@ auto ShmRing::pop(std::vector<std::uint8_t>& payload) -> bool {
|
||||
}
|
||||
|
||||
auto* slot = slot_header(read_seq);
|
||||
if (slot->sequence != read_seq + 1U) {
|
||||
if (std::atomic_ref(slot->sequence).load(std::memory_order_acquire) != read_seq + 1U) {
|
||||
// Slot not published for this seq, or already overwritten (lapped). Resync.
|
||||
header_->read_seq.store(write_seq, std::memory_order_release);
|
||||
advance_read_seq(write_seq);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -355,7 +388,7 @@ auto ShmRing::pop(std::vector<std::uint8_t>& payload) -> bool {
|
||||
if (payload_size > header_->slot_size_bytes) {
|
||||
// Corrupt/torn slot: skip it (resync) rather than throw — a single bad slot
|
||||
// must never abort the long-running consumer.
|
||||
header_->read_seq.store(write_seq, std::memory_order_release);
|
||||
advance_read_seq(write_seq);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -366,15 +399,21 @@ auto ShmRing::pop(std::vector<std::uint8_t>& payload) -> bool {
|
||||
// Seqlock verify: if the slot sequence changed during the copy, the producer
|
||||
// lapped us mid-read and the payload is torn — discard and resync.
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
if (slot->sequence != read_seq + 1U) {
|
||||
header_->read_seq.store(write_seq, std::memory_order_release);
|
||||
if (std::atomic_ref(slot->sequence).load(std::memory_order_acquire) != read_seq + 1U) {
|
||||
advance_read_seq(write_seq);
|
||||
return false;
|
||||
}
|
||||
|
||||
header_->read_seq.store(read_seq + 1U, std::memory_order_release);
|
||||
advance_read_seq(read_seq + 1U);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShmRing::advance_read_seq(std::uint64_t target) {
|
||||
auto current = header_->read_seq.load(std::memory_order_relaxed);
|
||||
while (current < target && !header_->read_seq.compare_exchange_weak(
|
||||
current, target, std::memory_order_release, std::memory_order_relaxed)) {}
|
||||
}
|
||||
|
||||
auto ShmRing::slot_stride_bytes() const -> std::size_t {
|
||||
return sizeof(SlotHeader) + header_->slot_size_bytes;
|
||||
}
|
||||
@@ -422,18 +461,18 @@ void ShmRing::validate_geometry(const Header& header, std::size_t mapped_size, c
|
||||
if (slot_size_bytes == 0U) {
|
||||
throw std::runtime_error("Shared memory ring slot size is zero for " + name);
|
||||
}
|
||||
if (slot_size_bytes % alignof(SlotHeader) != 0U) {
|
||||
throw std::runtime_error("Shared memory ring slot size must be a multiple of 8 for " + name);
|
||||
}
|
||||
|
||||
constexpr auto kMax = std::numeric_limits<std::size_t>::max();
|
||||
const auto slot_stride = sizeof(SlotHeader) + static_cast<std::size_t>(slot_size_bytes);
|
||||
// Guard each multiply/add against std::size_t overflow before computing expected_size.
|
||||
if (slot_stride > kMax / static_cast<std::size_t>(capacity)) {
|
||||
const auto stride = sizeof(SlotHeader) + static_cast<std::uint64_t>(slot_size_bytes);
|
||||
if (stride > (kMaxMappedSize - sizeof(Header)) / capacity) {
|
||||
throw std::runtime_error("Shared memory ring exceeds 1 TiB for " + name);
|
||||
}
|
||||
const auto expected_size = sizeof(Header) + stride * capacity;
|
||||
if (expected_size > kMaxMappedSize || expected_size > std::numeric_limits<std::size_t>::max()) {
|
||||
throw std::runtime_error("Shared memory ring geometry overflows for " + name);
|
||||
}
|
||||
const auto slots_size = slot_stride * static_cast<std::size_t>(capacity);
|
||||
if (slots_size > kMax - sizeof(Header)) {
|
||||
throw std::runtime_error("Shared memory ring geometry overflows for " + name);
|
||||
}
|
||||
const auto expected_size = sizeof(Header) + slots_size;
|
||||
|
||||
if (mapped_size < expected_size) {
|
||||
throw std::runtime_error("Shared memory ring size is too small for " + name);
|
||||
|
||||
Reference in New Issue
Block a user