Skip to main content

soil_consensus/
shared_data.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Provides a generic wrapper around shared data. See [`SharedData`] for more information.
8
9use parking_lot::{Condvar, MappedMutexGuard, Mutex, MutexGuard};
10use std::sync::Arc;
11
12/// Created by [`SharedDataLocked::release_mutex`].
13///
14/// As long as the object isn't dropped, the shared data is locked. It is advised to drop this
15/// object when the shared data doesn't need to be locked anymore. To get access to the shared data
16/// [`Self::upgrade`] is provided.
17#[must_use = "Shared data will be unlocked on drop!"]
18pub struct SharedDataLockedUpgradable<T> {
19	shared_data: SharedData<T>,
20}
21
22impl<T> SharedDataLockedUpgradable<T> {
23	/// Upgrade to a *real* mutex guard that will give access to the inner data.
24	///
25	/// Every call to this function will reaquire the mutex again.
26	pub fn upgrade(&mut self) -> MappedMutexGuard<'_, T> {
27		MutexGuard::map(self.shared_data.inner.lock(), |i| &mut i.shared_data)
28	}
29}
30
31impl<T> Drop for SharedDataLockedUpgradable<T> {
32	fn drop(&mut self) {
33		let mut inner = self.shared_data.inner.lock();
34		// It should not be locked anymore
35		inner.locked = false;
36
37		// Notify all waiting threads.
38		self.shared_data.cond_var.notify_all();
39	}
40}
41
42/// Created by [`SharedData::shared_data_locked`].
43///
44/// As long as this object isn't dropped, the shared data is held in a mutex guard and the shared
45/// data is tagged as locked. Access to the shared data is provided through
46/// [`Deref`](std::ops::Deref) and [`DerefMut`](std::ops::DerefMut). The trick is to use
47/// [`Self::release_mutex`] to release the mutex, but still keep the shared data locked. This means
48/// every other thread trying to access the shared data in this time will need to wait until this
49/// lock is freed.
50///
51/// If this object is dropped without calling [`Self::release_mutex`], the lock will be dropped
52/// immediately.
53#[must_use = "Shared data will be unlocked on drop!"]
54pub struct SharedDataLocked<'a, T> {
55	/// The current active mutex guard holding the inner data.
56	inner: MutexGuard<'a, SharedDataInner<T>>,
57	/// The [`SharedData`] instance that created this instance.
58	///
59	/// This instance is only taken on drop or when calling [`Self::release_mutex`].
60	shared_data: Option<SharedData<T>>,
61}
62
63impl<'a, T> SharedDataLocked<'a, T> {
64	/// Release the mutex, but keep the shared data locked.
65	pub fn release_mutex(mut self) -> SharedDataLockedUpgradable<T> {
66		SharedDataLockedUpgradable {
67			shared_data: self.shared_data.take().expect("`shared_data` is only taken on drop; qed"),
68		}
69	}
70}
71
72impl<'a, T> Drop for SharedDataLocked<'a, T> {
73	fn drop(&mut self) {
74		if let Some(shared_data) = self.shared_data.take() {
75			// If the `shared_data` is still set, it means [`Self::release_mutex`] wasn't
76			// called and the lock should be released.
77			self.inner.locked = false;
78
79			// Notify all waiting threads about the released lock.
80			shared_data.cond_var.notify_all();
81		}
82	}
83}
84
85impl<'a, T> std::ops::Deref for SharedDataLocked<'a, T> {
86	type Target = T;
87
88	fn deref(&self) -> &Self::Target {
89		&self.inner.shared_data
90	}
91}
92
93impl<'a, T> std::ops::DerefMut for SharedDataLocked<'a, T> {
94	fn deref_mut(&mut self) -> &mut Self::Target {
95		&mut self.inner.shared_data
96	}
97}
98
99/// Holds the shared data and if the shared data is currently locked.
100///
101/// For more information see [`SharedData`].
102struct SharedDataInner<T> {
103	/// The actual shared data that is protected here against concurrent access.
104	shared_data: T,
105	/// Is `shared_data` currently locked and can not be accessed?
106	locked: bool,
107}
108
109/// Some shared data that provides support for locking this shared data for some time.
110///
111/// When working with consensus engines there is often data that needs to be shared between multiple
112/// parts of the system, like block production and block import. This struct provides an abstraction
113/// for this shared data in a generic way.
114///
115/// The pain point when sharing this data is often the usage of mutex guards in an async context as
116/// this doesn't work for most of them as these guards don't implement `Send`. This abstraction
117/// provides a way to lock the shared data, while not having the mutex locked. So, the data stays
118/// locked and we are still able to hold this lock over an `await` call.
119///
120/// # Example
121///
122/// ```
123/// # use soil_consensus::shared_data::SharedData;
124///
125/// let shared_data = SharedData::new(String::from("hello world"));
126///
127/// let lock = shared_data.shared_data_locked();
128///
129/// let shared_data2 = shared_data.clone();
130/// let join_handle1 = std::thread::spawn(move || {
131///     // This will need to wait for the outer lock to be released before it can access the data.
132///     shared_data2.shared_data().push_str("1");
133/// });
134///
135/// assert_eq!(*lock, "hello world");
136///
137/// // Let us release the mutex, but we still keep it locked.
138/// // Now we could call `await` for example.
139/// let mut lock = lock.release_mutex();
140///
141/// let shared_data2 = shared_data.clone();
142/// let join_handle2 = std::thread::spawn(move || {
143///     shared_data2.shared_data().push_str("2");
144/// });
145///
146/// // We still have the lock and can upgrade it to access the data.
147/// assert_eq!(*lock.upgrade(), "hello world");
148/// lock.upgrade().push_str("3");
149///
150/// drop(lock);
151/// join_handle1.join().unwrap();
152/// join_handle2.join().unwrap();
153///
154/// let data = shared_data.shared_data();
155/// // As we don't know the order of the threads, we need to check for both combinations
156/// assert!(*data == "hello world321" || *data == "hello world312");
157/// ```
158///
159/// # Deadlock
160///
161/// Be aware that this data structure doesn't give you any guarantees that you can not create a
162/// deadlock. If you use [`release_mutex`](SharedDataLocked::release_mutex) followed by a call
163/// to [`shared_data`](Self::shared_data) in the same thread will make your program dead lock.
164/// The same applies when you are using a single threaded executor.
165pub struct SharedData<T> {
166	inner: Arc<Mutex<SharedDataInner<T>>>,
167	cond_var: Arc<Condvar>,
168}
169
170impl<T> Clone for SharedData<T> {
171	fn clone(&self) -> Self {
172		Self { inner: self.inner.clone(), cond_var: self.cond_var.clone() }
173	}
174}
175
176impl<T> SharedData<T> {
177	/// Create a new instance of [`SharedData`] to share the given `shared_data`.
178	pub fn new(shared_data: T) -> Self {
179		Self {
180			inner: Arc::new(Mutex::new(SharedDataInner { shared_data, locked: false })),
181			cond_var: Default::default(),
182		}
183	}
184
185	/// Acquire access to the shared data.
186	///
187	/// This will give mutable access to the shared data. After the returned mutex guard is dropped,
188	/// the shared data is accessible by other threads. So, this function should be used when
189	/// reading/writing of the shared data in a local context is required.
190	///
191	/// When requiring to lock shared data for some longer time, even with temporarily releasing the
192	/// lock, [`Self::shared_data_locked`] should be used.
193	pub fn shared_data(&self) -> MappedMutexGuard<'_, T> {
194		let mut guard = self.inner.lock();
195
196		while guard.locked {
197			self.cond_var.wait(&mut guard);
198		}
199
200		debug_assert!(!guard.locked);
201
202		MutexGuard::map(guard, |i| &mut i.shared_data)
203	}
204
205	/// Acquire access to the shared data and lock it.
206	///
207	/// This will give mutable access to the shared data. The returned [`SharedDataLocked`]
208	/// provides the function [`SharedDataLocked::release_mutex`] to release the mutex, but
209	/// keeping the data locked. This is useful in async contexts for example where the data needs
210	/// to be locked, but a mutex guard can not be held.
211	///
212	/// For an example see [`SharedData`].
213	pub fn shared_data_locked(&self) -> SharedDataLocked<'_, T> {
214		let mut guard = self.inner.lock();
215
216		while guard.locked {
217			self.cond_var.wait(&mut guard);
218		}
219
220		debug_assert!(!guard.locked);
221		guard.locked = true;
222
223		SharedDataLocked { inner: guard, shared_data: Some(self.clone()) }
224	}
225}
226
227#[cfg(test)]
228mod tests {
229	use super::*;
230
231	#[test]
232	fn shared_data_locking_works() {
233		const THREADS: u32 = 100;
234		let shared_data = SharedData::new(0u32);
235
236		let lock = shared_data.shared_data_locked();
237
238		for i in 0..THREADS {
239			let data = shared_data.clone();
240			std::thread::spawn(move || {
241				if i % 2 == 1 {
242					*data.shared_data() += 1;
243				} else {
244					let mut lock = data.shared_data_locked().release_mutex();
245					// Give the other threads some time to wake up
246					std::thread::sleep(std::time::Duration::from_millis(10));
247					*lock.upgrade() += 1;
248				}
249			});
250		}
251
252		let lock = lock.release_mutex();
253		std::thread::sleep(std::time::Duration::from_millis(100));
254		drop(lock);
255
256		while *shared_data.shared_data() < THREADS {
257			std::thread::sleep(std::time::Duration::from_millis(100));
258		}
259	}
260}