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}