zenoh_shm/api/protocol_implementations/posix/
posix_shm_provider_backend_buddy.rs

1//
2// Copyright (c) 2025 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15use std::{
16    alloc::Layout,
17    ptr::NonNull,
18    sync::{Arc, Mutex},
19};
20
21use buddy_system_allocator::Heap;
22use zenoh_core::{zlock, Resolvable, Wait};
23use zenoh_result::ZResult;
24
25use super::posix_shm_segment::PosixShmSegment;
26use crate::api::{
27    common::{types::ProtocolID, with_id::WithProtocolID},
28    protocol_implementations::posix::protocol_id::POSIX_PROTOCOL_ID,
29    provider::{
30        chunk::ChunkDescriptor,
31        memory_layout::MemoryLayout,
32        shm_provider_backend::ShmProviderBackend,
33        types::{AllocAlignment, ChunkAllocResult, ZAllocError, ZLayoutError},
34    },
35};
36
37/// Builder to create posix SHM provider
38#[zenoh_macros::unstable_doc]
39pub struct PosixShmProviderBackendBuddyBuilder<Layout> {
40    layout: Layout,
41}
42
43#[zenoh_macros::unstable_doc]
44impl<Layout> Resolvable for PosixShmProviderBackendBuddyBuilder<Layout> {
45    type To = ZResult<PosixShmProviderBackendBuddy>;
46}
47
48#[zenoh_macros::unstable_doc]
49impl<Layout: TryInto<MemoryLayout>> Wait for PosixShmProviderBackendBuddyBuilder<Layout>
50where
51    Layout::Error: Into<ZLayoutError>,
52{
53    fn wait(self) -> <Self as Resolvable>::To {
54        PosixShmProviderBackendBuddy::new(&self.layout.try_into().map_err(Into::into)?)
55    }
56}
57
58/// A buddy_system_allocator backend based on POSIX shared memory.
59/// buddy_system_allocator is the fastest allocator ever. The weak side is it's low memry efficiency and
60/// higher fragmentation as opposed to talc (which is treated as the universal default for zenoh SHM)
61#[zenoh_macros::unstable_doc]
62pub struct PosixShmProviderBackendBuddy {
63    segment: Arc<PosixShmSegment>,
64    heap: Mutex<Heap<BUDDY_ORDER>>,
65    alignment: AllocAlignment,
66}
67
68// see `buddy_system_allocator` doc for details
69const BUDDY_ORDER: usize = 30;
70
71impl PosixShmProviderBackendBuddy {
72    /// Get the builder to construct a new instance
73    #[zenoh_macros::unstable_doc]
74    pub fn builder<Layout>(layout: Layout) -> PosixShmProviderBackendBuddyBuilder<Layout> {
75        PosixShmProviderBackendBuddyBuilder { layout }
76    }
77
78    fn new(layout: &MemoryLayout) -> ZResult<Self> {
79        let segment = Arc::new(PosixShmSegment::create(layout.size())?);
80
81        // because of platform specific, our shm segment is >= requested size, so in order to utilize
82        // additional memory we re-layout the size
83        let real_size = segment.segment.elem_count().get();
84
85        let mut heap = Heap::empty();
86
87        unsafe { heap.init(segment.segment.elem_mut(0) as usize, real_size) };
88
89        tracing::trace!(
90            "Created PosixShmProviderBackendBuddy id {}, layout {:?}",
91            segment.segment.id(),
92            layout
93        );
94
95        Ok(Self {
96            segment,
97            heap: Mutex::new(heap),
98            alignment: layout.alignment(),
99        })
100    }
101}
102
103impl WithProtocolID for PosixShmProviderBackendBuddy {
104    fn id(&self) -> ProtocolID {
105        POSIX_PROTOCOL_ID
106    }
107}
108
109impl ShmProviderBackend for PosixShmProviderBackendBuddy {
110    fn alloc(&self, layout: &MemoryLayout) -> ChunkAllocResult {
111        tracing::trace!("PosixShmProviderBackendBuddy::alloc({:?})", layout);
112
113        let alloc_layout = unsafe {
114            Layout::from_size_align_unchecked(
115                layout.size().get(),
116                layout.alignment().get_alignment_value().get(),
117            )
118        };
119
120        let alloc = {
121            let mut lock = zlock!(self.heap);
122            lock.alloc(alloc_layout)
123        };
124
125        match alloc {
126            Ok(buf) => Ok(self.segment.clone().allocated_chunk(buf, layout)),
127            Err(_) => Err(ZAllocError::OutOfMemory),
128        }
129    }
130
131    fn free(&self, chunk: &ChunkDescriptor) {
132        let alloc_layout = unsafe {
133            Layout::from_size_align_unchecked(
134                chunk.len.get(),
135                self.alignment.get_alignment_value().get(),
136            )
137        };
138
139        let ptr = unsafe { self.segment.segment.elem_mut(chunk.chunk) };
140
141        unsafe { zlock!(self.heap).dealloc(NonNull::new_unchecked(ptr), alloc_layout) };
142    }
143
144    fn defragment(&self) -> usize {
145        0
146    }
147
148    fn available(&self) -> usize {
149        let lock = zlock!(self.heap);
150        lock.stats_total_bytes() - lock.stats_alloc_actual()
151    }
152
153    fn layout_for(&self, layout: MemoryLayout) -> Result<MemoryLayout, ZLayoutError> {
154        layout.extend(self.alignment)
155    }
156}