pgrx/
spinlock.rs

1//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2//LICENSE
3//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4//LICENSE
5//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <contact@pgcentral.org>
6//LICENSE
7//LICENSE All rights reserved.
8//LICENSE
9//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10
11use crate::{pg_sys, PGRXSharedMemory};
12use core::mem::MaybeUninit;
13use core::ops::{Deref, DerefMut};
14use std::cell::UnsafeCell;
15
16/// A Rust locking mechanism which uses a PostgreSQL `slock_t` to lock the data.
17///
18/// Note that this lock does not handle poisoning, unlike [`std::sync::Mutex`],
19/// but is similar in most other respects (aside from supporting cross-process
20/// use).
21///
22/// In most cases, [PgLwLock](crate::PgLwLock) should be preferred. Be aware of
23/// the following documentation from [`storage/spin.h`]:
24///
25/// > Keep in mind the coding rule that spinlocks must not be held for more than
26/// > a few instructions.  In particular, we assume it is not possible for a
27/// > CHECK_FOR_INTERRUPTS() to occur while holding a spinlock, and so it is not
28/// > necessary to do HOLD/RESUME_INTERRUPTS() in these macros.
29///
30/// [`storage/spin.h`]:
31///     https://github.com/postgres/postgres/blob/1f0c4fa255253d223447c2383ad2b384a6f05854/src/include/storage/spin.h
32#[doc(alias = "slock_t")]
33pub struct PgSpinLock<T> {
34    data: UnsafeCell<T>,
35    lock: UnsafeCell<pg_sys::slock_t>,
36}
37
38unsafe impl<T: PGRXSharedMemory> Sync for PgSpinLock<T> {}
39unsafe impl<T: PGRXSharedMemory> PGRXSharedMemory for PgSpinLock<T> {}
40
41impl<T> PgSpinLock<T> {
42    /// Create a new [`PgSpinLock`]. See the type documentation for more info.
43    #[inline]
44    #[doc(alias = "SpinLockInit")]
45    pub fn new(value: T) -> Self {
46        let mut slock = MaybeUninit::zeroed();
47        // Safety: We initialize the `slock_t` before use (and it was likely
48        // already properly initialized by `zeroed()` in the first place, since
49        // it's probably a primitive integer).
50        unsafe {
51            pg_sys::SpinLockInit(slock.as_mut_ptr());
52            Self { data: UnsafeCell::new(value), lock: UnsafeCell::new(slock.assume_init()) }
53        }
54    }
55}
56
57impl<T: PGRXSharedMemory> PgSpinLock<T> {
58    /// Returns true if the spinlock is locked, and false otherwise.
59    #[inline]
60    #[doc(alias = "SpinLockFree")]
61    pub fn is_locked(&self) -> bool {
62        // SAFETY: Doesn't actually modify state, despite appearances.
63        unsafe { !pg_sys::SpinLockFree(self.lock.get()) }
64    }
65
66    /// Returns a lock guard for the spinlock. See the [`PgSpinLockGuard`]
67    /// documentation for more info.
68    #[inline]
69    #[doc(alias = "SpinLockAcquire")]
70    pub fn lock(&self) -> PgSpinLockGuard<'_, T> {
71        unsafe {
72            pg_sys::SpinLockAcquire(self.lock.get());
73            PgSpinLockGuard { data: &mut *self.data.get(), lock: self.lock.get() }
74        }
75    }
76}
77
78/// An implementation of a "scoped lock" for a [`PgSpinLock`]. When this
79/// structure falls out of scope (is dropped), the lock will be unlocked.
80///
81/// See the documentation of [`PgSpinLock`] for more information.
82pub struct PgSpinLockGuard<'a, T: 'a> {
83    data: &'a mut T,
84    lock: *mut pg_sys::slock_t,
85}
86
87unsafe impl<T: PGRXSharedMemory> Sync for PgSpinLockGuard<'_, T> {}
88
89impl<T> Drop for PgSpinLockGuard<'_, T> {
90    #[inline]
91    #[doc(alias = "SpinLockRelease")]
92    fn drop(&mut self) {
93        unsafe { pg_sys::SpinLockRelease(self.lock) };
94    }
95}
96
97impl<T> Deref for PgSpinLockGuard<'_, T> {
98    type Target = T;
99
100    #[inline]
101    fn deref(&self) -> &T {
102        self.data
103    }
104}
105
106impl<T> DerefMut for PgSpinLockGuard<'_, T> {
107    #[inline]
108    fn deref_mut(&mut self) -> &mut T {
109        self.data
110    }
111}