Skip to main content

iceoryx2_cal/resizable_shared_memory/
mod.rs

1// Copyright (c) 2024 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13//! A [`ResizableSharedMemory`] is identified by a name and allows multiple processes to share
14//! memory between them (inter-process memory). One process owns the [`ResizableSharedMemory`]
15//! which can be created via the [`ResizableSharedMemoryBuilder`] and many processes can have a
16//! [`ResizableSharedMemoryView`] that can be constructed via [`ResizableSharedMemoryViewBuilder`].
17//!
18//! The [`ResizableSharedMemoryView`] never owns the [`ResizableSharedMemory`] and has only
19//! read-only access to it while the [`ResizableSharedMemory`] can use
20//! [`ResizableSharedMemory::allocate()`] to acquire memory and distribute it between the
21//! [`ResizableSharedMemoryView`]s.
22//!
23//! Whenever the [`ResizableSharedMemoryView`] receives an offset it must be registered via
24//! [`ResizableSharedMemoryView::register_and_translate_offset()`] and unregistered via
25//! [`ResizableSharedMemoryView::unregister_offset()`]. As soon as the [`ResizableSharedMemory`]
26//! calls [`ResizableSharedMemory::deallocate()`] unused [`SharedMemory`] segments may be recycled.
27//!
28//! # Example
29//!
30//! ```
31//! // owner of the ResizableSharedMemory
32//! use iceoryx2_bb_posix::access_mode::AccessMode;
33//! use iceoryx2_cal::resizable_shared_memory::*;
34//! use iceoryx2_bb_system_types::file_name::FileName;
35//! use iceoryx2_cal::shm_allocator::ShmAllocator;
36//! use iceoryx2_cal::shared_memory::SharedMemory;
37//! use iceoryx2_cal::named_concept::*;
38//! use core::alloc::Layout;
39//! use core::time::Duration;
40//!
41//! fn example<Alloc: ShmAllocator, Shm: SharedMemory<Alloc>, Memory: ResizableSharedMemory<Alloc, Shm>>(
42//!     name: &FileName
43//! ) {
44//!     // owner process creates a new memory
45//!     let memory = Memory::MemoryBuilder::new(name)
46//!         // hint to the underlying allocator that we need up to 128 chunks of memory
47//!         //
48//!         // note: as soon as there are more than 128 chunks requested a new resized segment is
49//!         //       created
50//!         .max_number_of_chunks_hint(128)
51//!         // hint to the underlying allocator that the chunks are not exceeding the Layout of
52//!         // [`u16`]
53//!         //
54//!         // note: as soon as there is a chunk requested that exceeds the provided layout hint a
55//!         //       new resized segment is created
56//!         .max_chunk_layout_hint(Layout::new::<u32>())
57//!         // defines the strategy how segments are resized
58//!         .allocation_strategy(AllocationStrategy::PowerOfTwo)
59//!         .create().unwrap();
60//!
61//!     let chunk = memory.allocate(Layout::new::<u16>()).unwrap();
62//!     // store the value 123 in the newly allocated chunk
63//!     unsafe { (chunk.data_ptr as *mut u16).write(123) };
64//!
65//!     // since this exceeds the the chunk layout hint, the underlying segment is resized
66//!     // following the provided allocation strategy
67//!     let another_chunk = memory.allocate(Layout::new::<u64>()).unwrap();
68//!     // release allocated chunk
69//!     unsafe { memory.deallocate(another_chunk.offset, Layout::new::<u64>()) };
70//!
71//!
72//!     let view = Memory::ViewBuilder::new(name)
73//!         // defines how long the builder shall wait to open the corresponding segments when
74//!         // the creator is concurrently creating them.
75//!         .timeout(Duration::from_secs(1))
76//!         .open(AccessMode::ReadWrite).unwrap();
77//!
78//!     // before we can consume the received offset we need to translate it into our local
79//!     // process space
80//!     // this operation also maps unmapped segments into the process space if required.
81//!     let ptr = unsafe { view.register_and_translate_offset(chunk.offset).unwrap() };
82//!     println!("received {}", unsafe {*(ptr as *const u16)});
83//!
84//!     // when we are finished consuming the memory we need to unregister the offset. this
85//!     // unmaps segments that are no longer used.
86//!     unsafe { view.unregister_offset(chunk.offset) };
87//! }
88//! ```
89
90pub mod dynamic;
91pub mod recommended;
92
93pub use crate::shm_allocator::{AllocationStrategy, pool_allocator::PoolAllocator};
94
95use core::alloc::Layout;
96use core::fmt::Debug;
97use core::time::Duration;
98
99use iceoryx2_bb_elementary::enum_gen;
100use iceoryx2_bb_elementary_traits::testing::abandonable::Abandonable;
101use iceoryx2_bb_posix::file::AccessMode;
102
103use crate::named_concept::*;
104use crate::shared_memory::{
105    SegmentId, SharedMemory, SharedMemoryCreateError, SharedMemoryOpenError, ShmPointer,
106};
107use crate::shm_allocator::{PointerOffset, ShmAllocationError, ShmAllocator};
108
109enum_gen! {
110/// Defines all erros that can occur when calling [`ResizableSharedMemory::allocate()`]
111///
112/// The [`ResizableSharedMemory`] cannot be resized indefinitely. If the resize limit is hit
113/// this error will be returned. It can be mitigated by providing a better
114/// [`ResizableSharedMemoryBuilder::max_number_of_chunks_hint()`] or
115/// [`ResizableSharedMemoryBuilder::max_chunk_layout_hint()`].
116    ResizableShmAllocationError
117  entry:
118    MaxReallocationsReached
119  mapping:
120    ShmAllocationError,
121    SharedMemoryCreateError
122}
123
124/// Creates a [`ResizableSharedMemoryView`] to an existing [`ResizableSharedMemory`] and maps the
125/// [`ResizableSharedMemory`] read-only into the process space.
126pub trait ResizableSharedMemoryViewBuilder<
127    Allocator: ShmAllocator,
128    Shm: SharedMemory<Allocator>,
129    ResizableShm: ResizableSharedMemory<Allocator, Shm>,
130    ResizableShmView: ResizableSharedMemoryView<Allocator, Shm>,
131>: NamedConceptBuilder<ResizableShm> + Debug
132{
133    /// The timeout defines how long the
134    /// [`SharedMemoryBuilder`](crate::shared_memory::SharedMemoryBuilder) should wait for
135    /// [`SharedMemoryBuilder::create()`](crate::shared_memory::SharedMemoryBuilder::create()) to finialize
136    /// the initialization. This is required when the [`SharedMemory`] is created and initialized
137    /// concurrently from another process. By default it is set to [`Duration::ZERO`] for no
138    /// timeout.
139    fn timeout(self, value: Duration) -> Self;
140
141    /// Opens already existing [`SharedMemory`]. If it does not exist or the initialization is not
142    /// yet finished the method will fail.
143    fn open(self, access_mode: AccessMode) -> Result<ResizableShmView, SharedMemoryOpenError>;
144}
145
146/// Creates a new [`ResizableSharedMemory`] which the process will own. As soon as the
147/// corresponding object goes out-of-scope the underlying [`SharedMemory`] resources will be
148/// removed.
149pub trait ResizableSharedMemoryBuilder<
150    Allocator: ShmAllocator,
151    Shm: SharedMemory<Allocator>,
152    ResizableShm: ResizableSharedMemory<Allocator, Shm>,
153>: NamedConceptBuilder<ResizableShm> + Debug
154{
155    /// Provides an initial hint to the underlying [`ShmAllocator`] on how large the largest chunk
156    /// will be. If the chunk exceeds the hinted [`Layout`] a new [`SharedMemory`] segment is
157    /// acquired to satisfy the memory needs.
158    fn max_chunk_layout_hint(self, value: Layout) -> Self;
159
160    /// Provides an initial hint to the underlying [`ShmAllocator`] on how many chunks at most will
161    /// be used in parallel. If the number of chunk exceeds the hint a new [`SharedMemory`] segment
162    /// is acquired to satisfy the memory needs.
163    fn max_number_of_chunks_hint(self, value: usize) -> Self;
164
165    /// Defines the [`AllocationStrategy`] that is pursued when a new [`SharedMemory`] segment is
166    /// acquired.
167    fn allocation_strategy(self, value: AllocationStrategy) -> Self;
168
169    /// Creates new [`SharedMemory`]. If it already exists the method will fail.
170    fn create(self) -> Result<ResizableShm, SharedMemoryCreateError>;
171}
172
173/// A read-only view to a [`ResizableSharedMemory`]. Can be created by arbitrary many processes.
174pub trait ResizableSharedMemoryView<Allocator: ShmAllocator, Shm: SharedMemory<Allocator>>:
175    Debug + Send + Abandonable
176{
177    /// Registers a received [`PointerOffset`] at the [`ResizableSharedMemoryView`] and returns the
178    /// absolut pointer to the data. If the segment of the received [`PointerOffset`] was not yet
179    /// mapped into the processes space, it will be opened and mapped. If this fails a
180    /// [`SharedMemoryOpenError`] is returned.
181    ///
182    /// # Safety
183    ///
184    ///   * This function shall be called exactly once for a received [`PointerOffset`]
185    unsafe fn register_and_translate_offset(
186        &self,
187        offset: PointerOffset,
188    ) -> Result<*const u8, SharedMemoryOpenError>;
189
190    /// Unregisters a received [`PointerOffset`] that was previously registered.
191    ///
192    /// # Safety
193    ///
194    ///  * [`ResizableSharedMemoryView::register_and_translate_offset()`] must have been called
195    ///    with the same [`PointerOffset`] before calling this function.
196    ///  * This function must be called before a registered [`PointerOffset`] goes out-of-scope.
197    ///  * This function must be called at most once for any received [`PointerOffset`]
198    unsafe fn unregister_offset(&self, offset: PointerOffset);
199
200    /// Returns the number of active [`SharedMemory`] segments.
201    fn number_of_active_segments(&self) -> usize;
202}
203
204/// The [`ResizableSharedMemory`] can be only owned by exactly one process that is allowed to
205/// [`ResizableSharedMemory::allocate()`] memory and distribute the memory to all
206/// [`ResizableSharedMemoryView`]s.
207pub trait ResizableSharedMemory<Allocator: ShmAllocator, Shm: SharedMemory<Allocator>>:
208    Sized + NamedConcept + NamedConceptMgmt + Debug + Send + Abandonable
209{
210    /// Type alias to the [`ResizableSharedMemoryViewBuilder`] to open a
211    /// [`ResizableSharedMemoryView`] to an existing [`ResizableSharedMemory`].
212    type ViewBuilder: ResizableSharedMemoryViewBuilder<Allocator, Shm, Self, Self::View>;
213
214    /// Type alias to the [`ResizableSharedMemoryBuilder`] to create a new [`ResizableSharedMemory`].
215    type MemoryBuilder: ResizableSharedMemoryBuilder<Allocator, Shm, Self>;
216
217    /// Type alias to the [`ResizableSharedMemoryView`] to open an existing
218    /// [`ResizableSharedMemory`] as read-only.
219    type View: ResizableSharedMemoryView<Allocator, Shm>;
220
221    /// Returns how many reallocations the [`ResizableSharedMemory`] supports. If the number is
222    /// exceeded any call to [`ResizableSharedMemory::allocate()`] that requires a resize of the
223    /// underlying [`SharedMemory`] segments will fail.
224    fn max_number_of_reallocations() -> usize;
225
226    /// Returns the number of active [`SharedMemory`] segments.
227    fn number_of_active_segments(&self) -> usize;
228
229    /// Allocates a new piece of [`SharedMemory`] if the provided [`Layout`] exceeds the current
230    /// supported [`Layout`], the memory would be out-of-memory or the number of chunks exceeds the
231    /// current supported amount of chunks, a new [`SharedMemory`] segment will be created. If this
232    /// fails an [`SharedMemoryCreateError`] will be returned.
233    fn allocate(
234        &self,
235        layout: core::alloc::Layout,
236    ) -> Result<ShmPointer, ResizableShmAllocationError>;
237
238    /// Release previously allocated memory
239    ///
240    /// # Safety
241    ///
242    ///  * the offset must be acquired with [`SharedMemory::allocate()`] - extracted from the
243    ///    [`ShmPointer`]
244    ///  * the layout must be identical to the one used in [`SharedMemory::allocate()`]
245    unsafe fn deallocate(&self, offset: PointerOffset, layout: core::alloc::Layout);
246}
247
248pub trait ResizableSharedMemoryForPoolAllocator<Shm: SharedMemory<PoolAllocator>>:
249    ResizableSharedMemory<PoolAllocator, Shm>
250{
251    /// Release previously allocated memory
252    ///
253    /// # Safety
254    ///
255    ///  * the offset must be acquired with [`SharedMemory::allocate()`] - extracted from the
256    ///    [`ShmPointer`]
257    unsafe fn deallocate_bucket(&self, offset: PointerOffset);
258
259    /// Returns the bucket size of the corresponding [`PoolAllocator`]
260    fn bucket_size(&self, segment_id: SegmentId) -> usize;
261}