iceoryx2_bb_posix/
ipc_capable.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright (c) 2024 Contributors to the Eclipse Foundation
//
// See the NOTICE file(s) distributed with this work for additional
// information regarding copyright ownership.
//
// This program and the accompanying materials are made available under the
// terms of the Apache Software License 2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
// which is available at https://opensource.org/licenses/MIT.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

use iceoryx2_pal_concurrency_sync::iox_atomic::IoxAtomicBool;
use std::{cell::UnsafeCell, sync::atomic::Ordering};

pub(crate) mod internal {
    use std::fmt::Debug;

    use super::*;

    #[derive(Debug, Clone, Copy, Eq, PartialEq)]
    pub enum Capability {
        InterProcess,
        ProcessLocal,
    }

    /// Stores an OS handle to some resource that is also inter-process capable, like a unnamed
    /// semaphore or mutex handle.
    pub struct HandleStorage<T> {
        handle: UnsafeCell<T>,
        is_inter_process_capable: IoxAtomicBool,
        is_initialized: IoxAtomicBool,
    }

    unsafe impl<T> Send for HandleStorage<T> {}
    unsafe impl<T> Sync for HandleStorage<T> {}

    impl<T> Debug for HandleStorage<T> {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(
                f,
                "HandleStorage<{}> {{ is_interprocess_capable: {}, is_initialized: {} }}",
                std::any::type_name::<T>(),
                self.is_inter_process_capable.load(Ordering::Relaxed),
                self.is_initialized.load(Ordering::Relaxed),
            )
        }
    }

    impl<T> Drop for HandleStorage<T> {
        fn drop(&mut self) {
            self.is_initialized.store(false, Ordering::Relaxed);
        }
    }

    impl<T> HandleStorage<T> {
        /// Creates a new HandleStorage with a predefined handle that must be not initialized.
        pub fn new(handle: T) -> Self {
            Self {
                handle: UnsafeCell::new(handle),
                is_initialized: IoxAtomicBool::new(false),
                is_inter_process_capable: IoxAtomicBool::new(false),
            }
        }

        /// Returns true if the [`Handle`] is initialized, otherwise false.
        pub fn is_initialized(&self) -> bool {
            self.is_initialized.load(Ordering::Relaxed)
        }

        /// Initializes the handle via a provided initializer callback. If the initializer returns
        /// true the underlying handle is marked as initialized otherwise it is still uninitialized.
        ///
        /// # Safety
        ///  * The handle must be uninitialized
        ///  * Must not be shared with other threads before calling [`IpcCapable::initialize()`]
        ///
        pub unsafe fn initialize<E, F: FnOnce(*mut T) -> Result<Capability, E>>(
            &self,
            initializer: F,
        ) -> Result<(), E> {
            debug_assert!(
                !self.is_initialized.load(Ordering::Relaxed),
                "The handle must be uninitialized before it can be initialized."
            );

            self.is_inter_process_capable.store(
                initializer(self.handle.get())? == Capability::InterProcess,
                Ordering::Relaxed,
            );

            // does not need to sync any memory since the construct is not allowed to
            // be shared with any other thread before it is initialized
            // -> Ordering::Relaxed
            self.is_initialized.store(true, Ordering::Relaxed);

            Ok(())
        }

        pub fn is_inter_process_capable(&self) -> bool {
            self.is_inter_process_capable.load(Ordering::Relaxed)
        }

        /// Releases the underlying resources of the handle via the cleanup callback.
        ///
        /// # Safety
        ///  * The handle must be initialized
        ///  * Must not be used concurrently. Only one thread - the one that calls
        ///     [`IpcCapable::cleanup()`] - is allowed to operate on the [`IpcCapable`].
        ///
        pub unsafe fn cleanup<F: FnOnce(&mut T)>(&self, cleanup: F) {
            debug_assert!(
                self.is_initialized.load(Ordering::Relaxed),
                "The handle must be initialized before it can be cleaned up."
            );

            cleanup(self.get());

            // does not need to sync any memory since the construct is not allowed to
            // be shared with any other thread before it is initialized
            // -> Ordering::Relaxed
            self.is_initialized.store(false, Ordering::Relaxed);
        }

        /// Returns a mutable reference to the underlying handle.
        ///
        /// # Safety
        ///  * The handle must be initialized
        ///
        #[allow(clippy::mut_from_ref)]
        pub unsafe fn get(&self) -> &mut T {
            debug_assert!(
                self.is_initialized.load(Ordering::Relaxed),
                "The handle must be initialized before it can be acquired."
            );

            unsafe { &mut *self.handle.get() }
        }
    }

    pub trait IpcConstructible<'a, T: Handle> {
        fn new(handle: &'a T) -> Self;
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum HandleState {
    Initialized,
    Uninitialized,
}

/// Represents a handle that is in general inter-process capable.
pub trait Handle: Send + Sync {
    fn new() -> Self;
    fn is_inter_process_capable(&self) -> bool;
    fn is_initialized(&self) -> bool;
}

/// Represents struct that can be configured for inter-process use.
pub trait IpcCapable<'a, T: Handle>: internal::IpcConstructible<'a, T> + Sized {
    /// Returns true if the object is interprocess capable, otherwise false
    fn is_interprocess_capable(&self) -> bool;

    /// Creates an IPC Capable object from its handle.
    ///
    /// # Safety
    ///   * The handle must be initialized
    ///   * The handle must be ipc capable, see [`Handle::is_inter_process_capable()`].
    ///   * The handle must not be cleaned up while it is used by the object
    ///
    unsafe fn from_ipc_handle(handle: &'a T) -> Self {
        debug_assert!(
            handle.is_initialized(),
            "The handle must be initialized before it can be used to construct an object."
        );

        debug_assert!(
            handle.is_inter_process_capable(),
            "The handle must be interprocess capable to be used for constructing an ipc capable object."
        );

        Self::new(handle)
    }
}