Skip to content

Commit 048f368

Browse files
committed
refactor: optimize ring buffer with MaybeUninit and track poller registrations
- Replace Vec<Option<T>> with RingBuffer using MaybeUninit for memory efficiency - Add proper Drop implementation for RingBuffer to clean up initialized slots - Track poller registrations in TopicPoller with poller_count - Skip eventfd write when no pollers are registered - Update tests to use new RingBuffer API Signed-off-by: ncerzzk <huangcmzzk@gmail.com>
1 parent a049790 commit 048f368

2 files changed

Lines changed: 101 additions & 9 deletions

File tree

src/lib.rs

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
//! and poll-based notifications built on `mio` and `eventfd`.
55
66
use std::collections::HashMap;
7-
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
8-
use std::sync::atomic::{AtomicU32, Ordering};
7+
use std::mem::MaybeUninit;
8+
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
9+
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
910
use std::sync::{Arc, LazyLock, Mutex, RwLock};
1011
use std::time::Duration;
1112

@@ -15,6 +16,57 @@ use mio::{Events, Poll, Token};
1516
pub trait MorbDataType: Send + Sync + 'static + Clone {}
1617
impl<T> MorbDataType for T where T: Send + Sync + 'static + Clone {}
1718

19+
struct RingBuffer<T> {
20+
slots: Box<[MaybeUninit<T>]>,
21+
initialized: Box<[bool]>,
22+
}
23+
24+
impl<T> RingBuffer<T> {
25+
fn new(size: usize) -> Self {
26+
let mut slots = Vec::with_capacity(size);
27+
slots.resize_with(size, MaybeUninit::uninit);
28+
29+
Self {
30+
slots: slots.into_boxed_slice(),
31+
initialized: vec![false; size].into_boxed_slice(),
32+
}
33+
}
34+
35+
fn write(&mut self, index: usize, value: T) {
36+
if self.initialized[index] {
37+
unsafe {
38+
self.slots[index].assume_init_drop();
39+
}
40+
}
41+
42+
self.slots[index].write(value);
43+
self.initialized[index] = true;
44+
}
45+
46+
fn read_cloned(&self, index: usize) -> Option<T>
47+
where
48+
T: Clone,
49+
{
50+
if !self.initialized[index] {
51+
return None;
52+
}
53+
54+
Some(unsafe { self.slots[index].assume_init_ref().clone() })
55+
}
56+
}
57+
58+
impl<T> Drop for RingBuffer<T> {
59+
fn drop(&mut self) {
60+
for (index, initialized) in self.initialized.iter().copied().enumerate() {
61+
if initialized {
62+
unsafe {
63+
self.slots[index].assume_init_drop();
64+
}
65+
}
66+
}
67+
}
68+
}
69+
1870
/// Stores all globally registered topics.
1971
pub struct TopicManager {
2072
topics: HashMap<String, Box<dyn std::any::Any + Send + Sync>>,
@@ -92,7 +144,7 @@ impl<T: MorbDataType> Publisher<T> {
92144
let mut fifo = self.topic.fifo.lock().unwrap();
93145
let index = self.topic.generation.load(Ordering::Acquire) as usize
94146
% (self.topic.queue_size as usize);
95-
fifo[index] = Some(data);
147+
fifo.write(index, data);
96148
self.topic.generation.fetch_add(1, Ordering::AcqRel);
97149
}
98150
self.topic.notify();
@@ -128,14 +180,20 @@ impl<T: MorbDataType> Subscriber<T> {
128180
}
129181
let index = (self.sub_generation as usize) % (self.topic.queue_size as usize);
130182
self.sub_generation += 1;
131-
self.topic.fifo.lock().unwrap()[index].clone()
183+
self.topic.fifo.lock().unwrap().read_cloned(index)
132184
}
133185
}
134186

135187
/// Waits for updates from one or more topics.
136188
pub struct TopicPoller {
137189
poll: Poll,
138190
events: Events,
191+
registrations: Vec<TopicPollerRegistration>,
192+
}
193+
194+
struct TopicPollerRegistration {
195+
eventfd: RawFd,
196+
poller_count: Arc<AtomicUsize>,
139197
}
140198

