mz_adapter/
frontend_transaction.rs1use std::time::{Duration, Instant};
13
14use mz_repr::Timestamp;
15use mz_sql::session::vars::{EndTransactionAction, IsolationLevel};
16use mz_timestamp_oracle::TimestampOracle;
17use tokio::sync::watch;
18
19use crate::session::{ReadOnlyCompletion, Session};
20use crate::{AdapterError, ExecuteResponse, PeekClient};
21
22pub(crate) async fn complete_read_only_transaction(
23 session: &mut Session,
24 client: &mut PeekClient,
25 cancel: &mut watch::Receiver<()>,
26 completion: ReadOnlyCompletion,
27) -> Result<ExecuteResponse, AdapterError> {
28 cancel.borrow_and_update();
31 client
32 .coordinator_client()
33 .metrics()
34 .frontend_transaction_completions
35 .inc();
36 let result = async {
37 session.apply_external_metadata_updates();
38 let catalog = client.catalog_snapshot("end_transaction").await;
39 mz_sql::rbac::check_session_roles(&catalog.for_session(session), session)?;
40
41 if let Some(context) = &completion.timestamp {
42 match session.vars().transaction_isolation() {
43 IsolationLevel::StrictSerializable => {
44 let started = Instant::now();
45 let immediately_handled =
46 if let Some((timeline, timestamp)) = context.timestamp_to_linearize() {
47 client
48 .coordinator_client()
49 .metrics()
50 .frontend_transaction_waits
51 .inc();
52 let oracle = client.ensure_oracle(timeline.clone()).await?;
53 wait_for_read_timestamp(&**oracle, *timestamp, cancel).await?
54 } else {
55 client
56 .coordinator_client()
57 .metrics()
58 .linearize_read_fast_path
59 .inc();
60 true
61 };
62 let metrics = client.coordinator_client().metrics();
63 let duration = if immediately_handled {
64 &metrics.linearize_read_immediate_seconds
65 } else {
66 &metrics.linearize_read_delayed_seconds
67 };
68 duration.observe(started.elapsed().as_secs_f64());
69 }
70 IsolationLevel::StrongSessionSerializable => {
71 if let Some((timeline, timestamp)) = context.timeline_timestamp() {
72 session
73 .ensure_timestamp_oracle(timeline.clone())
74 .apply_write(*timestamp);
75 }
76 }
77 IsolationLevel::ReadUncommitted
78 | IsolationLevel::ReadCommitted
79 | IsolationLevel::RepeatableRead
80 | IsolationLevel::Serializable
81 | IsolationLevel::BoundedStaleness(_) => {}
82 }
83 }
84 Ok::<_, AdapterError>(())
85 }
86 .await;
87
88 let action = if result.is_ok() {
89 completion.action
90 } else {
91 EndTransactionAction::Rollback
92 };
93 let _ = session.clear_transaction();
94 let params = session.vars_mut().end_transaction(action);
95 result?;
96 Ok(match action {
97 EndTransactionAction::Commit => ExecuteResponse::TransactionCommitted { params },
98 EndTransactionAction::Rollback => ExecuteResponse::TransactionRolledBack { params },
99 })
100}
101
102async fn wait_for_read_timestamp(
103 oracle: &(dyn TimestampOracle<Timestamp> + Send + Sync),
104 timestamp: Timestamp,
105 cancel: &mut watch::Receiver<()>,
106) -> Result<bool, AdapterError> {
107 let wait = async {
108 let mut immediately_handled = true;
109 loop {
110 #[cfg(debug_assertions)]
111 {
112 fail::fail_point!("frontend_transaction_before_oracle_read");
113 while fail::eval("frontend_transaction_hold_oracle_read", |_| ()).is_some() {
114 tokio::time::sleep(Duration::from_millis(10)).await;
115 }
116 }
117 let current = oracle.read_ts().await;
118 if timestamp <= current {
119 break immediately_handled;
120 }
121 immediately_handled = false;
122 let delay = Duration::from_millis(timestamp.saturating_sub(current).into());
123 tokio::time::sleep(delay.min(Duration::from_secs(1))).await;
124 }
125 };
126 tokio::select! {
127 immediately_handled = wait => Ok(immediately_handled),
128 _ = cancel.changed() => Err(AdapterError::Canceled),
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
136
137 use async_trait::async_trait;
138 use futures::FutureExt;
139 use mz_timestamp_oracle::WriteTimestamp;
140
141 #[derive(Debug, Default)]
142 struct ReadOracle {
143 timestamp: AtomicU64,
144 calls: AtomicUsize,
145 }
146
147 #[async_trait]
148 impl TimestampOracle<Timestamp> for ReadOracle {
149 async fn read_ts(&self) -> Timestamp {
150 self.calls.fetch_add(1, Ordering::SeqCst);
151 self.timestamp.load(Ordering::SeqCst).into()
152 }
153 async fn write_ts(&self) -> WriteTimestamp {
154 unreachable!("read-only completion")
155 }
156 async fn peek_write_ts(&self) -> Timestamp {
157 unreachable!("must use a linearized read")
158 }
159 async fn apply_write(&self, _: Timestamp) {
160 unreachable!("read-only completion")
161 }
162 }
163
164 #[mz_ore::test(tokio::test(start_paused = true))]
165 async fn completion_waits_for_the_shared_oracle() {
166 let oracle = ReadOracle::default();
167 let (_tx, mut cancel) = watch::channel(());
168 let wait = wait_for_read_timestamp(&oracle, 10.into(), &mut cancel);
169 tokio::pin!(wait);
170 assert!(wait.as_mut().now_or_never().is_none());
171 assert_eq!(oracle.calls.load(Ordering::SeqCst), 1);
172 tokio::time::advance(Duration::from_millis(10)).await;
173 assert!(
174 wait.as_mut().now_or_never().is_none(),
175 "wall time alone cannot release the read"
176 );
177 oracle.timestamp.store(10, Ordering::SeqCst);
178 tokio::time::advance(Duration::from_millis(10)).await;
179 assert!(!wait.await.expect("oracle advancement completes the read"));
180 }
181
182 #[mz_ore::test(tokio::test)]
183 async fn completion_wait_is_cancellable_and_old_cancels_are_ignored() {
184 let oracle = ReadOracle::default();
185 let (tx, mut cancel) = watch::channel(());
186 tx.send_replace(());
187 cancel.borrow_and_update();
188 {
189 let wait = wait_for_read_timestamp(&oracle, 10.into(), &mut cancel);
190 tokio::pin!(wait);
191 assert!(wait.as_mut().now_or_never().is_none());
192 tx.send_replace(());
193 assert!(matches!(wait.await, Err(AdapterError::Canceled)));
194 }
195 cancel.borrow_and_update();
196 oracle.timestamp.store(10, Ordering::SeqCst);
197 assert!(
198 wait_for_read_timestamp(&oracle, 10.into(), &mut cancel)
199 .await
200 .is_ok()
201 );
202 }
203
204 #[mz_ore::test(tokio::test)]
205 async fn completion_wait_ends_when_the_connection_is_gone() {
206 let oracle = ReadOracle::default();
207 let (tx, mut cancel) = watch::channel(());
208 drop(tx);
209 assert!(matches!(
210 wait_for_read_timestamp(&oracle, 10.into(), &mut cancel).await,
211 Err(AdapterError::Canceled)
212 ));
213 }
214}