Skip to main content

gstd/sync/
mutex.rs

1// Copyright (C) Gear Technologies Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3
4use super::access::AccessQueue;
5use crate::{
6    BlockCount, BlockNumber, Config, MessageId, async_runtime,
7    errors::{Error, Result, UsageError},
8    exec, format, msg,
9};
10use core::{
11    cell::UnsafeCell,
12    future::Future,
13    ops::{Deref, DerefMut},
14    pin::Pin,
15    task::{Context, Poll},
16};
17
18static mut NEXT_MUTEX_ID: MutexId = MutexId::new();
19
20#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
21pub(crate) struct MutexId(u32);
22
23impl MutexId {
24    pub const fn new() -> Self {
25        MutexId(0)
26    }
27
28    pub fn next(self) -> Self {
29        Self(self.0.wrapping_add(1))
30    }
31}
32
33/// A mutual exclusion primitive useful for protecting shared data.
34///
35/// This mutex will block the execution waiting for the lock to become
36/// available. The mutex can be created via a [`new`](Mutex::new) constructor.
37/// Each mutex has a type parameter which represents the data that it is
38/// protecting. The data can only be accessed through the RAII guard
39/// [`MutexGuard`] returned from [`lock`](Mutex::lock),
40/// which guarantees that data access only occurs when the mutex is
41/// locked.
42///
43/// # Examples
44///
45/// This example (program A), after locking the mutex, sends the `PING` message
46/// to another program (program B) and waits for a reply. If any other program
47/// (program C) tries to invoke program A, it will wait until program A receives
48/// the `PONG` reply from program B and unlocks the mutex.
49///
50/// ```ignored
51/// use gstd::{msg, sync::Mutex, ActorId};
52///
53/// static mut DEST: ActorId = ActorId::zero();
54/// static MUTEX: Mutex<()> = Mutex::new(());
55///
56/// #[unsafe(no_mangle)]
57/// extern "C" fn init() {
58///     // `some_address` can be obtained from the init payload
59///     # let some_address = ActorId::zero();
60///     unsafe { DEST = some_address };
61/// }
62///
63/// #[gstd::async_main]
64/// async fn main() {
65///     let payload = msg::load_bytes().expect("Unable to load payload bytes");
66///     if payload == b"START" {
67///         let _unused = MUTEX.lock().await;
68///
69///         let reply = msg::send_bytes_for_reply(unsafe { DEST }, b"PING", 0, 0)
70///             .expect("Unable to send bytes")
71///             .await
72///             .expect("Error in async message processing");
73///
74///         if reply == b"PONG" {
75///             msg::reply(b"SUCCESS", 0).unwrap();
76///         } else {
77///             msg::reply(b"FAIL", 0).unwrap();
78///         }
79///     }
80/// }
81/// # fn main() {}
82/// ```
83pub struct Mutex<T> {
84    id: UnsafeCell<Option<MutexId>>,
85    locked: UnsafeCell<Option<(MessageId, BlockNumber)>>,
86    value: UnsafeCell<T>,
87    queue: AccessQueue,
88}
89
90impl<T> From<T> for Mutex<T> {
91    fn from(t: T) -> Self {
92        Mutex::new(t)
93    }
94}
95
96impl<T: Default> Default for Mutex<T> {
97    fn default() -> Self {
98        <T as Default>::default().into()
99    }
100}
101
102impl<T> Mutex<T> {
103    /// Create a new mutex in an unlocked state ready for use.
104    pub const fn new(t: T) -> Mutex<T> {
105        Mutex {
106            id: UnsafeCell::new(None),
107            value: UnsafeCell::new(t),
108            locked: UnsafeCell::new(None),
109            queue: AccessQueue::new(),
110        }
111    }
112
113    /// Acquire a mutex, protecting the subsequent code from execution by other
114    /// actors until the mutex hasn't been unlocked.
115    ///
116    /// This function will block access to the section of code by
117    /// other programs or users that invoke the same program. If another
118    /// actor reaches the code blocked by the mutex, it goes to the wait
119    /// state until the mutex unlocks. RAII guard wrapped in the future is
120    /// returned to allow scoped unlock of the lock. When the guard goes out
121    /// of scope, the mutex will be unlocked.
122    pub fn lock(&self) -> MutexLockFuture<'_, T> {
123        MutexLockFuture {
124            mutex_id: self.get_or_assign_id(),
125            mutex: self,
126            own_up_for: None,
127        }
128    }
129
130    // Returns a mutable reference to the mutex lock owner. The function uses unsafe
131    // code because it is called from the places where there is only non-mutable
132    // reference to the mutex exists, and the latter can't be turned into a
133    // mutable one as it will break logic around the `Mutex.lock` function which
134    // must be called on a non-mutable reference to the mutex.
135    #[allow(clippy::mut_from_ref)]
136    fn locked_by_mut(&self) -> &mut Option<(MessageId, BlockNumber)> {
137        unsafe { &mut *self.locked.get() }
138    }
139
140    fn get_or_assign_id(&self) -> MutexId {
141        let id = unsafe { &mut *self.id.get() };
142        *id.get_or_insert_with(|| unsafe {
143            let id = NEXT_MUTEX_ID;
144            NEXT_MUTEX_ID = NEXT_MUTEX_ID.next();
145            id
146        })
147    }
148}
149
150/// An RAII implementation of a "scoped lock" of a mutex. When this structure is
151/// dropped (falls out of scope), the lock will be unlocked.
152///
153/// The data protected by the mutex is accessible through this guard via its
154/// [`Deref`] and [`DerefMut`] implementations.
155///
156/// This structure wrapped in the future is returned by the
157/// [`lock`](Mutex::lock) method on [`Mutex`].
158pub struct MutexGuard<'a, T> {
159    mutex: &'a Mutex<T>,
160    holder_msg_id: MessageId,
161}
162
163impl<T> MutexGuard<'_, T> {
164    #[track_caller]
165    fn ensure_access_by_holder(&self) {
166        let current_msg_id = msg::id();
167        if self.holder_msg_id != current_msg_id {
168            panic!(
169                "Mutex guard held by message 0x{} is being accessed by message 0x{}",
170                hex::encode(self.holder_msg_id),
171                hex::encode(current_msg_id)
172            );
173        }
174    }
175}
176
177impl<T> Drop for MutexGuard<'_, T> {
178    fn drop(&mut self) {
179        let is_holder_msg_signal_handler = match () {
180            #[cfg(not(feature = "ethexe"))]
181            () => msg::signal_from() == Ok(self.holder_msg_id),
182            #[cfg(feature = "ethexe")]
183            () => false,
184        };
185
186        if !is_holder_msg_signal_handler {
187            self.ensure_access_by_holder();
188        }
189
190        let locked_by = self.mutex.locked_by_mut();
191        let owner_msg_id = locked_by.map(|v| v.0);
192
193        if owner_msg_id != Some(self.holder_msg_id) && !is_holder_msg_signal_handler {
194            // If owner_msg_id is None or not equal to the holder_msg_id, firstly, it means
195            // we are in the message signal handler and, secondly, the lock was seized by
196            // some other message. In this case, the next rival message was
197            // awoken by the ousting mechanism in the MutexLockFuture::poll
198            panic!(
199                "Mutex guard held by message 0x{} does not match lock owner message {}",
200                hex::encode(self.holder_msg_id),
201                owner_msg_id.map_or("None".into(), |v| format!("0x{}", hex::encode(v)))
202            );
203        }
204
205        if owner_msg_id == Some(self.holder_msg_id) {
206            if let Some(message_id) = self.mutex.queue.dequeue() {
207                exec::wake(message_id).expect("Failed to wake the message");
208            }
209            *locked_by = None;
210        }
211    }
212}
213
214impl<'a, T> AsRef<T> for MutexGuard<'a, T> {
215    fn as_ref(&self) -> &'a T {
216        self.ensure_access_by_holder();
217        unsafe { &*self.mutex.value.get() }
218    }
219}
220
221impl<'a, T> AsMut<T> for MutexGuard<'a, T> {
222    fn as_mut(&mut self) -> &'a mut T {
223        self.ensure_access_by_holder();
224        unsafe { &mut *self.mutex.value.get() }
225    }
226}
227
228impl<T> Deref for MutexGuard<'_, T> {
229    type Target = T;
230
231    fn deref(&self) -> &T {
232        self.ensure_access_by_holder();
233        unsafe { &*self.mutex.value.get() }
234    }
235}
236
237impl<T> DerefMut for MutexGuard<'_, T> {
238    fn deref_mut(&mut self) -> &mut T {
239        self.ensure_access_by_holder();
240        unsafe { &mut *self.mutex.value.get() }
241    }
242}
243
244// we are always single-threaded
245unsafe impl<T> Sync for Mutex<T> {}
246
247/// The future returned by the [`lock`](Mutex::lock) method.
248///
249/// The output of the future is the [`MutexGuard`] that can be obtained by using
250/// `await` syntax.
251///
252/// # Examples
253///
254/// In the following example, variable types are annotated explicitly for
255/// demonstration purposes only. Usually, annotating them is unnecessary because
256/// they can be inferred automatically.
257///
258/// ```
259/// use gstd::sync::{Mutex, MutexGuard, MutexLockFuture};
260///
261/// #[gstd::async_main]
262/// async fn main() {
263///     let mutex: Mutex<i32> = Mutex::new(42);
264///     let future: MutexLockFuture<i32> = mutex.lock();
265///     let guard: MutexGuard<i32> = future.await;
266///     let value: i32 = *guard;
267///     assert_eq!(value, 42);
268/// }
269/// # fn main() {}
270/// ```
271pub struct MutexLockFuture<'a, T> {
272    mutex_id: MutexId,
273    mutex: &'a Mutex<T>,
274    // The maximum number of blocks the mutex lock can be owned.
275    // If the value is None, the default value taken from the `Config::mx_lock_duration` is used.
276    own_up_for: Option<BlockCount>,
277}
278
279impl<'a, T> MutexLockFuture<'a, T> {
280    /// Sets the maximum number of blocks the mutex lock can be owned by
281    /// some message before the ownership can be seized by another rival
282    pub fn own_up_for(self, block_count: BlockCount) -> Result<Self> {
283        if block_count == 0 {
284            Err(Error::Gstd(UsageError::ZeroMxLockDuration))
285        } else {
286            Ok(MutexLockFuture {
287                mutex_id: self.mutex_id,
288                mutex: self.mutex,
289                own_up_for: Some(block_count),
290            })
291        }
292    }
293
294    fn acquire_lock_ownership(
295        &mut self,
296        owner_msg_id: MessageId,
297        current_block: BlockNumber,
298    ) -> Poll<MutexGuard<'a, T>> {
299        let owner_deadline_block =
300            current_block.saturating_add(self.own_up_for.unwrap_or_else(Config::mx_lock_duration));
301        async_runtime::locks().remove_mx_lock_monitor(owner_msg_id, self.mutex_id);
302        if let Some(next_rival_msg_id) = self.mutex.queue.first() {
303            // Give the next rival message a chance to own the lock after this owner
304            // exceeds the lock ownership duration
305            async_runtime::locks().insert_mx_lock_monitor(
306                *next_rival_msg_id,
307                self.mutex_id,
308                owner_deadline_block,
309            );
310        }
311        let locked_by = self.mutex.locked_by_mut();
312        *locked_by = Some((owner_msg_id, owner_deadline_block));
313        Poll::Ready(MutexGuard {
314            mutex: self.mutex,
315            holder_msg_id: owner_msg_id,
316        })
317    }
318
319    fn queue_for_lock_ownership(
320        &mut self,
321        rival_msg_id: MessageId,
322        owner_deadline_block: Option<BlockNumber>,
323    ) -> Poll<MutexGuard<'a, T>> {
324        // If the message is already in the access queue, and we come here,
325        // it means the message has just been woken up from the waitlist.
326        // In that case we do not want to register yet another access attempt
327        // and just go back to the waitlist
328        if !self.mutex.queue.contains(&rival_msg_id) {
329            self.mutex.queue.enqueue(rival_msg_id);
330            if let Some(owner_deadline_block) = owner_deadline_block {
331                // Lock owner did not know about this message when it was getting into
332                // lock ownership. We have to take care of ourselves and give us a chance
333                // to oust the lock owner when the lock ownership duration expires
334                if self.mutex.queue.len() == 1 {
335                    async_runtime::locks().insert_mx_lock_monitor(
336                        rival_msg_id,
337                        self.mutex_id,
338                        owner_deadline_block,
339                    );
340                }
341            }
342        }
343        Poll::Pending
344    }
345}
346
347impl<'a, T> Future for MutexLockFuture<'a, T> {
348    type Output = MutexGuard<'a, T>;
349
350    // In case of locked mutex and an `.await`, function `poll` checks if the
351    // mutex can be taken, else it waits (goes into *waiting queue*).
352    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
353        let current_msg_id = msg::id();
354        let current_block = exec::block_height();
355        let locked_by = self.mutex.locked_by_mut();
356
357        if locked_by.is_none() {
358            return self
359                .get_mut()
360                .acquire_lock_ownership(current_msg_id, current_block);
361        }
362
363        let (lock_owner_msg_id, deadline_block) =
364            (*locked_by).unwrap_or_else(|| unreachable!("Checked above"));
365
366        if current_block < deadline_block {
367            return self
368                .get_mut()
369                .queue_for_lock_ownership(current_msg_id, Some(deadline_block));
370        }
371
372        if let Some(msg_future_task) = async_runtime::futures().get_mut(&lock_owner_msg_id) {
373            msg_future_task.set_lock_exceeded();
374            exec::wake(lock_owner_msg_id).expect("Failed to wake the message");
375        }
376
377        while let Some(next_msg_id) = self.mutex.queue.dequeue() {
378            if next_msg_id == lock_owner_msg_id {
379                continue;
380            }
381            if next_msg_id == current_msg_id {
382                break;
383            }
384            exec::wake(next_msg_id).expect("Failed to wake the message");
385            *locked_by = None;
386            // We have just woken up the next lock owner, but we don't know its ownership
387            // duration, thus we pass None as owner_deadline_block. The woken up message
388            // will give us a chance to own the lock itself by registering a
389            // lock monitor for us
390            return self
391                .get_mut()
392                .queue_for_lock_ownership(current_msg_id, None);
393        }
394
395        self.get_mut()
396            .acquire_lock_ownership(current_msg_id, current_block)
397    }
398}