mirror of
https://github.com/rdmsr/zag.git
synced 2026-11-01 15:48:21 +00:00
ke/log_ring: cleanup
This commit is contained in:
+178
-67
@@ -1,25 +1,32 @@
|
|||||||
//! Lock-free multi-producer multi-consumer ring buffer designed for kernel logging.
|
//! Lock-free multi-producer multi-consumer ring buffer designed
|
||||||
|
//! for kernel logging.
|
||||||
//!
|
//!
|
||||||
//! # Overview
|
//! # Overview
|
||||||
//!
|
//!
|
||||||
//! `RingBuffer` is a concurrent ring buffer that allows multiple writers and readers
|
//! `RingBuffer` is a concurrent ring buffer that allows multiple writers
|
||||||
//! to operate simultaneously without locks, making it safe to use from interrupt and
|
//! and readers to operate concurrently without locks, and also supports re-
|
||||||
//! NMI context. It is modelled after Linux's `printk_ringbuffer`, but is simpler (no "committed" state).
|
//! entrancy, making it safe to use from interrupt context.
|
||||||
|
//! It is modelled after Linux's `printk_ringbuffer`, but is simplified,
|
||||||
|
//! there is no support for pr_cont or the "committed" state.
|
||||||
//! See linux's printk_ringbuffer.c for more details.
|
//! See linux's printk_ringbuffer.c for more details.
|
||||||
//!
|
//!
|
||||||
//! The ring buffer is composed of three parallel arrays:
|
//! The ring buffer is composed of three parallel arrays:
|
||||||
//!
|
//!
|
||||||
//! - `descs` - descriptor ring: tracks the state and data location of each record
|
//! - `descs`: tracks the state and data location of each record
|
||||||
//! - `data` - data ring: byte storage for the actual messages
|
//! - `data`: byte storage for the actual messages
|
||||||
//! - `infos` - array containing metadata: stores sequence number, timestamp, and length per record
|
//! - `infos`: metadata: sequence number, timestamp, and length per record
|
||||||
//!
|
//!
|
||||||
//! `infos` is indexed by descriptor IDs, so it doesn't *really* count as a ring.
|
//! `infos` is indexed by descriptor ID, so it doesn't *really* count as a ring.
|
||||||
//! `head_id` is the ID of the newest descriptor, the last slot claimed by a writer.
|
//! `head_id` is the ID of the newest descriptor, the last slot claimed by a
|
||||||
//! `tail_id` is the ID of the oldest descriptor, the oldest slot which is the next to be evicted.
|
//! writer.
|
||||||
|
//! `tail_id` is the ID of the oldest descriptor, the oldest slot which is the
|
||||||
|
//! next to be evicted.
|
||||||
//! `tail_id` must always point to a free or published descriptor.
|
//! `tail_id` must always point to a free or published descriptor.
|
||||||
//! The number of live descriptors at any moment is `head_id - tail_id`,
|
//! The number of live descriptors at any moment is `head_id - tail_id`,
|
||||||
//! when this reaches the maximum descriptor count (`desc_count`), the ring is full and `tail_id` must be advanced.
|
//! when this reaches the maximum descriptor count (`desc_count`),
|
||||||
//! `head_lpos` and `tail_lpos` are the logical positions of the next data block to be written and the oldest data block, respectively.
|
//! the ring is full and `tail_id` must be advanced.
|
||||||
|
//! `head_lpos` and `tail_lpos` are the logical positions of the next data block
|
||||||
|
//! to be written and the oldest data block, respectively.
|
||||||
//!
|
//!
|
||||||
//! # Descriptor states
|
//! # Descriptor states
|
||||||
//!
|
//!
|
||||||
@@ -30,9 +37,9 @@
|
|||||||
//! `Reserved` A writer has claimed this slot and is actively writing.
|
//! `Reserved` A writer has claimed this slot and is actively writing.
|
||||||
//! `Published` The record is complete and visible to readers.
|
//! `Published` The record is complete and visible to readers.
|
||||||
//! `Free` The slot has been recycled and is available for reuse.
|
//! `Free` The slot has been recycled and is available for reuse.
|
||||||
//! `Miss` Pseudo-state: the slot has been recycled to a newer generation.
|
//! `Miss` Pseudo-state: the slot has been reused in a newer generation.
|
||||||
//! Returned when the ID embedded in a descriptor state does not match
|
//! Returned when the ID embedded in a descriptor state does not
|
||||||
//! the expected ID, indicating the record is gone.
|
//! match the expected ID, indicating the record is gone.
|
||||||
//!
|
//!
|
||||||
//! # IDs and logical positions
|
//! # IDs and logical positions
|
||||||
//!
|
//!
|
||||||
@@ -47,7 +54,8 @@
|
|||||||
//! to the same physical slot can always be distinguished by comparing the full
|
//! to the same physical slot can always be distinguished by comparing the full
|
||||||
//! integer, which prevents ABA bugs.
|
//! integer, which prevents ABA bugs.
|
||||||
//! The `State` atomic packs both the descriptor ID and its state into a single
|
//! The `State` atomic packs both the descriptor ID and its state into a single
|
||||||
//! `usize`, allowing both to be read and updated atomically via CAS (a CAS is used to prevent ABA where the generation could overflow):
|
//! `usize`, allowing both to be read and updated atomically via CAS
|
||||||
|
//! (a CAS is used to prevent ABA where the generation could overflow):
|
||||||
//!
|
//!
|
||||||
//! [ id (upper 62 bits) | state (lower 2 bits) ] (on 64-bit platforms)
|
//! [ id (upper 62 bits) | state (lower 2 bits) ] (on 64-bit platforms)
|
||||||
//!
|
//!
|
||||||
@@ -57,6 +65,8 @@
|
|||||||
//! If an allocation would cross the boundary, the block is placed at offset 0 of
|
//! If an allocation would cross the boundary, the block is placed at offset 0 of
|
||||||
//! the next generation and a stub (containing only the descriptor ID) is left at
|
//! the next generation and a stub (containing only the descriptor ID) is left at
|
||||||
//! the original position so the data ring can be walked linearly during eviction.
|
//! the original position so the data ring can be walked linearly during eviction.
|
||||||
|
//!
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const r = @import("root");
|
const r = @import("root");
|
||||||
const rtl = @import("rtl");
|
const rtl = @import("rtl");
|
||||||
@@ -76,7 +86,6 @@ const DescState = enum(u2) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const usize_bits = @bitSizeOf(usize);
|
const usize_bits = @bitSizeOf(usize);
|
||||||
// Id is a monotonically increasing counter. `id & desc_mask` maps to the physical array index.
|
|
||||||
const Id = @Int(.unsigned, usize_bits - 2);
|
const Id = @Int(.unsigned, usize_bits - 2);
|
||||||
const IdSigned = @Int(.signed, usize_bits - 2);
|
const IdSigned = @Int(.signed, usize_bits - 2);
|
||||||
|
|
||||||
@@ -118,7 +127,8 @@ pub const ReserveError = error{ NoSpace, ReservationFailed, ZeroSize };
|
|||||||
pub const ReadError = error{NotYetAvailable};
|
pub const ReadError = error{NotYetAvailable};
|
||||||
|
|
||||||
// Logical positions are always aligned to `@sizeOf(usize)` (even numbers).
|
// Logical positions are always aligned to `@sizeOf(usize)` (even numbers).
|
||||||
// Thus, 1 (an odd number) is guaranteed to never collide with a real data block position.
|
// Thus, 1 (an odd number) is guaranteed to never collide with a real data
|
||||||
|
// block position.
|
||||||
const lpos_no_data = 1;
|
const lpos_no_data = 1;
|
||||||
|
|
||||||
pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
||||||
@@ -146,7 +156,8 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
// Return the exact Id of the descriptor that occupied this physical slot one generation ago.
|
// Return the exact Id of the descriptor that occupied this physical slot
|
||||||
|
// one generation ago.
|
||||||
fn prev_wrap(id: Id) Id {
|
fn prev_wrap(id: Id) Id {
|
||||||
return id -% desc_count;
|
return id -% desc_count;
|
||||||
}
|
}
|
||||||
@@ -180,15 +191,19 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check_size(size: usize) bool {
|
fn check_size(size: usize) bool {
|
||||||
// If the size is larger than half of the data buffer, it can never fit in the ring buffer.
|
// If the size is larger than half of the data buffer,
|
||||||
|
// it can never fit in the ring buffer.
|
||||||
return size <= data_size / 2;
|
return size <= data_size / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true if `lpos_current` has not yet reached `lpos_next`.
|
// Return true if `lpos_current` has not yet reached `lpos_next`.
|
||||||
// Uses wrapping subtraction so that:
|
// Uses wrapping subtraction so that:
|
||||||
// - if lpos_current > lpos_next (another CPU already advanced past the target), returns false.
|
// - if current > next (another CPU already advanced past the target),
|
||||||
// - if lpos_current == lpos_next (already at target), the -% 1 wraps to usize max, returns false.
|
// returns false.
|
||||||
// - if the distance exceeds data_size (should never happen), returns false.
|
// - if current == next (already at target), the -1 wraps to usize max,
|
||||||
|
// returns false.
|
||||||
|
// - if the distance exceeds data_size (should never happen),
|
||||||
|
// returns false.
|
||||||
fn need_more_space(lpos_current: usize, lpos_next: usize) bool {
|
fn need_more_space(lpos_current: usize, lpos_next: usize) bool {
|
||||||
return lpos_next -% lpos_current -% 1 < data_size;
|
return lpos_next -% lpos_current -% 1 < data_size;
|
||||||
}
|
}
|
||||||
@@ -229,11 +244,18 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_desc_finalized(self: *Self, id: Id, seq: u64, desc_out: *Desc) !void {
|
fn read_desc_finalized(
|
||||||
|
self: *Self,
|
||||||
|
id: Id,
|
||||||
|
seq: u64,
|
||||||
|
desc_out: *Desc,
|
||||||
|
) !void {
|
||||||
const res = self.read_desc(id);
|
const res = self.read_desc(id);
|
||||||
desc_out.* = res.desc;
|
desc_out.* = res.desc;
|
||||||
|
|
||||||
if (res.state == .Miss or res.state == .Reserved or (res.seq != seq)) {
|
if (res.state == .Miss or res.state == .Reserved or
|
||||||
|
(res.seq != seq))
|
||||||
|
{
|
||||||
return error.Invalid;
|
return error.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,10 +265,21 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
|
|
||||||
fn free_desc(self: *Self, id: Id) void {
|
fn free_desc(self: *Self, id: Id) void {
|
||||||
var desc = self.to_desc(id);
|
var desc = self.to_desc(id);
|
||||||
const expected_state: State = .{ .state = .Published, .id = id };
|
const expected_state: State = .{
|
||||||
const new_state: State = .{ .state = .Free, .id = id };
|
.state = .Published,
|
||||||
|
.id = id,
|
||||||
|
};
|
||||||
|
const new_state: State = .{
|
||||||
|
.state = .Free,
|
||||||
|
.id = id,
|
||||||
|
};
|
||||||
|
|
||||||
_ = desc.state.cmpxchgStrong(expected_state, new_state, .release, .monotonic);
|
_ = desc.state.cmpxchgStrong(
|
||||||
|
expected_state,
|
||||||
|
new_state,
|
||||||
|
.release,
|
||||||
|
.monotonic,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn free_data(self: *Self, begin: usize, end: usize) ?usize {
|
fn free_data(self: *Self, begin: usize, end: usize) ?usize {
|
||||||
@@ -256,8 +289,15 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
while (need_more_space(cur_begin, end)) {
|
while (need_more_space(cur_begin, end)) {
|
||||||
const blk = self.to_block(cur_begin);
|
const blk = self.to_block(cur_begin);
|
||||||
|
|
||||||
// Loading the ID here is purposely racey because we check if the descriptor is valid anyway.
|
// Loading the ID here is purposely racey because
|
||||||
const id: Id = @truncate(@atomicLoad(usize, @as(*usize, @ptrCast(&blk.id)), .monotonic));
|
// we check if the descriptor is valid anyway.
|
||||||
|
const id: Id = @truncate(
|
||||||
|
@atomicLoad(
|
||||||
|
usize,
|
||||||
|
@as(*usize, @ptrCast(&blk.id)),
|
||||||
|
.monotonic,
|
||||||
|
),
|
||||||
|
);
|
||||||
const res = self.read_desc(id);
|
const res = self.read_desc(id);
|
||||||
desc = res.desc;
|
desc = res.desc;
|
||||||
|
|
||||||
@@ -268,14 +308,16 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
},
|
},
|
||||||
.Published => {
|
.Published => {
|
||||||
if (desc.blk_pos.begin != cur_begin) {
|
if (desc.blk_pos.begin != cur_begin) {
|
||||||
// The descriptor does not point back to the data block, we must've lost a race.
|
// The descriptor does not point back to
|
||||||
|
// the data block, we must've lost a race.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
self.free_desc(id);
|
self.free_desc(id);
|
||||||
},
|
},
|
||||||
.Free => {
|
.Free => {
|
||||||
if (desc.blk_pos.begin != cur_begin) {
|
if (desc.blk_pos.begin != cur_begin) {
|
||||||
// The descriptor does not point back to the data block, we must've lost a race.
|
// The descriptor does not point back to
|
||||||
|
// the data block, we must've lost a race.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -301,14 +343,20 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
// Invalidate all data blocks before the new tail position.
|
// Invalidate all data blocks before the new tail position.
|
||||||
if (self.free_data(cur_tail, new_tail_lpos)) |new_tail| {
|
if (self.free_data(cur_tail, new_tail_lpos)) |new_tail| {
|
||||||
// Successfully freed some blocks, try advancing the tail.
|
// Successfully freed some blocks, try advancing the tail.
|
||||||
if (self.tail_lpos.cmpxchgWeak(cur_tail, new_tail, .release, .monotonic)) |real_cur_tail| {
|
if (self.tail_lpos.cmpxchgWeak(
|
||||||
|
cur_tail,
|
||||||
|
new_tail,
|
||||||
|
.release,
|
||||||
|
.monotonic,
|
||||||
|
)) |real_cur_tail| {
|
||||||
cur_tail = real_cur_tail;
|
cur_tail = real_cur_tail;
|
||||||
std.atomic.spinLoopHint();
|
std.atomic.spinLoopHint();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Failed to free the data, which means we either lost a race or the descriptor is still in use.
|
// Failed to free the data, which means we either lost a race
|
||||||
|
// or the descriptor is still in use.
|
||||||
// Reload the tail position to try again.
|
// Reload the tail position to try again.
|
||||||
const new_tail = self.tail_lpos.load(.acquire);
|
const new_tail = self.tail_lpos.load(.acquire);
|
||||||
if (new_tail == cur_tail) {
|
if (new_tail == cur_tail) {
|
||||||
@@ -334,12 +382,14 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
self.free_desc(tail_id);
|
self.free_desc(tail_id);
|
||||||
},
|
},
|
||||||
.Reserved => {
|
.Reserved => {
|
||||||
// The descriptor is still reserved, we can't do anything but fail the reservation.
|
// The descriptor is still reserved,
|
||||||
|
// we can't do anything but fail the reservation.
|
||||||
return error.ReservationFailed;
|
return error.ReservationFailed;
|
||||||
},
|
},
|
||||||
.Miss => {
|
.Miss => {
|
||||||
if (tail_desc.state.load(.monotonic).id == prev_wrap(tail_id)) {
|
if (tail_desc.state.load(.monotonic).id == prev_wrap(tail_id)) {
|
||||||
// This must mean that the descriptor is currently getting reserved by another writer.
|
// This must mean that the descriptor is currently
|
||||||
|
// getting reserved by another writer.
|
||||||
return error.ReservationFailed;
|
return error.ReservationFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,23 +397,32 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
.Free => {
|
.Free => {
|
||||||
// The descriptor is already free, we can just advance the tail.
|
// The descriptor is already free, we can just advance the
|
||||||
|
// tail.
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate data blocks
|
// Invalidate data blocks
|
||||||
try self.data_advance_tail(tail_desc.blk_pos.end);
|
try self.data_advance_tail(tail_desc.blk_pos.end);
|
||||||
|
|
||||||
// Read the next descriptor after tail_id, because the tail must always be in a finalized or free state.
|
// Read the next descriptor after tail_id, because the tail must
|
||||||
|
// always be in a finalized or free state.
|
||||||
const next_res = self.read_desc(tail_id +% 1);
|
const next_res = self.read_desc(tail_id +% 1);
|
||||||
|
|
||||||
if (next_res.state == .Published or next_res.state == .Free) {
|
if (next_res.state == .Published or next_res.state == .Free) {
|
||||||
// Advance the tail to the next descriptor.
|
// Advance the tail to the next descriptor.
|
||||||
// release: push all previous state changes before the tail update, guaranteeing the new tail is visible after all evictions.
|
// release: push all previous state changes before the tail update,
|
||||||
_ = self.tail_id.cmpxchgStrong(@as(usize, tail_id), @as(usize, tail_id +% 1), .release, .monotonic);
|
// guaranteeing the new tail is visible after all evictions.
|
||||||
|
_ = self.tail_id.cmpxchgStrong(
|
||||||
|
@as(usize, tail_id),
|
||||||
|
@as(usize, tail_id +% 1),
|
||||||
|
.release,
|
||||||
|
.monotonic,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// The next descriptor is not in the desired state.
|
// The next descriptor is not in the desired state.
|
||||||
// One possibility is that the tail was pushed by another CPU, in which case we are good.
|
// One possibility is that the tail was pushed by another CPU,
|
||||||
|
// in which case we are good.
|
||||||
const tail_id_cur = self.tail_id.load(.acquire);
|
const tail_id_cur = self.tail_id.load(.acquire);
|
||||||
if (tail_id_cur != tail_id) {
|
if (tail_id_cur != tail_id) {
|
||||||
// Another CPU pushed the tail, we are good.
|
// Another CPU pushed the tail, we are good.
|
||||||
@@ -388,12 +447,19 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
const tail_id = self.tail_id.load(.acquire);
|
const tail_id = self.tail_id.load(.acquire);
|
||||||
|
|
||||||
if (prev_id == tail_id) {
|
if (prev_id == tail_id) {
|
||||||
// Wrapped back to the tail, need to make space by advancing it.
|
// Wrapped back to the tail, need to make space by
|
||||||
|
// advancing it.
|
||||||
try self.advance_tail(prev_id);
|
try self.advance_tail(prev_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ordering: acq_rel to claim head and publish, acquire on failure to retry.
|
// Ordering: acq_rel to claim head and publish,
|
||||||
if (self.head_id.cmpxchgWeak(head_id, new_id, .acq_rel, .acquire)) |val| {
|
// acquire on failure to retry.
|
||||||
|
if (self.head_id.cmpxchgWeak(
|
||||||
|
head_id,
|
||||||
|
new_id,
|
||||||
|
.acq_rel,
|
||||||
|
.acquire,
|
||||||
|
)) |val| {
|
||||||
head_id = @truncate(val);
|
head_id = @truncate(val);
|
||||||
std.atomic.spinLoopHint();
|
std.atomic.spinLoopHint();
|
||||||
} else {
|
} else {
|
||||||
@@ -403,16 +469,28 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
|
|
||||||
const desc = self.to_desc(new_id);
|
const desc = self.to_desc(new_id);
|
||||||
|
|
||||||
// Ordering: monotonic is safe here because acq_rel on head_id published our claim.
|
// Ordering: monotonic is safe here because acq_rel on head_id
|
||||||
|
// published our claim.
|
||||||
const prev_state = desc.state.load(.monotonic);
|
const prev_state = desc.state.load(.monotonic);
|
||||||
const raw: usize = @bitCast(prev_state);
|
const raw: usize = @bitCast(prev_state);
|
||||||
|
|
||||||
if (raw != 0 and get_desc_state(prev_id, prev_state) != .Free) {
|
if (raw != 0 and
|
||||||
|
get_desc_state(prev_id, prev_state) != .Free)
|
||||||
|
{
|
||||||
return error.ReservationFailed;
|
return error.ReservationFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ordering: release ensures all setup is published before making the descriptor reserved.
|
// Ordering: release ensures all setup is published before making
|
||||||
if (desc.state.cmpxchgStrong(prev_state, .{ .state = .Reserved, .id = new_id }, .release, .monotonic) != null) {
|
// the descriptor reserved.
|
||||||
|
if (desc.state.cmpxchgStrong(
|
||||||
|
prev_state,
|
||||||
|
.{
|
||||||
|
.state = .Reserved,
|
||||||
|
.id = new_id,
|
||||||
|
},
|
||||||
|
.release,
|
||||||
|
.monotonic,
|
||||||
|
) != null) {
|
||||||
return error.ReservationFailed;
|
return error.ReservationFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +499,8 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
|
|
||||||
// Return true if the range [begin, end) crosses the buffer boundary.
|
// Return true if the range [begin, end) crosses the buffer boundary.
|
||||||
// Compares the generation counters (upper bits) of begin and end-1.
|
// Compares the generation counters (upper bits) of begin and end-1.
|
||||||
// The -% 1 ensures a block that fits exactly at the end does not count as wrapping.
|
// The -% 1 ensures a block that fits exactly at the end does not count
|
||||||
|
// as wrapping.
|
||||||
fn data_wraps(begin: usize, end: usize) bool {
|
fn data_wraps(begin: usize, end: usize) bool {
|
||||||
return (begin >> data_size_bits) != ((end -% 1) >> data_size_bits);
|
return (begin >> data_size_bits) != ((end -% 1) >> data_size_bits);
|
||||||
}
|
}
|
||||||
@@ -431,8 +510,9 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
const next_lpos = lpos +% size;
|
const next_lpos = lpos +% size;
|
||||||
|
|
||||||
// We never physically split a payload across the ring boundary.
|
// We never physically split a payload across the ring boundary.
|
||||||
// If it would cross the boundary, we advance the `lpos` to the next generation boundary
|
// If it would cross the boundary, we advance the `lpos` to the next
|
||||||
// so the data can sit contiguously at physical offset 0.
|
// generation boundary so the data can sit contiguously at
|
||||||
|
// physical offset 0.
|
||||||
if (data_wraps(begin_lpos, next_lpos)) {
|
if (data_wraps(begin_lpos, next_lpos)) {
|
||||||
return ((next_lpos >> data_size_bits) << data_size_bits) + size;
|
return ((next_lpos >> data_size_bits) << data_size_bits) + size;
|
||||||
}
|
}
|
||||||
@@ -462,7 +542,12 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Try to claim the new head position.
|
// Try to claim the new head position.
|
||||||
if (self.head_lpos.cmpxchgWeak(head_lpos, new_head_lpos, .acq_rel, .acquire)) |real_head_lpos| {
|
if (self.head_lpos.cmpxchgWeak(
|
||||||
|
head_lpos,
|
||||||
|
new_head_lpos,
|
||||||
|
.acq_rel,
|
||||||
|
.acquire,
|
||||||
|
)) |real_head_lpos| {
|
||||||
head_lpos = real_head_lpos;
|
head_lpos = real_head_lpos;
|
||||||
std.atomic.spinLoopHint();
|
std.atomic.spinLoopHint();
|
||||||
} else {
|
} else {
|
||||||
@@ -517,25 +602,33 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
|
|
||||||
/// Initialize and return a new ring buffer instance.
|
/// Initialize and return a new ring buffer instance.
|
||||||
pub fn init() Self {
|
pub fn init() Self {
|
||||||
// Start near usize max so that the first wrap of the ID/lpos space happens
|
// Start near usize max so that the first wrap of the ID/lpos space
|
||||||
// almost immediately, testing overflow handling early rather than after
|
// happens almost immediately, testing overflow handling early
|
||||||
// billions of records.
|
// rather than after billions of records.
|
||||||
const dummy_id: Id = 0 -% @as(Id, desc_count + 1);
|
const dummy_id: Id = 0 -% @as(Id, desc_count + 1);
|
||||||
const blk0: usize = -%@as(usize, data_size);
|
const blk0: usize = -%@as(usize, data_size);
|
||||||
|
|
||||||
var self: Self = std.mem.zeroes(Self);
|
var self: Self = std.mem.zeroes(Self);
|
||||||
|
|
||||||
// Place a dummy descriptor in the last slot. head_id and tail_id both start
|
// Place a dummy descriptor in the last slot. head_id and tail_id
|
||||||
// here so that the first reservation (head_id + 1) lands in slot 0, giving
|
// both start here so that the first reservation (head_id + 1) lands
|
||||||
// the first real record sequence number 0.
|
// in slot 0, giving the first real record sequence number 0.
|
||||||
self.descs[desc_count - 1].state.store(.{ .state = .Free, .id = dummy_id }, .monotonic);
|
self.descs[desc_count - 1].state.store(.{
|
||||||
self.descs[desc_count - 1].blk_pos = .{ .begin = lpos_no_data, .end = lpos_no_data };
|
.state = .Free,
|
||||||
|
.id = dummy_id,
|
||||||
|
}, .monotonic);
|
||||||
|
|
||||||
|
self.descs[desc_count - 1].blk_pos = .{
|
||||||
|
.begin = lpos_no_data,
|
||||||
|
.end = lpos_no_data,
|
||||||
|
};
|
||||||
|
|
||||||
self.head_id.store(dummy_id, .monotonic);
|
self.head_id.store(dummy_id, .monotonic);
|
||||||
self.tail_id.store(dummy_id, .monotonic);
|
self.tail_id.store(dummy_id, .monotonic);
|
||||||
|
|
||||||
// Make it -(desc_count) so that when the slot is first reserved and the sequence is advanced by desc_count,
|
// Make it -(desc_count) so that when the slot is first reserved and
|
||||||
// the first record gets sequence number 0.
|
// the sequence is advanced by desc_count, the first record gets
|
||||||
|
// sequence number 0.
|
||||||
self.infos[0].sequence = -%(@as(u64, desc_count));
|
self.infos[0].sequence = -%(@as(u64, desc_count));
|
||||||
|
|
||||||
self.head_lpos.store(blk0, .monotonic);
|
self.head_lpos.store(blk0, .monotonic);
|
||||||
@@ -565,17 +658,24 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
if (seq == 0 and (id & desc_mask) != 0) {
|
if (seq == 0 and (id & desc_mask) != 0) {
|
||||||
// First time this slot has been used.
|
// First time this slot has been used.
|
||||||
// Slot 0 never hits this branch, its seq is set to -(desc_count)
|
// Slot 0 never hits this branch, its seq is set to -(desc_count)
|
||||||
// in init() so that the else branch below produces seq=0 on its first use.
|
// in init() so that the else branch below produces seq=0 on
|
||||||
|
// its first use.
|
||||||
info.sequence = @as(u64, id & desc_mask);
|
info.sequence = @as(u64, id & desc_mask);
|
||||||
} else {
|
} else {
|
||||||
// Slot is being recycled (or is slot 0 on first use).
|
// Slot is being recycled (or is slot 0 on first use).
|
||||||
// Adding desc_count advances the sequence by one full ring wrap,
|
// Adding desc_count advances the sequence by one full ring wrap,
|
||||||
// keeping sequence numbers monotonically increasing across generations.
|
// keeping sequence numbers monotonically increasing across
|
||||||
|
// generations.
|
||||||
info.sequence = seq +% desc_count;
|
info.sequence = seq +% desc_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
reservation.buf = self.alloc_data(size, id, &desc.blk_pos) catch |e| {
|
reservation.buf = self.alloc_data(
|
||||||
// On failure, publish the descriptor as-is to clean up the reservation.
|
size,
|
||||||
|
id,
|
||||||
|
&desc.blk_pos,
|
||||||
|
) catch |e| {
|
||||||
|
// On failure, publish the descriptor as-is to clean up
|
||||||
|
// the reservation.
|
||||||
self.publish(reservation);
|
self.publish(reservation);
|
||||||
return e;
|
return e;
|
||||||
};
|
};
|
||||||
@@ -589,7 +689,18 @@ pub fn RingBuffer(data_size_bits: usize, avg_msg_bits: usize) type {
|
|||||||
pub fn publish(self: *Self, res: Reservation) void {
|
pub fn publish(self: *Self, res: Reservation) void {
|
||||||
const desc = self.to_desc(res.id);
|
const desc = self.to_desc(res.id);
|
||||||
|
|
||||||
_ = desc.state.cmpxchgStrong(.{ .state = .Reserved, .id = res.id }, .{ .state = .Published, .id = res.id }, .release, .monotonic);
|
_ = desc.state.cmpxchgStrong(
|
||||||
|
.{
|
||||||
|
.state = .Reserved,
|
||||||
|
.id = res.id,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.state = .Published,
|
||||||
|
.id = res.id,
|
||||||
|
},
|
||||||
|
.release,
|
||||||
|
.monotonic,
|
||||||
|
);
|
||||||
|
|
||||||
// Restore IRQ state after publishing the new entry.
|
// Restore IRQ state after publishing the new entry.
|
||||||
ki.impl.restore_interrupts(res.irq_state);
|
ki.impl.restore_interrupts(res.irq_state);
|
||||||
|
|||||||
Reference in New Issue
Block a user