oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1use crate::{Doc, Origin, Store, Transaction, TransactionMut};
2use async_lock::futures::{Read, Write};
3use std::future::Future;
4use std::pin::Pin;
5use std::task::{Context, Poll};
6use thiserror::Error;
7
8/// Trait implemented by [Doc] and shared types, used for carrying over the responsibilities of
9/// creating new transactions, used as a unit of work in Yrs.
10pub trait Transact {
11 /// Creates and returns a lightweight read-only transaction.
12 ///
13 /// # Errors
14 ///
15 /// While it's possible to have multiple read-only transactions active at the same time,
16 /// this method will return a [TransactionAcqError::SharedAcqFailed] error whenever called
17 /// while a read-write transaction (see: [Self::try_transact_mut]) is active at the same time.
18 fn try_transact(&self) -> Result<Transaction, TransactionAcqError>;
19
20 /// Creates and returns a read-write capable transaction. This transaction can be used to
21 /// mutate the contents of underlying document store and upon dropping or committing it may
22 /// subscription callbacks.
23 ///
24 /// # Errors
25 ///
26 /// Only one read-write transaction can be active at the same time. If any other transaction -
27 /// be it a read-write or read-only one - is active at the same time, this method will return
28 /// a [TransactionAcqError::ExclusiveAcqFailed] error.
29 fn try_transact_mut(&self) -> Result<TransactionMut, TransactionAcqError>;
30
31 /// Creates and returns a read-write capable transaction with an `origin` classifier attached.
32 /// This transaction can be used to mutate the contents of underlying document store and upon
33 /// dropping or committing it may subscription callbacks.
34 ///
35 /// An `origin` may be used to identify context of operations made (example updates performed
36 /// locally vs. incoming from remote replicas) and it's used i.e. by [`UndoManager`][crate::undo::UndoManager].
37 ///
38 /// # Errors
39 ///
40 /// Only one read-write transaction can be active at the same time. If any other transaction -
41 /// be it a read-write or read-only one - is active at the same time, this method will return
42 /// a [TransactionAcqError::ExclusiveAcqFailed] error.
43 fn try_transact_mut_with<T>(&self, origin: T) -> Result<TransactionMut, TransactionAcqError>
44 where
45 T: Into<Origin>;
46
47 /// Creates and returns a read-write capable transaction with an `origin` classifier attached.
48 /// This transaction can be used to mutate the contents of underlying document store and upon
49 /// dropping or committing it may subscription callbacks.
50 ///
51 /// An `origin` may be used to identify context of operations made (example updates performed
52 /// locally vs. incoming from remote replicas) and it's used i.e. by [`UndoManager`][crate::undo::UndoManager].
53 ///
54 /// # Errors
55 ///
56 /// Only one read-write transaction can be active at the same time. If any other transaction -
57 /// be it a read-write or read-only one - is active at the same time, this method will
58 /// block the thread until exclusive access can be acquired, unless building for wasm
59 /// which panics. If blocking is undesirable, use `try_transact_mut_with(origin).unwrap()` instead`.
60 fn transact_mut_with<T>(&self, origin: T) -> TransactionMut
61 where
62 T: Into<Origin>;
63
64 /// Creates and returns a lightweight read-only transaction.
65 ///
66 /// # Errors
67 ///
68 /// While it's possible to have multiple read-only transactions active at the same time,
69 /// this method will block the thread until exclusive access can be acquired, unless building for wasm
70 /// which panics. If blocking is undesirable, use `try_transact(origin).unwrap()` instead`.
71 fn transact(&self) -> Transaction;
72
73 /// Creates and returns a read-write capable transaction. This transaction can be used to
74 /// mutate the contents of underlying document store and upon dropping or committing it may
75 /// subscription callbacks.
76 ///
77 /// # Errors
78 ///
79 /// Only one read-write transaction can be active at the same time. If any other transaction -
80 /// be it a read-write or read-only one - is active at the same time, this method will block
81 /// the thread until exclusive access can be acquired, unless building for wasm
82 /// which panics. If blocking is undesirable, use `try_transact_mut(origin).unwrap()` instead`.
83 fn transact_mut(&self) -> TransactionMut;
84}
85
86impl Transact for Doc {
87 fn try_transact(&self) -> Result<Transaction, TransactionAcqError> {
88 match self.store.try_read() {
89 Some(store) => Ok(Transaction::new(store)),
90 None => Err(TransactionAcqError::SharedAcqFailed),
91 }
92 }
93
94 fn try_transact_mut(&self) -> Result<TransactionMut, TransactionAcqError> {
95 match self.store.try_write() {
96 Some(store) => Ok(TransactionMut::new(self.clone(), store, None)),
97 None => Err(TransactionAcqError::ExclusiveAcqFailed),
98 }
99 }
100
101 fn try_transact_mut_with<T>(&self, origin: T) -> Result<TransactionMut, TransactionAcqError>
102 where
103 T: Into<Origin>,
104 {
105 match self.store.try_write() {
106 Some(store) => Ok(TransactionMut::new(
107 self.clone(),
108 store,
109 Some(origin.into()),
110 )),
111 None => Err(TransactionAcqError::ExclusiveAcqFailed),
112 }
113 }
114
115 fn transact_mut_with<T>(&self, origin: T) -> TransactionMut
116 where
117 T: Into<Origin>,
118 {
119 let lock = self.store.write_blocking();
120 TransactionMut::new(self.clone(), lock, Some(origin.into()))
121 }
122
123 fn transact(&self) -> Transaction {
124 let lock = self.store.read_blocking();
125 Transaction::new(lock)
126 }
127
128 fn transact_mut(&self) -> TransactionMut {
129 let lock = self.store.write_blocking();
130 TransactionMut::new(self.clone(), lock, None)
131 }
132}
133
134/// Trait implemented by [Doc] and shared types, used for carrying over the responsibilities of
135/// creating new transactions, used as a unit of work in Yrs.
136pub trait AsyncTransact<'doc> {
137 type Read: Future<Output = Transaction<'doc>>;
138 type Write: Future<Output = TransactionMut<'doc>>;
139
140 fn transact(&'doc self) -> Self::Read;
141 fn transact_mut(&'doc self) -> Self::Write;
142
143 /// Creates and returns a read-write capable transaction with an `origin` classifier attached.
144 /// This transaction can be used to mutate the contents of underlying document store and upon
145 /// dropping or committing it may subscription callbacks.
146 ///
147 /// An `origin` may be used to identify context of operations made (example updates performed
148 /// locally vs. incoming from remote replicas) and it's used i.e. by [`UndoManager`][crate::undo::UndoManager].
149 fn transact_mut_with<T>(&'doc self, origin: T) -> Self::Write
150 where
151 T: Into<Origin>;
152}
153
154impl<'doc> AsyncTransact<'doc> for Doc {
155 type Read = AcquireTransaction<'doc>;
156 type Write = AcquireTransactionMut<'doc>;
157
158 fn transact(&'doc self) -> Self::Read {
159 let fut = self.store.read_async();
160 AcquireTransaction { fut }
161 }
162
163 fn transact_mut(&'doc self) -> Self::Write {
164 let fut = self.store.write_async();
165 AcquireTransactionMut {
166 doc: self.clone(),
167 origin: None,
168 fut,
169 }
170 }
171
172 fn transact_mut_with<T>(&'doc self, origin: T) -> Self::Write
173 where
174 T: Into<Origin>,
175 {
176 let fut = self.store.write_async();
177 AcquireTransactionMut {
178 doc: self.clone(),
179 origin: Some(origin.into()),
180 fut,
181 }
182 }
183}
184
185pub struct AcquireTransaction<'doc> {
186 fut: Read<'doc, Store>,
187}
188
189impl<'doc> Unpin for AcquireTransaction<'doc> {}
190
191impl<'doc> Future for AcquireTransaction<'doc> {
192 type Output = Transaction<'doc>;
193
194 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
195 let pinned = unsafe { Pin::new_unchecked(&mut self.fut) };
196 pinned.poll(cx).map(Transaction::new)
197 }
198}
199
200pub struct AcquireTransactionMut<'doc> {
201 doc: Doc,
202 origin: Option<Origin>,
203 fut: Write<'doc, Store>,
204}
205
206impl<'doc> Unpin for AcquireTransactionMut<'doc> {}
207
208impl<'doc> Future for AcquireTransactionMut<'doc> {
209 type Output = TransactionMut<'doc>;
210
211 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
212 let pinned = unsafe { Pin::new_unchecked(&mut self.fut) };
213 match pinned.poll(cx) {
214 Poll::Ready(store) => {
215 let doc = self.doc.clone();
216 let origin = self.origin.take();
217 Poll::Ready(TransactionMut::new(doc, store, origin))
218 }
219 Poll::Pending => Poll::Pending,
220 }
221 }
222}
223
224#[derive(Error, Debug)]
225pub enum TransactionAcqError {
226 #[error("Failed to acquire read-only transaction. Drop read-write transaction and retry.")]
227 SharedAcqFailed,
228 #[error("Failed to acquire read-write transaction. Drop other transactions and retry.")]
229 ExclusiveAcqFailed,
230 #[error("All references to a parent document containing this structure has been dropped.")]
231 DocumentDropped,
232}
233
234#[cfg(test)]
235mod test {
236 use crate::{Doc, GetString, Text, Transact};
237 use rand::random;
238 use std::sync::{Arc, Barrier};
239 use std::time::{Duration, Instant};
240
241 #[test]
242 fn multi_thread_transact_mut() {
243 let doc = Doc::new();
244 let txt = doc.get_or_insert_text("text");
245
246 const N: usize = 3;
247 let barrier = Arc::new(Barrier::new(N + 1));
248
249 let start = Instant::now();
250 for _ in 0..N {
251 let d = doc.clone();
252 let t = txt.clone();
253 let b = barrier.clone();
254 std::thread::spawn(move || {
255 // let mut txn = d.try_transact_mut().unwrap(); // this will hang forever
256 let mut txn = d.transact_mut();
257 let n = random::<u64>() % 5;
258 std::thread::sleep(Duration::from_millis(n * 100));
259 t.insert(&mut txn, 0, "a");
260 drop(txn);
261 b.wait();
262 });
263 }
264
265 barrier.wait();
266 println!("{} threads executed in {:?}", N, Instant::now() - start);
267
268 let expected: String = (0..N).map(|_| 'a').collect();
269 let txn = doc.transact();
270 let str = txt.get_string(&txn);
271 assert_eq!(str, expected);
272 }
273}
274