1 | use crate::sync::batch_semaphore::Semaphore; |
2 | use crate::sync::rwlock::read_guard::RwLockReadGuard; |
3 | use crate::sync::rwlock::write_guard_mapped::RwLockMappedWriteGuard; |
4 | use std::marker::PhantomData; |
5 | use std::{fmt, mem, ops}; |
6 | |
7 | /// RAII structure used to release the exclusive write access of a lock when |
8 | /// dropped. |
9 | /// |
10 | /// This structure is created by the [`write`] method |
11 | /// on [`RwLock`]. |
12 | /// |
13 | /// [`write`]: method@crate::sync::RwLock::write |
14 | /// [`RwLock`]: struct@crate::sync::RwLock |
15 | #[clippy::has_significant_drop] |
16 | #[must_use = "if unused the RwLock will immediately unlock" ] |
17 | pub struct RwLockWriteGuard<'a, T: ?Sized> { |
18 | // When changing the fields in this struct, make sure to update the |
19 | // `skip_drop` method. |
20 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
21 | pub(super) resource_span: tracing::Span, |
22 | pub(super) permits_acquired: u32, |
23 | pub(super) s: &'a Semaphore, |
24 | pub(super) data: *mut T, |
25 | pub(super) marker: PhantomData<&'a mut T>, |
26 | } |
27 | |
28 | #[allow (dead_code)] // Unused fields are still used in Drop. |
29 | struct Inner<'a, T: ?Sized> { |
30 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
31 | resource_span: tracing::Span, |
32 | permits_acquired: u32, |
33 | s: &'a Semaphore, |
34 | data: *mut T, |
35 | } |
36 | |
37 | impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> { |
38 | fn skip_drop(self) -> Inner<'a, T> { |
39 | let me = mem::ManuallyDrop::new(self); |
40 | // SAFETY: This duplicates the values in every field of the guard, then |
41 | // forgets the originals, so in the end no value is duplicated. |
42 | Inner { |
43 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
44 | resource_span: unsafe { std::ptr::read(&me.resource_span) }, |
45 | permits_acquired: me.permits_acquired, |
46 | s: me.s, |
47 | data: me.data, |
48 | } |
49 | } |
50 | |
51 | /// Makes a new [`RwLockMappedWriteGuard`] for a component of the locked data. |
52 | /// |
53 | /// This operation cannot fail as the `RwLockWriteGuard` passed in already |
54 | /// locked the data. |
55 | /// |
56 | /// This is an associated function that needs to be used as |
57 | /// `RwLockWriteGuard::map(..)`. A method would interfere with methods of |
58 | /// the same name on the contents of the locked data. |
59 | /// |
60 | /// This is an asynchronous version of [`RwLockWriteGuard::map`] from the |
61 | /// [`parking_lot` crate]. |
62 | /// |
63 | /// [`RwLockMappedWriteGuard`]: struct@crate::sync::RwLockMappedWriteGuard |
64 | /// [`RwLockWriteGuard::map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.map |
65 | /// [`parking_lot` crate]: https://crates.io/crates/parking_lot |
66 | /// |
67 | /// # Examples |
68 | /// |
69 | /// ``` |
70 | /// use tokio::sync::{RwLock, RwLockWriteGuard}; |
71 | /// |
72 | /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
73 | /// struct Foo(u32); |
74 | /// |
75 | /// # #[tokio::main] |
76 | /// # async fn main() { |
77 | /// let lock = RwLock::new(Foo(1)); |
78 | /// |
79 | /// { |
80 | /// let mut mapped = RwLockWriteGuard::map(lock.write().await, |f| &mut f.0); |
81 | /// *mapped = 2; |
82 | /// } |
83 | /// |
84 | /// assert_eq!(Foo(2), *lock.read().await); |
85 | /// # } |
86 | /// ``` |
87 | #[inline ] |
88 | pub fn map<F, U: ?Sized>(mut this: Self, f: F) -> RwLockMappedWriteGuard<'a, U> |
89 | where |
90 | F: FnOnce(&mut T) -> &mut U, |
91 | { |
92 | let data = f(&mut *this) as *mut U; |
93 | let this = this.skip_drop(); |
94 | |
95 | RwLockMappedWriteGuard { |
96 | permits_acquired: this.permits_acquired, |
97 | s: this.s, |
98 | data, |
99 | marker: PhantomData, |
100 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
101 | resource_span: this.resource_span, |
102 | } |
103 | } |
104 | |
105 | /// Makes a new [`RwLockReadGuard`] for a component of the locked data. |
106 | /// |
107 | /// This operation cannot fail as the `RwLockWriteGuard` passed in already |
108 | /// locked the data. |
109 | /// |
110 | /// This is an associated function that needs to be used as |
111 | /// `RwLockWriteGuard::downgrade_map(..)`. A method would interfere with methods of |
112 | /// the same name on the contents of the locked data. |
113 | /// |
114 | /// This is equivalent to a combination of asynchronous [`RwLockWriteGuard::map`] and [`RwLockWriteGuard::downgrade`] |
115 | /// from the [`parking_lot` crate]. |
116 | /// |
117 | /// Inside of `f`, you retain exclusive access to the data, despite only being given a `&T`. Handing out a |
118 | /// `&mut T` would result in unsoundness, as you could use interior mutability. |
119 | /// |
120 | /// [`RwLockMappedWriteGuard`]: struct@crate::sync::RwLockMappedWriteGuard |
121 | /// [`RwLockWriteGuard::map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.map |
122 | /// [`RwLockWriteGuard::downgrade`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.downgrade |
123 | /// [`parking_lot` crate]: https://crates.io/crates/parking_lot |
124 | /// |
125 | /// # Examples |
126 | /// |
127 | /// ``` |
128 | /// use tokio::sync::{RwLock, RwLockWriteGuard}; |
129 | /// |
130 | /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
131 | /// struct Foo(u32); |
132 | /// |
133 | /// # #[tokio::main] |
134 | /// # async fn main() { |
135 | /// let lock = RwLock::new(Foo(1)); |
136 | /// |
137 | /// let mapped = RwLockWriteGuard::downgrade_map(lock.write().await, |f| &f.0); |
138 | /// let foo = lock.read().await; |
139 | /// assert_eq!(foo.0, *mapped); |
140 | /// # } |
141 | /// ``` |
142 | #[inline ] |
143 | pub fn downgrade_map<F, U: ?Sized>(this: Self, f: F) -> RwLockReadGuard<'a, U> |
144 | where |
145 | F: FnOnce(&T) -> &U, |
146 | { |
147 | let data = f(&*this) as *const U; |
148 | let this = this.skip_drop(); |
149 | let guard = RwLockReadGuard { |
150 | s: this.s, |
151 | data, |
152 | marker: PhantomData, |
153 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
154 | resource_span: this.resource_span, |
155 | }; |
156 | |
157 | // Release all but one of the permits held by the write guard |
158 | let to_release = (this.permits_acquired - 1) as usize; |
159 | this.s.release(to_release); |
160 | |
161 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
162 | guard.resource_span.in_scope(|| { |
163 | tracing::trace!( |
164 | target: "runtime::resource::state_update" , |
165 | write_locked = false, |
166 | write_locked.op = "override" , |
167 | ) |
168 | }); |
169 | |
170 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
171 | guard.resource_span.in_scope(|| { |
172 | tracing::trace!( |
173 | target: "runtime::resource::state_update" , |
174 | current_readers = 1, |
175 | current_readers.op = "add" , |
176 | ) |
177 | }); |
178 | |
179 | guard |
180 | } |
181 | |
182 | /// Attempts to make a new [`RwLockMappedWriteGuard`] for a component of |
183 | /// the locked data. The original guard is returned if the closure returns |
184 | /// `None`. |
185 | /// |
186 | /// This operation cannot fail as the `RwLockWriteGuard` passed in already |
187 | /// locked the data. |
188 | /// |
189 | /// This is an associated function that needs to be |
190 | /// used as `RwLockWriteGuard::try_map(...)`. A method would interfere with |
191 | /// methods of the same name on the contents of the locked data. |
192 | /// |
193 | /// This is an asynchronous version of [`RwLockWriteGuard::try_map`] from |
194 | /// the [`parking_lot` crate]. |
195 | /// |
196 | /// [`RwLockMappedWriteGuard`]: struct@crate::sync::RwLockMappedWriteGuard |
197 | /// [`RwLockWriteGuard::try_map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.try_map |
198 | /// [`parking_lot` crate]: https://crates.io/crates/parking_lot |
199 | /// |
200 | /// # Examples |
201 | /// |
202 | /// ``` |
203 | /// use tokio::sync::{RwLock, RwLockWriteGuard}; |
204 | /// |
205 | /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
206 | /// struct Foo(u32); |
207 | /// |
208 | /// # #[tokio::main] |
209 | /// # async fn main() { |
210 | /// let lock = RwLock::new(Foo(1)); |
211 | /// |
212 | /// { |
213 | /// let guard = lock.write().await; |
214 | /// let mut guard = RwLockWriteGuard::try_map(guard, |f| Some(&mut f.0)).expect("should not fail" ); |
215 | /// *guard = 2; |
216 | /// } |
217 | /// |
218 | /// assert_eq!(Foo(2), *lock.read().await); |
219 | /// # } |
220 | /// ``` |
221 | #[inline ] |
222 | pub fn try_map<F, U: ?Sized>( |
223 | mut this: Self, |
224 | f: F, |
225 | ) -> Result<RwLockMappedWriteGuard<'a, U>, Self> |
226 | where |
227 | F: FnOnce(&mut T) -> Option<&mut U>, |
228 | { |
229 | let data = match f(&mut *this) { |
230 | Some(data) => data as *mut U, |
231 | None => return Err(this), |
232 | }; |
233 | let this = this.skip_drop(); |
234 | |
235 | Ok(RwLockMappedWriteGuard { |
236 | permits_acquired: this.permits_acquired, |
237 | s: this.s, |
238 | data, |
239 | marker: PhantomData, |
240 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
241 | resource_span: this.resource_span, |
242 | }) |
243 | } |
244 | |
245 | /// Attempts to make a new [`RwLockReadGuard`] for a component of |
246 | /// the locked data. The original guard is returned if the closure returns |
247 | /// `None`. |
248 | /// |
249 | /// This operation cannot fail as the `RwLockWriteGuard` passed in already |
250 | /// locked the data. |
251 | /// |
252 | /// This is an associated function that needs to be |
253 | /// used as `RwLockWriteGuard::try_downgrade_map(...)`. A method would interfere with |
254 | /// methods of the same name on the contents of the locked data. |
255 | /// |
256 | /// This is equivalent to a combination of asynchronous [`RwLockWriteGuard::try_map`] and [`RwLockWriteGuard::downgrade`] |
257 | /// from the [`parking_lot` crate]. |
258 | /// |
259 | /// Inside of `f`, you retain exclusive access to the data, despite only being given a `&T`. Handing out a |
260 | /// `&mut T` would result in unsoundness, as you could use interior mutability. |
261 | /// |
262 | /// If this function returns `Err(...)`, the lock is never unlocked nor downgraded. |
263 | /// |
264 | /// [`RwLockMappedWriteGuard`]: struct@crate::sync::RwLockMappedWriteGuard |
265 | /// [`RwLockWriteGuard::map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.map |
266 | /// [`RwLockWriteGuard::downgrade`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.downgrade |
267 | /// [`parking_lot` crate]: https://crates.io/crates/parking_lot |
268 | /// |
269 | /// # Examples |
270 | /// |
271 | /// ``` |
272 | /// use tokio::sync::{RwLock, RwLockWriteGuard}; |
273 | /// |
274 | /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
275 | /// struct Foo(u32); |
276 | /// |
277 | /// # #[tokio::main] |
278 | /// # async fn main() { |
279 | /// let lock = RwLock::new(Foo(1)); |
280 | /// |
281 | /// let guard = RwLockWriteGuard::try_downgrade_map(lock.write().await, |f| Some(&f.0)).expect("should not fail" ); |
282 | /// let foo = lock.read().await; |
283 | /// assert_eq!(foo.0, *guard); |
284 | /// # } |
285 | /// ``` |
286 | #[inline ] |
287 | pub fn try_downgrade_map<F, U: ?Sized>(this: Self, f: F) -> Result<RwLockReadGuard<'a, U>, Self> |
288 | where |
289 | F: FnOnce(&T) -> Option<&U>, |
290 | { |
291 | let data = match f(&*this) { |
292 | Some(data) => data as *const U, |
293 | None => return Err(this), |
294 | }; |
295 | let this = this.skip_drop(); |
296 | let guard = RwLockReadGuard { |
297 | s: this.s, |
298 | data, |
299 | marker: PhantomData, |
300 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
301 | resource_span: this.resource_span, |
302 | }; |
303 | |
304 | // Release all but one of the permits held by the write guard |
305 | let to_release = (this.permits_acquired - 1) as usize; |
306 | this.s.release(to_release); |
307 | |
308 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
309 | guard.resource_span.in_scope(|| { |
310 | tracing::trace!( |
311 | target: "runtime::resource::state_update" , |
312 | write_locked = false, |
313 | write_locked.op = "override" , |
314 | ) |
315 | }); |
316 | |
317 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
318 | guard.resource_span.in_scope(|| { |
319 | tracing::trace!( |
320 | target: "runtime::resource::state_update" , |
321 | current_readers = 1, |
322 | current_readers.op = "add" , |
323 | ) |
324 | }); |
325 | |
326 | Ok(guard) |
327 | } |
328 | |
329 | /// Converts this `RwLockWriteGuard` into an `RwLockMappedWriteGuard`. This |
330 | /// method can be used to store a non-mapped guard in a struct field that |
331 | /// expects a mapped guard. |
332 | /// |
333 | /// This is equivalent to calling `RwLockWriteGuard::map(guard, |me| me)`. |
334 | #[inline ] |
335 | pub fn into_mapped(this: Self) -> RwLockMappedWriteGuard<'a, T> { |
336 | RwLockWriteGuard::map(this, |me| me) |
337 | } |
338 | |
339 | /// Atomically downgrades a write lock into a read lock without allowing |
340 | /// any writers to take exclusive access of the lock in the meantime. |
341 | /// |
342 | /// **Note:** This won't *necessarily* allow any additional readers to acquire |
343 | /// locks, since [`RwLock`] is fair and it is possible that a writer is next |
344 | /// in line. |
345 | /// |
346 | /// Returns an RAII guard which will drop this read access of the `RwLock` |
347 | /// when dropped. |
348 | /// |
349 | /// # Examples |
350 | /// |
351 | /// ``` |
352 | /// # use tokio::sync::RwLock; |
353 | /// # use std::sync::Arc; |
354 | /// # |
355 | /// # #[tokio::main] |
356 | /// # async fn main() { |
357 | /// let lock = Arc::new(RwLock::new(1)); |
358 | /// |
359 | /// let n = lock.write().await; |
360 | /// |
361 | /// let cloned_lock = lock.clone(); |
362 | /// let handle = tokio::spawn(async move { |
363 | /// *cloned_lock.write().await = 2; |
364 | /// }); |
365 | /// |
366 | /// let n = n.downgrade(); |
367 | /// assert_eq!(*n, 1, "downgrade is atomic" ); |
368 | /// |
369 | /// drop(n); |
370 | /// handle.await.unwrap(); |
371 | /// assert_eq!(*lock.read().await, 2, "second writer obtained write lock" ); |
372 | /// # } |
373 | /// ``` |
374 | /// |
375 | /// [`RwLock`]: struct@crate::sync::RwLock |
376 | pub fn downgrade(self) -> RwLockReadGuard<'a, T> { |
377 | let this = self.skip_drop(); |
378 | let guard = RwLockReadGuard { |
379 | s: this.s, |
380 | data: this.data, |
381 | marker: PhantomData, |
382 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
383 | resource_span: this.resource_span, |
384 | }; |
385 | |
386 | // Release all but one of the permits held by the write guard |
387 | let to_release = (this.permits_acquired - 1) as usize; |
388 | this.s.release(to_release); |
389 | |
390 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
391 | guard.resource_span.in_scope(|| { |
392 | tracing::trace!( |
393 | target: "runtime::resource::state_update" , |
394 | write_locked = false, |
395 | write_locked.op = "override" , |
396 | ) |
397 | }); |
398 | |
399 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
400 | guard.resource_span.in_scope(|| { |
401 | tracing::trace!( |
402 | target: "runtime::resource::state_update" , |
403 | current_readers = 1, |
404 | current_readers.op = "add" , |
405 | ) |
406 | }); |
407 | |
408 | guard |
409 | } |
410 | } |
411 | |
412 | impl<T: ?Sized> ops::Deref for RwLockWriteGuard<'_, T> { |
413 | type Target = T; |
414 | |
415 | fn deref(&self) -> &T { |
416 | unsafe { &*self.data } |
417 | } |
418 | } |
419 | |
420 | impl<T: ?Sized> ops::DerefMut for RwLockWriteGuard<'_, T> { |
421 | fn deref_mut(&mut self) -> &mut T { |
422 | unsafe { &mut *self.data } |
423 | } |
424 | } |
425 | |
426 | impl<'a, T: ?Sized> fmt::Debug for RwLockWriteGuard<'a, T> |
427 | where |
428 | T: fmt::Debug, |
429 | { |
430 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
431 | fmt::Debug::fmt(&**self, f) |
432 | } |
433 | } |
434 | |
435 | impl<'a, T: ?Sized> fmt::Display for RwLockWriteGuard<'a, T> |
436 | where |
437 | T: fmt::Display, |
438 | { |
439 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
440 | fmt::Display::fmt(&**self, f) |
441 | } |
442 | } |
443 | |
444 | impl<'a, T: ?Sized> Drop for RwLockWriteGuard<'a, T> { |
445 | fn drop(&mut self) { |
446 | self.s.release(self.permits_acquired as usize); |
447 | |
448 | #[cfg (all(tokio_unstable, feature = "tracing" ))] |
449 | self.resource_span.in_scope(|| { |
450 | tracing::trace!( |
451 | target: "runtime::resource::state_update" , |
452 | write_locked = false, |
453 | write_locked.op = "override" , |
454 | ) |
455 | }); |
456 | } |
457 | } |
458 | |