141199
impl Default for TopicPoller {
@@ -150,6 +208,7 @@ impl TopicPoller {
150208
Self {
151209
poll: Poll::new().unwrap(),
152210
events: Events::with_capacity(1024),
211+
registrations: Vec::new(),
153212
}
154213
}
155214

@@ -159,12 +218,31 @@ impl TopicPoller {
159218
self.poll.registry(),
160219
topic.token,
161220
mio::Interest::READABLE,
162-
)
221+
)?;
222+
223+
topic.poller_count.fetch_add(1, Ordering::Relaxed);
224+
self.registrations.push(TopicPollerRegistration {
225+
eventfd: topic.eventfd.as_raw_fd(),
226+
poller_count: Arc::clone(&topic.poller_count),
227+
});
228+
229+
Ok(())
163230
}
164231

165232
/// Removes a topic from the poller.
166233
pub fn remove_topic<T: MorbDataType>(&mut self, topic: &Topic<T>) -> std::io::Result<()> {
167-
mio::unix::SourceFd(&topic.eventfd.as_raw_fd()).deregister(self.poll.registry())
234+
mio::unix::SourceFd(&topic.eventfd.as_raw_fd()).deregister(self.poll.registry())?;
235+
236+
if let Some(index) = self
237+
.registrations
238+
.iter()
239+
.position(|registration| registration.eventfd == topic.eventfd.as_raw_fd())
240+
{
241+
let registration = self.registrations.swap_remove(index);
242+
registration.poller_count.fetch_sub(1, Ordering::Relaxed);
243+
}
244+
245+
Ok(())
168246
}
169247

170248
/// Waits until at least one registered topic becomes readable or the timeout expires.
@@ -178,14 +256,23 @@ impl TopicPoller {
178256
}
179257
}
180258

259+
impl Drop for TopicPoller {
260+
fn drop(&mut self) {
261+
for registration in self.registrations.drain(..) {
262+
registration.poller_count.fetch_sub(1, Ordering::Relaxed);
263+
}
264+
}
265+
}
266+
181267
/// A named message channel with fixed-size retention and poll notifications.
182268
pub struct Topic<T: MorbDataType> {
183269
name: String,
184-
fifo: Mutex<Vec<Option<T>>>,
270+
fifo: Mutex<RingBuffer<T>>,
185271
pub(crate) generation: AtomicU32,
186272
queue_size: u16,
187273
token: mio::Token,
188274
eventfd: OwnedFd,
275+
poller_count: Arc<AtomicUsize>,
189276
}
190277

191278
impl<T: MorbDataType> Topic<T> {
@@ -194,15 +281,20 @@ impl<T: MorbDataType> Topic<T> {
194281

195282
Self {
196283
name,
197-
fifo: Mutex::new(vec![None; queue_size as usize]),
284+
fifo: Mutex::new(RingBuffer::new(queue_size as usize)),
198285
generation: AtomicU32::new(0),
199286
queue_size,
200287
token: Token(topic_id),
201288
eventfd: unsafe { OwnedFd::from_raw_fd(libc::eventfd(0, libc::EFD_NONBLOCK)) },
289+
poller_count: Arc::new(AtomicUsize::new(0)),
202290
}
203291
}
204292

205293
fn notify(&self) {
294+
if self.poller_count.load(Ordering::Relaxed) == 0 {
295+
return;
296+
}
297+
206298
let value = usize::from(self.token) as u64;
207299
unsafe {
208300
libc::write(

src/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ fn subscriber_read_after_new_generation_observes_committed_data() {
215215
let unlock_barrier = writer_may_unlock_fifo.clone();
216216
let handle = thread::spawn(move || {
217217
let mut fifo = topic_for_writer.fifo.lock().unwrap();
218-
fifo[0] = Some(123_u32);
218+
fifo.write(0, 123_u32);
219219
topic_for_writer.generation.store(1, Ordering::Release);
220220
published_flag.store(true, Ordering::Release);
221221

0 commit comments

Comments
 (0)