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