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}