Refactor radar system
This commit is contained in:
@@ -32,10 +32,7 @@ struct RadarConfig {
|
||||
// Radar device identity and runtime mode.
|
||||
std::string model = "librevna";
|
||||
std::string serial{};
|
||||
std::string remote_host = "127.0.0.1";
|
||||
std::uint32_t remote_port = 50209;
|
||||
DriverMode driver_mode = DriverMode::Mock;
|
||||
float mock_signal_hz = 5'000'000.0F;
|
||||
RadarSweepSettings sweep{};
|
||||
};
|
||||
|
||||
@@ -134,7 +131,7 @@ struct GprRxGeometry {
|
||||
};
|
||||
|
||||
struct GprConfig {
|
||||
// Stable collection-level GPR processing settings.
|
||||
// Initial GPR settings; the data_processor can override them through live config.
|
||||
float relative_permittivity = 1.0F;
|
||||
std::vector<GprTxGeometry> tx_geometry{};
|
||||
std::vector<GprRxGeometry> rx_geometry{};
|
||||
@@ -154,5 +151,6 @@ struct RunConfig {
|
||||
};
|
||||
|
||||
[[nodiscard]] auto load_run_config(const std::string& path) -> RunConfig;
|
||||
void validate_gpr_config(const GprConfig& config, const RunConfig& run_config);
|
||||
|
||||
} // namespace radar::config
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
|
||||
namespace radar::config {
|
||||
// Returns true when stop interrupts the delay, checking at least every 50 ms.
|
||||
inline auto wait_or_stop(std::uint32_t delay_ms, const std::atomic<bool>& stop) -> bool {
|
||||
std::uint32_t waited = 0;
|
||||
while (waited < delay_ms && !stop.load(std::memory_order_relaxed)) {
|
||||
const auto chunk = std::min(50U, delay_ms - waited);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(chunk));
|
||||
waited += chunk;
|
||||
}
|
||||
return stop.load(std::memory_order_relaxed);
|
||||
}
|
||||
} // namespace radar::config
|
||||
@@ -65,7 +65,18 @@ using Json = nlohmann::json;
|
||||
if (!value.is_number()) {
|
||||
throw std::runtime_error("Expected JSON number at " + context);
|
||||
}
|
||||
return value.get<double>();
|
||||
const auto numeric = value.get<double>();
|
||||
if (!std::isfinite(numeric)) {
|
||||
throw std::runtime_error("Expected finite number at " + context);
|
||||
}
|
||||
return numeric;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto number_to_f32(double value, const std::string& context) -> float {
|
||||
if (!std::isfinite(value) || std::fabs(value) > std::numeric_limits<float>::max()) {
|
||||
throw std::runtime_error("Value out of finite float32 range at " + context);
|
||||
}
|
||||
return static_cast<float>(value);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto number_to_u32(double value, const std::string& context) -> std::uint32_t {
|
||||
@@ -74,7 +85,7 @@ using Json = nlohmann::json;
|
||||
}
|
||||
|
||||
const auto rounded = std::round(value);
|
||||
if (std::fabs(value - rounded) > 0.000001) {
|
||||
if (value != rounded) {
|
||||
throw std::runtime_error("Expected integer value at " + context);
|
||||
}
|
||||
return static_cast<std::uint32_t>(rounded);
|
||||
@@ -82,7 +93,7 @@ using Json = nlohmann::json;
|
||||
|
||||
[[nodiscard]] auto number_to_i32(double value, const std::string& context) -> std::int32_t {
|
||||
const auto rounded = std::round(value);
|
||||
if (std::fabs(value - rounded) > 0.000001) {
|
||||
if (value != rounded) {
|
||||
throw std::runtime_error("Expected integer value at " + context);
|
||||
}
|
||||
|
||||
@@ -135,7 +146,7 @@ using Json = nlohmann::json;
|
||||
|
||||
[[nodiscard]] auto optional_f32(const Json& object, const std::string& key, float fallback) -> float {
|
||||
if (const auto* value = optional_field(object, key); value != nullptr) {
|
||||
return static_cast<float>(as_number(*value, key));
|
||||
return number_to_f32(as_number(*value, key), key);
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
@@ -173,8 +184,8 @@ using Json = nlohmann::json;
|
||||
}
|
||||
|
||||
PreprocessNotchBandConfig band{};
|
||||
band.low_hz = static_cast<float>(as_number((*band_array)[0], context + ".bands_hz[][0]"));
|
||||
band.high_hz = static_cast<float>(as_number((*band_array)[1], context + ".bands_hz[][1]"));
|
||||
band.low_hz = number_to_f32(as_number((*band_array)[0], context + ".bands_hz[][0]"), context);
|
||||
band.high_hz = number_to_f32(as_number((*band_array)[1], context + ".bands_hz[][1]"), context);
|
||||
notch.bands_hz.push_back(band);
|
||||
}
|
||||
}
|
||||
@@ -242,6 +253,9 @@ using Json = nlohmann::json;
|
||||
}
|
||||
|
||||
void validate_radar_sweep(const RadarSweepSettings& sweep) {
|
||||
if (sweep.start_hz < 0.0F || sweep.stop_hz < 0.0F || !(sweep.if_bandwidth_hz > 0.0F)) {
|
||||
throw std::runtime_error("radar.sweep frequencies must be non-negative and IF bandwidth must be > 0");
|
||||
}
|
||||
if (sweep.points == 0U) {
|
||||
throw std::runtime_error("radar.sweep.points must be > 0");
|
||||
}
|
||||
@@ -297,32 +311,6 @@ void validate_combo_key(const ipc::ComboKey& key, const RunConfig& config) {
|
||||
}
|
||||
}
|
||||
|
||||
void validate_gpr_config(const GprConfig& config, const RunConfig& run_config) {
|
||||
if (!(config.relative_permittivity > 0.0F)) {
|
||||
throw std::runtime_error("gpr.relative_permittivity must be > 0");
|
||||
}
|
||||
|
||||
std::unordered_set<std::uint32_t> seen_tx_positions{};
|
||||
for (const auto& entry : config.tx_geometry) {
|
||||
if (entry.output_pos >= run_config.output_switch.positions) {
|
||||
throw std::runtime_error("gpr.tx_geometry output_pos is out of range");
|
||||
}
|
||||
if (!seen_tx_positions.insert(entry.output_pos).second) {
|
||||
throw std::runtime_error("gpr.tx_geometry contains duplicate output_pos");
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<std::uint32_t> seen_rx_positions{};
|
||||
for (const auto& entry : config.rx_geometry) {
|
||||
if (entry.input_pos >= run_config.input_switch.positions) {
|
||||
throw std::runtime_error("gpr.rx_geometry input_pos is out of range");
|
||||
}
|
||||
if (!seen_rx_positions.insert(entry.input_pos).second) {
|
||||
throw std::runtime_error("gpr.rx_geometry contains duplicate input_pos");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_gpr_config(const Json& object, const RunConfig& run_config) -> GprConfig {
|
||||
const auto* gpr_obj = as_object(object, "gpr");
|
||||
GprConfig config{};
|
||||
@@ -406,6 +394,9 @@ void validate_gpr_config(const GprConfig& config, const RunConfig& run_config) {
|
||||
if (endpoint.slot_size_bytes == 0U) {
|
||||
throw std::runtime_error("Ring slot_size_bytes must be > 0 for " + key);
|
||||
}
|
||||
if (endpoint.slot_size_bytes % 8U != 0U) {
|
||||
throw std::runtime_error("Ring slot_size_bytes must be a multiple of 8 for " + key);
|
||||
}
|
||||
if (endpoint.name.empty() || endpoint.name.front() != '/') {
|
||||
throw std::runtime_error("Ring name must start with '/' for " + key);
|
||||
}
|
||||
@@ -415,6 +406,39 @@ void validate_gpr_config(const GprConfig& config, const RunConfig& run_config) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void validate_gpr_config(const GprConfig& config, const RunConfig& run_config) {
|
||||
if (!std::isfinite(config.relative_permittivity) || !(config.relative_permittivity > 0.0F)) {
|
||||
throw std::runtime_error("gpr.relative_permittivity must be > 0");
|
||||
}
|
||||
|
||||
std::unordered_set<std::uint32_t> seen_tx_positions{};
|
||||
for (const auto& entry : config.tx_geometry) {
|
||||
if (!std::isfinite(entry.x_m) || !std::isfinite(entry.y_m) || !std::isfinite(entry.z_m)) {
|
||||
throw std::runtime_error("gpr.tx_geometry coordinates must be finite");
|
||||
}
|
||||
if (entry.output_pos >= run_config.output_switch.positions) {
|
||||
throw std::runtime_error("gpr.tx_geometry output_pos is out of range");
|
||||
}
|
||||
if (!seen_tx_positions.insert(entry.output_pos).second) {
|
||||
throw std::runtime_error("gpr.tx_geometry contains duplicate output_pos");
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<std::uint32_t> seen_rx_positions{};
|
||||
for (const auto& entry : config.rx_geometry) {
|
||||
if (!std::isfinite(entry.x_m) || !std::isfinite(entry.y_m) || !std::isfinite(entry.z_m)) {
|
||||
throw std::runtime_error("gpr.rx_geometry coordinates must be finite");
|
||||
}
|
||||
if (entry.input_pos >= run_config.input_switch.positions) {
|
||||
throw std::runtime_error("gpr.rx_geometry input_pos is out of range");
|
||||
}
|
||||
if (!seen_rx_positions.insert(entry.input_pos).second) {
|
||||
throw std::runtime_error("gpr.rx_geometry contains duplicate input_pos");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto load_run_config(const std::string& path) -> RunConfig {
|
||||
std::ifstream stream(path);
|
||||
if (!stream.is_open()) {
|
||||
@@ -440,30 +464,21 @@ auto load_run_config(const std::string& path) -> RunConfig {
|
||||
const auto* radar_obj = as_object(required_field(*root_obj, "radar"), "radar");
|
||||
config.radar.model = optional_string(*radar_obj, "model", "librevna");
|
||||
config.radar.serial = optional_string(*radar_obj, "serial", "");
|
||||
config.radar.remote_host = optional_string(*radar_obj, "remote_host", "127.0.0.1");
|
||||
config.radar.remote_port = optional_u32(*radar_obj, "remote_port", 50209);
|
||||
if (config.radar.remote_host.empty()) {
|
||||
throw std::runtime_error("radar.remote_host must not be empty");
|
||||
}
|
||||
if (config.radar.remote_port == 0U || config.radar.remote_port > 65535U) {
|
||||
throw std::runtime_error("radar.remote_port must be in 1..65535");
|
||||
}
|
||||
config.radar.driver_mode = parse_driver_mode(optional_string(*radar_obj, "driver_mode", "mock"));
|
||||
config.radar.mock_signal_hz = optional_f32(*radar_obj, "mock_signal_hz", 5'000'000.0F);
|
||||
|
||||
const auto* sweep_obj = as_object(required_field(*radar_obj, "sweep"), "radar.sweep");
|
||||
config.radar.sweep.start_hz =
|
||||
static_cast<float>(as_number(required_field(*sweep_obj, "start_hz"), "radar.sweep.start_hz"));
|
||||
number_to_f32(as_number(required_field(*sweep_obj, "start_hz"), "radar.sweep.start_hz"), "radar.sweep.start_hz");
|
||||
config.radar.sweep.stop_hz =
|
||||
static_cast<float>(as_number(required_field(*sweep_obj, "stop_hz"), "radar.sweep.stop_hz"));
|
||||
number_to_f32(as_number(required_field(*sweep_obj, "stop_hz"), "radar.sweep.stop_hz"), "radar.sweep.stop_hz");
|
||||
if (const auto* points_value = optional_field(*sweep_obj, "points"); points_value != nullptr) {
|
||||
config.radar.sweep.points =
|
||||
number_to_u32(as_number(*points_value, "radar.sweep.points"), "radar.sweep.points");
|
||||
} else if (config.radar.model != "kamil_adc") {
|
||||
throw std::runtime_error("Missing required config field: points");
|
||||
}
|
||||
config.radar.sweep.if_bandwidth_hz = static_cast<float>(
|
||||
as_number(required_field(*sweep_obj, "if_bandwidth_hz"), "radar.sweep.if_bandwidth_hz")
|
||||
config.radar.sweep.if_bandwidth_hz = number_to_f32(
|
||||
as_number(required_field(*sweep_obj, "if_bandwidth_hz"), "radar.sweep.if_bandwidth_hz"), "radar.sweep.if_bandwidth_hz"
|
||||
);
|
||||
config.radar.sweep.power_dbm = optional_f32(*sweep_obj, "stimulus_power_dbm", -10.0F);
|
||||
validate_radar_sweep(config.radar.sweep);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -26,7 +27,8 @@ struct ComboKey {
|
||||
|
||||
struct ComboKeyHash {
|
||||
[[nodiscard]] auto operator()(const ComboKey& key) const noexcept -> std::size_t {
|
||||
return (static_cast<std::size_t>(key.input_pos) << 32U) ^ static_cast<std::size_t>(key.output_pos);
|
||||
const auto packed = (static_cast<std::uint64_t>(key.input_pos) << 32U) | key.output_pos;
|
||||
return std::hash<std::uint64_t>{}(packed);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ class ShmRing {
|
||||
[[nodiscard]] auto slot_stride_bytes() const -> std::size_t;
|
||||
[[nodiscard]] auto slot_header(std::uint64_t sequence) const -> SlotHeader*;
|
||||
[[nodiscard]] auto slot_payload(SlotHeader* slot) const -> std::uint8_t*;
|
||||
void advance_read_seq(std::uint64_t target);
|
||||
|
||||
void close();
|
||||
static void validate_name(const std::string& name);
|
||||
|
||||
@@ -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