Skip to main content

style/
shared_lock.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Different objects protected by the same lock
6
7use crate::derives::MallocSizeOf;
8use crate::stylesheets::Origin;
9use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
10use servo_arc::Arc;
11use std::cell::UnsafeCell;
12use std::fmt;
13use std::ptr;
14use style_traits::{CssString, CssStringWriter};
15use to_shmem::{SharedMemoryBuilder, ToShmem};
16
17/// A shared read/write lock that can protect multiple objects.
18///
19/// We don't need the blocking behavior, just the safety. As such we implement
20/// this with an AtomicRefCell, which is ~2x as fast as an RwLock, and panics
21/// (rather than deadlocking) when things go wrong (which is much easier to
22/// debug on CI).
23///
24/// Gecko also needs the ability to have "read only" SharedRwLocks, which are
25/// used for objects stored in (read only) shared memory. Attempting to acquire
26/// write access to objects protected by a read only SharedRwLock will panic.
27#[derive(Clone)]
28pub struct SharedRwLock {
29    cell: Option<Arc<AtomicRefCell<SomethingZeroSizedButTyped>>>,
30}
31
32#[cfg(feature = "servo")]
33malloc_size_of::malloc_size_of_is_0!(SharedRwLock);
34
35#[derive(MallocSizeOf)]
36struct SomethingZeroSizedButTyped;
37
38impl fmt::Debug for SharedRwLock {
39    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40        f.write_str("SharedRwLock")
41    }
42}
43
44impl Default for SharedRwLock {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl SharedRwLock {
51    /// Create a new shared lock.
52    pub fn new() -> Self {
53        SharedRwLock {
54            cell: Some(Arc::new(AtomicRefCell::new(SomethingZeroSizedButTyped))),
55        }
56    }
57
58    /// Create a new global shared lock.
59    pub fn new_leaked() -> Self {
60        SharedRwLock {
61            cell: Some(Arc::new_leaked(AtomicRefCell::new(
62                SomethingZeroSizedButTyped,
63            ))),
64        }
65    }
66
67    /// Create a new read-only shared lock.
68    pub fn read_only() -> Self {
69        SharedRwLock { cell: None }
70    }
71
72    #[inline]
73    fn ptr(&self) -> *const SomethingZeroSizedButTyped {
74        self.cell
75            .as_ref()
76            .map(|cell| cell.as_ptr() as *const _)
77            .unwrap_or(ptr::null())
78    }
79
80    /// Wrap the given data to make its access protected by this lock.
81    pub fn wrap<T>(&self, data: T) -> Locked<T> {
82        Locked {
83            shared_lock: self.clone(),
84            data: UnsafeCell::new(data),
85        }
86    }
87
88    /// Obtain the lock for reading.
89    pub fn read(&self) -> SharedRwLockReadGuard<'_> {
90        SharedRwLockReadGuard(self.cell.as_ref().map(|cell| cell.borrow()))
91    }
92
93    /// Obtain the lock for writing.
94    pub fn write(&self) -> SharedRwLockWriteGuard<'_> {
95        SharedRwLockWriteGuard(self.cell.as_ref().unwrap().borrow_mut())
96    }
97}
98
99/// Proof that a shared lock was obtained for reading.
100pub struct SharedRwLockReadGuard<'a>(Option<AtomicRef<'a, SomethingZeroSizedButTyped>>);
101
102impl<'a> SharedRwLockReadGuard<'a> {
103    #[inline]
104    fn ptr(&self) -> *const SomethingZeroSizedButTyped {
105        self.0
106            .as_ref()
107            .map(|r| &**r as *const _)
108            .unwrap_or(ptr::null())
109    }
110}
111
112/// Proof that a shared lock was obtained for writing.
113pub struct SharedRwLockWriteGuard<'a>(AtomicRefMut<'a, SomethingZeroSizedButTyped>);
114
115/// Data protect by a shared lock.
116pub struct Locked<T> {
117    shared_lock: SharedRwLock,
118    data: UnsafeCell<T>,
119}
120
121// Unsafe: the data inside `UnsafeCell` is only accessed in `read_with` and `write_with`,
122// where guards ensure synchronization.
123unsafe impl<T: Send> Send for Locked<T> {}
124unsafe impl<T: Send + Sync> Sync for Locked<T> {}
125
126impl<T: fmt::Debug> fmt::Debug for Locked<T> {
127    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128        let guard = self.shared_lock.read();
129        self.read_with(&guard).fmt(f)
130    }
131}
132
133impl<T> Locked<T> {
134    #[inline]
135    fn is_read_only_lock(&self) -> bool {
136        self.shared_lock.cell.is_none()
137    }
138
139    fn same_lock_as(&self, ptr: *const SomethingZeroSizedButTyped) -> bool {
140        ptr::eq(self.shared_lock.ptr(), ptr)
141    }
142
143    /// Access the data for reading.
144    pub fn read_with<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a T {
145        assert!(
146            self.is_read_only_lock() || self.same_lock_as(guard.ptr()),
147            "Locked::read_with called with a guard from an unrelated SharedRwLock: {:?} vs. {:?}",
148            self.shared_lock.ptr(),
149            guard.ptr(),
150        );
151
152        let ptr = self.data.get();
153
154        // Unsafe:
155        //
156        // * The guard guarantees that the lock is taken for reading,
157        //   and we’ve checked that it’s the correct lock.
158        // * The returned reference borrows *both* the data and the guard,
159        //   so that it can outlive neither.
160        unsafe { &*ptr }
161    }
162
163    /// Access the data for reading without verifying the lock. Use with caution.
164    pub unsafe fn read_unchecked(&self) -> &T {
165        unsafe {
166            let ptr = self.data.get();
167            &*ptr
168        }
169    }
170
171    /// Access the data for writing.
172    pub fn write_with<'a>(&'a self, guard: &'a mut SharedRwLockWriteGuard) -> &'a mut T {
173        assert!(
174            !self.is_read_only_lock() && self.same_lock_as(&*guard.0),
175            "Locked::write_with called with a guard from a read only or unrelated SharedRwLock"
176        );
177
178        let ptr = self.data.get();
179
180        // Unsafe:
181        //
182        // * The guard guarantees that the lock is taken for writing,
183        //   and we’ve checked that it’s the correct lock.
184        // * The returned reference borrows *both* the data and the guard,
185        //   so that it can outlive neither.
186        // * We require a mutable borrow of the guard,
187        //   so that one write guard can only be used once at a time.
188        unsafe { &mut *ptr }
189    }
190}
191
192impl<T: ToShmem> ToShmem for Locked<T> {
193    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
194        use std::mem::ManuallyDrop;
195
196        let guard = self.shared_lock.read();
197        Ok(ManuallyDrop::new(Locked {
198            shared_lock: SharedRwLock::read_only(),
199            data: UnsafeCell::new(ManuallyDrop::into_inner(
200                self.read_with(&guard).to_shmem(builder)?,
201            )),
202        }))
203    }
204}
205
206#[allow(dead_code)]
207mod compile_time_assert {
208    use super::{SharedRwLockReadGuard, SharedRwLockWriteGuard};
209
210    trait Marker1 {}
211    impl<T: Clone> Marker1 for T {}
212    impl<'a> Marker1 for SharedRwLockReadGuard<'a> {} // Assert SharedRwLockReadGuard: !Clone
213    impl<'a> Marker1 for SharedRwLockWriteGuard<'a> {} // Assert SharedRwLockWriteGuard: !Clone
214
215    trait Marker2 {}
216    impl<T: Copy> Marker2 for T {}
217    impl<'a> Marker2 for SharedRwLockReadGuard<'a> {} // Assert SharedRwLockReadGuard: !Copy
218    impl<'a> Marker2 for SharedRwLockWriteGuard<'a> {} // Assert SharedRwLockWriteGuard: !Copy
219}
220
221/// Like ToCss, but with a lock guard given by the caller, and with the writer specified
222/// concretely rather than with a parameter.
223pub trait ToCssWithGuard {
224    /// Serialize `self` in CSS syntax, writing to `dest`, using the given lock guard.
225    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result;
226
227    /// Serialize `self` in CSS syntax using the given lock guard and return a string.
228    ///
229    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
230    #[inline]
231    fn to_css_string(&self, guard: &SharedRwLockReadGuard) -> CssString {
232        let mut s = CssString::new();
233        self.to_css(guard, &mut s).unwrap();
234        s
235    }
236}
237
238/// A trait to do a deep clone of a given CSS type. Gets a lock and a read
239/// guard, in order to be able to read and clone nested structures.
240pub trait DeepCloneWithLock: Sized {
241    /// Deep clones this object.
242    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self;
243}
244
245/// Guards for a document
246#[derive(Clone)]
247pub struct StylesheetGuards<'a> {
248    /// For author-origin stylesheets.
249    pub author: &'a SharedRwLockReadGuard<'a>,
250
251    /// For user-agent-origin and user-origin stylesheets
252    pub ua_or_user: &'a SharedRwLockReadGuard<'a>,
253}
254
255impl<'a> StylesheetGuards<'a> {
256    /// Get the guard for a given stylesheet origin.
257    pub fn for_origin(&self, origin: Origin) -> &SharedRwLockReadGuard<'a> {
258        match origin {
259            Origin::Author => self.author,
260            _ => self.ua_or_user,
261        }
262    }
263
264    /// Same guard for all origins
265    pub fn same(guard: &'a SharedRwLockReadGuard<'a>) -> Self {
266        StylesheetGuards {
267            author: guard,
268            ua_or_user: guard,
269        }
270    }
271}