Skip to content

Commit 58bcbb8

Browse files
d-e-s-odanielocfb
authored andcommitted
Loosen RingBuffer lifetime requirements
With commit 0068735 ("Fix Map leaking file descriptors when not managed by Object") we tightened the lifetime requirements surrounding RingBuffer and RingBufferBuilder objects. Specifically, the new contract was that the Map being passed to the RingBufferBuilder had to outlive said object as well as any constructed RingBuffer. In a conversation with Andrii Nakryiko <[email protected]>, he confirmed that libbpf's API contract is grab a reference of all file descriptors added to the ring buffer via ring_buffer__add(). As such, with this change we loosen lifetime requirements surround RingBuffer and RingBufferBuilder accordingly. Signed-off-by: Daniel Müller <[email protected]>
1 parent eba5dfa commit 58bcbb8

File tree

2 files changed

+62
-9
lines changed

2 files changed

+62
-9
lines changed

libbpf-rs/src/ringbuf.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ impl Debug for RingBufferCallback<'_> {
4949
/// between [`Program`][crate::Program]s and userspace. As of Linux 5.8, the
5050
/// `ringbuf` map is now preferred over the `perf buffer`.
5151
#[derive(Debug, Default)]
52-
pub struct RingBufferBuilder<'a> {
53-
fd_callbacks: Vec<(BorrowedFd<'a>, RingBufferCallback<'a>)>,
52+
pub struct RingBufferBuilder<'slf, 'cb> {
53+
fd_callbacks: Vec<(BorrowedFd<'slf>, RingBufferCallback<'cb>)>,
5454
}
5555

56-
impl<'a> RingBufferBuilder<'a> {
56+
impl<'slf, 'cb: 'slf> RingBufferBuilder<'slf, 'cb> {
5757
/// Create a new `RingBufferBuilder` object.
5858
pub fn new() -> Self {
5959
RingBufferBuilder {
@@ -69,9 +69,9 @@ impl<'a> RingBufferBuilder<'a> {
6969
///
7070
/// The callback provides a raw byte slice. You may find libraries such as
7171
/// [`plain`](https://crates.io/crates/plain) helpful.
72-
pub fn add<NewF>(&mut self, map: &'a MapHandle, callback: NewF) -> Result<&mut Self>
72+
pub fn add<NewF>(&mut self, map: &'slf MapHandle, callback: NewF) -> Result<&mut Self>
7373
where
74-
NewF: FnMut(&[u8]) -> i32 + 'a,
74+
NewF: FnMut(&[u8]) -> i32 + 'cb,
7575
{
7676
if map.map_type() != MapType::RingBuf {
7777
return Err(Error::InvalidInput("Must use a RingBuf map".into()));
@@ -82,7 +82,7 @@ impl<'a> RingBufferBuilder<'a> {
8282
}
8383

8484
/// Build a new [`RingBuffer`]. Must have added at least one ringbuf.
85-
pub fn build(self) -> Result<RingBuffer<'a>> {
85+
pub fn build(self) -> Result<RingBuffer<'cb>> {
8686
let mut cbs = vec![];
8787
let mut ptr: Option<NonNull<libbpf_sys::ring_buffer>> = None;
8888
let c_sample_cb: libbpf_sys::ring_buffer_sample_fn = Some(Self::call_sample_cb);
@@ -145,10 +145,10 @@ impl<'a> RingBufferBuilder<'a> {
145145
/// between [`Program`][crate::Program]s and userspace. As of Linux 5.8, the
146146
/// `ringbuf` map is now preferred over the `perf buffer`.
147147
#[derive(Debug)]
148-
pub struct RingBuffer<'a> {
148+
pub struct RingBuffer<'cb> {
149149
ptr: NonNull<libbpf_sys::ring_buffer>,
150150
#[allow(clippy::vec_box)]
151-
_cbs: Vec<Box<RingBufferCallback<'a>>>,
151+
_cbs: Vec<Box<RingBufferCallback<'cb>>>,
152152
}
153153

154154
impl RingBuffer<'_> {
@@ -199,7 +199,7 @@ mod test {
199199

200200
/// Check that `RingBuffer` is `Send`.
201201
#[test]
202-
fn perfbuffer_is_send() {
202+
fn ringbuffer_is_send() {
203203
fn test<T>()
204204
where
205205
T: Send,

libbpf-rs/tests/test.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(clippy::let_unit_value)]
2+
13
use std::collections::HashSet;
24
use std::ffi::c_void;
35
use std::fs;
@@ -891,6 +893,57 @@ fn test_sudo_object_ringbuf_closure() {
891893
assert_eq!(v2, 2);
892894
}
893895

896+
/// Check that `RingBuffer` works correctly even if the map file descriptors
897+
/// provided during construction are closed. This test validates that `libbpf`'s
898+
/// refcount behavior is correctly reflected in our `RingBuffer` lifetimes.
899+
#[test]
900+
fn test_sudo_object_ringbuf_with_closed_map() {
901+
bump_rlimit_mlock();
902+
903+
fn test(poll_fn: impl FnOnce(&libbpf_rs::RingBuffer)) {
904+
let mut value = 0i32;
905+
906+
{
907+
let mut obj = get_test_object("tracepoint.bpf.o");
908+
let prog = obj
909+
.prog_mut("handle__tracepoint")
910+
.expect("Failed to find program");
911+
912+
let _link = prog
913+
.attach_tracepoint("syscalls", "sys_enter_getpid")
914+
.expect("Failed to attach prog");
915+
916+
let map = obj.map("ringbuf").expect("Failed to get ringbuf map");
917+
918+
let callback = |data: &[u8]| {
919+
plain::copy_from_bytes(&mut value, data).expect("Wrong size");
920+
0
921+
};
922+
923+
let mut builder = libbpf_rs::RingBufferBuilder::new();
924+
builder.add(map, callback).expect("Failed to add ringbuf");
925+
let ringbuf = builder.build().expect("Failed to build");
926+
927+
drop(obj);
928+
929+
// Trigger the tracepoint. At this point `map` along with the containing
930+
// `obj` have been destroyed.
931+
let _pid = unsafe { libc::getpid() };
932+
let () = poll_fn(&ringbuf);
933+
}
934+
935+
// If we see a 1 here the ring buffer was still working as expected.
936+
assert_eq!(value, 1);
937+
}
938+
939+
test(|ringbuf| ringbuf.consume().expect("Failed to consume ringbuf"));
940+
test(|ringbuf| {
941+
ringbuf
942+
.poll(Duration::from_secs(5))
943+
.expect("Failed to poll ringbuf")
944+
});
945+
}
946+
894947
#[test]
895948
fn test_sudo_object_task_iter() {
896949
bump_rlimit_mlock();

0 commit comments

Comments
 (0)