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