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_pal_concurrency_sync::iox_atomic::IoxAtomicBool;
14use std::{cell::UnsafeCell, sync::atomic::Ordering};
15
16pub(crate) mod internal {
17    use std::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 std::fmt::Formatter<'_>) -> std::fmt::Result {
40            write!(
41                f,
42                "HandleStorage<{}> {{ is_interprocess_capable: {}, is_initialized: {} }}",
43                std::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}