mz_timestamp_oracle/
batching_oracle.rs1use std::sync::Arc;
14
15use async_trait::async_trait;
16use mz_ore::cast::CastFrom;
17use tokio::sync::mpsc::UnboundedSender;
18use tokio::sync::oneshot;
19
20use crate::metrics::Metrics;
21use crate::{TimestampOracle, WriteTimestamp};
22
23pub struct BatchingTimestampOracle<T> {
36 inner: Arc<dyn TimestampOracle<T> + Send + Sync>,
37 command_tx: UnboundedSender<Command<T>>,
38}
39
40enum Command<T> {
42 ReadTs(oneshot::Sender<T>),
43}
44
45impl<T> std::fmt::Debug for BatchingTimestampOracle<T> {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("BatchingTimestampOracle").finish()
48 }
49}
50
51impl<T> BatchingTimestampOracle<T>
52where
53 T: Clone + Send + Sync + 'static,
54{
55 pub fn new(metrics: Arc<Metrics>, oracle: Arc<dyn TimestampOracle<T> + Send + Sync>) -> Self {
57 let (command_tx, mut command_rx) = tokio::sync::mpsc::unbounded_channel();
58
59 let task_oracle = Arc::clone(&oracle);
60
61 mz_ore::task::spawn(|| "BatchingTimestampOracle Worker Task", async move {
62 let read_ts_metrics = &metrics.batching.read_ts;
63
64 while let Some(cmd) = command_rx.recv().await {
67 let mut pending_cmds = vec![cmd];
68 while let Ok(cmd) = command_rx.try_recv() {
69 pending_cmds.push(cmd);
70 }
71
72 read_ts_metrics
73 .ops_count
74 .inc_by(u64::cast_from(pending_cmds.len()));
75 read_ts_metrics.batches_count.inc();
76
77 let ts = task_oracle.read_ts().await;
78 for cmd in pending_cmds {
79 match cmd {
80 Command::ReadTs(response_tx) => {
81 let _ = response_tx.send(ts.clone());
84 }
85 }
86 }
87 }
88
89 tracing::debug!("shutting down BatchingTimestampOracle task");
90 });
91
92 Self {
93 inner: oracle,
94 command_tx,
95 }
96 }
97}
98
99async fn worker_task_gone<T>() -> T {
109 tracing::debug!("BatchingTimestampOracle worker task is gone, parking read_ts");
110 std::future::pending().await
111}
112
113#[async_trait]
114impl<T> TimestampOracle<T> for BatchingTimestampOracle<T>
115where
116 T: Send + Sync,
117{
118 async fn write_ts(&self) -> WriteTimestamp<T> {
119 self.inner.write_ts().await
120 }
121
122 async fn peek_write_ts(&self) -> T {
123 self.inner.peek_write_ts().await
124 }
125
126 async fn read_ts(&self) -> T {
127 let (tx, rx) = oneshot::channel();
128
129 if self.command_tx.send(Command::ReadTs(tx)).is_err() {
130 return worker_task_gone().await;
131 }
132
133 match rx.await {
134 Ok(ts) => ts,
135 Err(_) => worker_task_gone().await,
136 }
137 }
138
139 async fn apply_write(&self, write_ts: T) {
140 self.inner.apply_write(write_ts).await
141 }
142}
143
144#[cfg(test)]
145mod tests {
146
147 use mz_ore::metrics::MetricsRegistry;
148 use mz_repr::Timestamp;
149 use tracing::info;
150
151 use crate::postgres_oracle::{PostgresTimestampOracle, PostgresTimestampOracleConfig};
152
153 use super::*;
154
155 #[derive(Debug)]
158 struct PendingOracle;
159
160 #[async_trait]
161 impl TimestampOracle<Timestamp> for PendingOracle {
162 async fn write_ts(&self) -> WriteTimestamp<Timestamp> {
163 std::future::pending().await
164 }
165
166 async fn peek_write_ts(&self) -> Timestamp {
167 std::future::pending().await
168 }
169
170 async fn read_ts(&self) -> Timestamp {
171 std::future::pending().await
172 }
173
174 async fn apply_write(&self, _write_ts: Timestamp) {
175 std::future::pending().await
176 }
177 }
178
179 #[mz_ore::test]
184 fn test_read_ts_waits_when_worker_task_is_gone() {
185 let metrics = Arc::new(Metrics::new(&MetricsRegistry::new()));
186
187 let worker_runtime = tokio::runtime::Runtime::new().expect("can build runtime");
188 let oracle = worker_runtime
189 .block_on(async { BatchingTimestampOracle::new(metrics, Arc::new(PendingOracle)) });
190 drop(worker_runtime);
191
192 let caller_runtime = tokio::runtime::Runtime::new().expect("can build runtime");
193 caller_runtime.block_on(async {
194 let read_ts = std::pin::pin!(oracle.read_ts());
195 assert!(futures::poll!(read_ts).is_pending());
196 });
197 }
198
199 #[mz_ore::test(tokio::test)]
200 #[cfg_attr(miri, ignore)] async fn test_batching_timestamp_oracle() -> Result<(), anyhow::Error> {
202 let config = match PostgresTimestampOracleConfig::new_for_test() {
203 Some(config) => config,
204 None => {
205 info!(
206 "{} env not set: skipping test that uses external service",
207 PostgresTimestampOracleConfig::EXTERNAL_TESTS_POSTGRES_URL
208 );
209 return Ok(());
210 }
211 };
212 let metrics = Arc::new(Metrics::new(&MetricsRegistry::new()));
213
214 crate::tests::timestamp_oracle_impl_test(|timeline, now_fn, initial_ts| {
215 let pg_oracle = PostgresTimestampOracle::open(
217 config.clone(),
218 timeline,
219 initial_ts,
220 now_fn,
221 false, );
223
224 async {
225 let arced_pg_oracle: Arc<dyn TimestampOracle<Timestamp> + Send + Sync> =
226 Arc::new(pg_oracle.await);
227
228 let batching_oracle =
229 BatchingTimestampOracle::new(Arc::clone(&metrics), arced_pg_oracle);
230
231 let arced_oracle: Arc<dyn TimestampOracle<Timestamp> + Send + Sync> =
232 Arc::new(batching_oracle);
233
234 arced_oracle
235 }
236 })
237 .await?;
238
239 Ok(())
240 }
241}