1use std::collections::BTreeMap;
16use std::fmt::{self, Debug, Display, Formatter};
17use std::future::Future;
18use std::pin::Pin;
19use std::sync::Arc;
20use std::task::{Context, Poll, ready};
21
22use differential_dataflow::Collection;
23use mz_repr::{Diff, GlobalId, Row};
24use mz_storage_types::errors::{DataflowError, DecodeError};
25use mz_storage_types::sources::SourceTimestamp;
26use mz_timely_util::antichain::AntichainExt;
27use mz_timely_util::builder_async::{AsyncOutputHandle, MAX_OUTSTANDING_BYTES, PressOnDropButton};
28use pin_project::pin_project;
29use serde::{Deserialize, Serialize};
30use timely::ContainerBuilder;
31use timely::container::PushInto;
32use timely::dataflow::operators::Capability;
33use timely::dataflow::{Scope, StreamVec};
34use timely::progress::{Antichain, Timestamp};
35use tokio::sync::Semaphore;
36use tokio_util::sync::PollSemaphore;
37
38use crate::healthcheck::{HealthStatusMessage, StatusNamespace};
39use crate::source::RawSourceCreationConfig;
40
41#[derive(Clone, Debug)]
48pub enum ProgressStatisticsUpdate {
49 SteadyState {
50 offset_known: u64,
51 offset_committed: u64,
52 },
53 Snapshot {
54 records_known: u64,
55 records_staged: u64,
56 },
57}
58
59pub type StackedCollection<'scope, T, D> = Collection<'scope, T, Vec<(D, T, Diff)>>;
60
61#[derive(Clone, Debug, PartialEq)]
66pub struct ResumeUppers<T> {
67 pub source: Option<Antichain<T>>,
70 pub exports: BTreeMap<GlobalId, Antichain<T>>,
76}
77
78impl<T: Display + 'static> Display for ResumeUppers<T> {
81 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
82 match &self.source {
83 Some(source) => write!(f, "source={}", source.pretty())?,
84 None => f.write_str("source=pending")?,
85 }
86 f.write_str(" exports={")?;
87 for (i, (id, upper)) in self.exports.iter().enumerate() {
88 if i > 0 {
89 f.write_str(", ")?;
90 }
91 write!(f, "{id}: {}", upper.pretty())?;
92 }
93 f.write_str("}")
94 }
95}
96
97pub trait SourceRender {
99 type Time: SourceTimestamp;
100 const STATUS_NAMESPACE: StatusNamespace;
101
102 fn render<'scope>(
127 self,
128 scope: Scope<'scope, Self::Time>,
129 config: &RawSourceCreationConfig,
130 resume_uppers: impl futures::Stream<Item = ResumeUppers<Self::Time>> + 'static,
131 start_signal: impl std::future::Future<Output = ()> + 'static,
132 ) -> (
133 BTreeMap<
134 GlobalId,
135 StackedCollection<'scope, Self::Time, Result<SourceMessage, DataflowError>>,
136 >,
137 StreamVec<'scope, Self::Time, HealthStatusMessage>,
138 StreamVec<'scope, Self::Time, Probe<Self::Time>>,
139 Vec<PressOnDropButton>,
140 );
141}
142
143#[derive(Debug, Clone)]
146pub struct SourceMessage {
147 pub key: Row,
149 pub value: Row,
151 pub metadata: Row,
153}
154
155impl SourceMessage {
156 pub fn byte_len(&self) -> usize {
158 self.key.byte_len() + self.value.byte_len() + self.metadata.byte_len()
159 }
160}
161
162pub trait FuelSize {
167 fn fuel_size(&self) -> usize;
168}
169
170impl FuelSize for SourceMessage {
171 fn fuel_size(&self) -> usize {
172 self.byte_len()
173 }
174}
175
176impl FuelSize for Row {
177 fn fuel_size(&self) -> usize {
178 self.byte_len()
179 }
180}
181
182impl FuelSize for Vec<u8> {
183 fn fuel_size(&self) -> usize {
184 self.len()
185 }
186}
187
188impl FuelSize for bytes::Bytes {
189 fn fuel_size(&self) -> usize {
190 self.len()
191 }
192}
193
194impl<T: FuelSize, E: FuelSize> FuelSize for Result<T, E> {
195 fn fuel_size(&self) -> usize {
196 match self {
197 Ok(t) => t.fuel_size(),
198 Err(e) => e.fuel_size(),
199 }
200 }
201}
202
203impl<A: FuelSize, B: FuelSize> FuelSize for (A, B) {
208 fn fuel_size(&self) -> usize {
209 self.0.fuel_size() + self.1.fuel_size()
210 }
211}
212
213impl<A: FuelSize, B: FuelSize, C: FuelSize> FuelSize for (A, B, C) {
214 fn fuel_size(&self) -> usize {
215 self.0.fuel_size() + self.1.fuel_size() + self.2.fuel_size()
216 }
217}
218
219macro_rules! impl_fuel_size_stack {
221 ($($t:ty),* $(,)?) => {
222 $(
223 impl FuelSize for $t {
224 fn fuel_size(&self) -> usize {
225 std::mem::size_of_val(self)
226 }
227 }
228 )*
229 };
230}
231
232impl_fuel_size_stack!(
233 usize,
234 u32,
235 u64,
236 Diff,
237 mz_repr::Timestamp,
238 mz_storage_types::sources::MzOffset,
239 mz_sql_server_util::cdc::Lsn,
240 DataflowError,
241);
242
243impl<P, T> FuelSize for mz_timely_util::order::Partitioned<P, T> {
244 fn fuel_size(&self) -> usize {
245 std::mem::size_of_val(self)
246 }
247}
248
249pub struct SharedFuel<T: Timestamp, CB: ContainerBuilder> {
255 handles: Vec<AsyncOutputHandle<T, CB>>,
256 outstanding_bytes: usize,
257 yielded: bool,
258}
259
260impl<T: Timestamp, CB: ContainerBuilder> SharedFuel<T, CB> {
261 pub fn new(handles: Vec<AsyncOutputHandle<T, CB>>) -> Self {
263 Self {
264 handles,
265 outstanding_bytes: 0,
266 yielded: false,
267 }
268 }
269
270 pub fn take_yielded(&mut self) -> bool {
272 std::mem::take(&mut self.yielded)
273 }
274
275 pub async fn give<D>(&mut self, index: usize, cap: &Capability<T>, data: D)
283 where
284 CB: PushInto<D>,
285 D: FuelSize,
286 {
287 let size = data.fuel_size();
288 self.handles[index].give(cap, data);
289 if self.charge(size) {
290 tokio::task::yield_now().await;
291 }
292 }
293
294 fn charge(&mut self, size: usize) -> bool {
296 self.outstanding_bytes += size;
297 let exceeded = self.outstanding_bytes > MAX_OUTSTANDING_BYTES;
298 if exceeded {
299 self.outstanding_bytes = 0;
300 self.yielded = true;
301 }
302 exceeded
303 }
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct Probe<T> {
309 pub probe_ts: mz_repr::Timestamp,
311 pub upstream_frontier: Antichain<T>,
313}
314
315#[derive(
317 Debug,
318 PartialEq,
319 Eq,
320 Hash,
321 PartialOrd,
322 Ord,
323 Clone,
324 Serialize,
325 Deserialize
326)]
327pub struct SourceOutput<FromTime> {
328 pub key: Row,
330 pub value: Row,
332 pub metadata: Row,
334 pub from_time: FromTime,
336}
337
338#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
340pub struct DecodeResult<FromTime> {
341 pub key: Option<Result<Row, DecodeError>>,
343 pub value: Option<Result<Row, DecodeError>>,
347 pub metadata: Row,
349 pub from_time: FromTime,
351}
352
353#[pin_project]
354pub struct SignaledFuture<F> {
355 #[pin]
356 fut: F,
357 semaphore: PollSemaphore,
358}
359
360impl<F: Future> SignaledFuture<F> {
361 pub fn new(semaphore: Arc<Semaphore>, fut: F) -> Self {
362 Self {
363 fut,
364 semaphore: PollSemaphore::new(semaphore),
365 }
366 }
367}
368
369impl<F: Future> Future for SignaledFuture<F> {
370 type Output = F::Output;
371
372 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
373 let this = self.project();
374 let permit = ready!(this.semaphore.poll_acquire(cx));
375 let ret = this.fut.poll(cx);
376 drop(permit);
377 ret
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use mz_storage_types::sources::MzOffset;
384
385 use super::*;
386
387 #[mz_ore::test]
388 fn resume_uppers_display() {
389 let offset = |o| Antichain::from_elem(MzOffset::from(o));
390 let uppers = ResumeUppers {
391 source: Some(offset(10)),
392 exports: BTreeMap::from([
393 (GlobalId::User(3), offset(30)),
394 (GlobalId::User(4), offset(10)),
395 ]),
396 };
397 assert_eq!(
398 uppers.to_string(),
399 "source={10} exports={u3: {30}, u4: {10}}"
400 );
401
402 let pending = ResumeUppers {
403 source: None,
404 exports: BTreeMap::from([(GlobalId::User(3), Antichain::<MzOffset>::new())]),
405 };
406 assert_eq!(pending.to_string(), "source=pending exports={u3: {}}");
407 }
408}