iceoryx2_bb_posix/ipc_capable.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
13use iceoryx2_bb_concurrency::atomic::AtomicBool;
14use iceoryx2_bb_concurrency::atomic::Ordering;
15use iceoryx2_bb_concurrency::cell::UnsafeCell;
16
17pub(crate) mod internal {
18 use core::fmt::Debug;
19
20 use super::*;
21
22 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
23 pub enum Capability {
24 InterProcess,
25 ProcessLocal,
26 }
27
28 /// Stores an OS handle to some resource that is also inter-process capable, like a unnamed
29 /// semaphore or mutex handle.
30 pub struct HandleStorage<T> {
31 handle: UnsafeCell<T>,
32 is_inter_process_capable: AtomicBool,
33 is_initialized: AtomicBool,
34 }
35
36 unsafe impl<T> Send for HandleStorage<T> {}
37 unsafe impl<T> Sync for HandleStorage<T> {}
38
39 impl<T> Debug for HandleStorage<T> {
40 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41 write!(
42 f,
43 "HandleStorage<{}> {{ is_interprocess_capable: {}, is_initialized: {} }}",
44 core::any::type_name::<T>(),
45 self.is_inter_process_capable.load(Ordering::Relaxed),
46 self.is_initialized.load(Ordering::Relaxed),
47 )
48 }
49 }
50
51 impl<T> Drop for HandleStorage<T> {
52 fn drop(&mut self) {
53 self.is_initialized.store(false, Ordering::Relaxed);
54 }
55 }
56
57 impl<T> HandleStorage<T> {
58 /// Creates a new HandleStorage with a predefined handle that must be not initialized.
59 pub fn new(handle: T) -> Self {
60 Self {
61 handle: UnsafeCell::new(handle),
62 is_initialized: AtomicBool::new(false),
63 is_inter_process_capable: AtomicBool::new(false),
64 }
65 }
66
67 /// Returns true if the [`Handle`] is initialized, otherwise false.
68 pub fn is_initialized(&self) -> bool {
69 self.is_initialized.load(Ordering::Relaxed)
70 }
71
72 /// Initializes the handle via a provided initializer callback. If the initializer returns
73 /// true the underlying handle is marked as initialized otherwise it is still uninitialized.
74 ///
75 /// # Safety
76 /// * The handle must be uninitialized
77 /// * Must not be shared with other threads before calling [`IpcCapable::initialize()`]
78 ///
79 pub unsafe fn initialize<E, F: FnOnce(*mut T) -> Result<Capability, E>>(
80 &self,
81 initializer: F,
82 ) -> Result<(), E> {
83 debug_assert!(
84 !self.is_initialized.load(Ordering::Relaxed),
85 "The handle must be uninitialized before it can be initialized."
86 );
87
88 self.is_inter_process_capable.store(
89 initializer(self.handle.get())? == Capability::InterProcess,
90 Ordering::Relaxed,
91 );
92
93 // does not need to sync any memory since the construct is not allowed to
94 // be shared with any other thread before it is initialized
95 // -> Ordering::Relaxed
96 self.is_initialized.store(true, Ordering::Relaxed);
97
98 Ok(())
99 }
100
101 pub fn is_inter_process_capable(&self) -> bool {
102 self.is_inter_process_capable.load(Ordering::Relaxed)
103 }
104
105 /// Releases the underlying resources of the handle via the cleanup callback.
106 ///
107 /// # Safety
108 /// * The handle must be initialized
109 /// * Must not be used concurrently. Only one thread - the one that calls
110 /// [`IpcCapable::cleanup()`] - is allowed to operate on the [`IpcCapable`].
111 ///
112 pub unsafe fn cleanup<F: FnOnce(&mut T)>(&self, cleanup: F) {
113 debug_assert!(
114 self.is_initialized.load(Ordering::Relaxed),
115 "The handle must be initialized before it can be cleaned up."
116 );
117
118 cleanup(self.get());
119
120 // does not need to sync any memory since the construct is not allowed to
121 // be shared with any other thread before it is initialized
122 // -> Ordering::Relaxed
123 self.is_initialized.store(false, Ordering::Relaxed);
124 }
125
126 /// Returns a mutable reference to the underlying handle.
127 ///
128 /// # Safety
129 /// * The handle must be initialized
130 ///
131 #[allow(clippy::mut_from_ref)]
132 pub unsafe fn get(&self) -> &mut T {
133 debug_assert!(
134 self.is_initialized.load(Ordering::Relaxed),
135 "The handle must be initialized before it can be acquired."
136 );
137
138 unsafe { &mut *self.handle.get() }
139 }
140 }
141
142 pub trait IpcConstructible<'a, T: Handle> {
143 fn new(handle: &'a T) -> Self;
144 }
145}
146
147#[derive(Debug, PartialEq, Eq, Clone, Copy)]
148pub enum HandleState {
149 Initialized,
150 Uninitialized,
151}
152
153/// Represents a handle that is in general inter-process capable.
154pub trait Handle: Send + Sync {
155 fn new() -> Self;
156 fn is_inter_process_capable(&self) -> bool;
157 fn is_initialized(&self) -> bool;
158}
159
160/// Represents struct that can be configured for inter-process use.
161pub trait IpcCapable<'a, T: Handle>: internal::IpcConstructible<'a, T> + Sized {
162 /// Returns true if the object is interprocess capable, otherwise false
163 fn is_interprocess_capable(&self) -> bool;
164
165 /// Creates an IPC Capable object from its handle.
166 ///
167 /// # Safety
168 /// * The handle must be initialized
169 /// * The handle must be ipc capable, see [`Handle::is_inter_process_capable()`].
170 /// * The handle must not be cleaned up while it is used by the object
171 ///
172 unsafe fn from_ipc_handle(handle: &'a T) -> Self {
173 debug_assert!(
174 handle.is_initialized(),
175 "The handle must be initialized before it can be used to construct an object."
176 );
177
178 debug_assert!(
179 handle.is_inter_process_capable(),
180 "The handle must be interprocess capable to be used for constructing an ipc capable object."
181 );
182
183 Self::new(handle)
184 }
185}