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