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}
258
259impl<T: Timestamp, CB: ContainerBuilder> SharedFuel<T, CB> {
260 pub fn new(handles: Vec<AsyncOutputHandle<T, CB>>) -> Self {
262 Self {
263 handles,
264 outstanding_bytes: 0,
265 }
266 }
267
268 pub async fn give<D>(&mut self, index: usize, cap: &Capability<T>, data: D)
276 where
277 CB: PushInto<D>,
278 D: FuelSize,
279 {
280 let size = data.fuel_size();
281 self.handles[index].give(cap, data);
282 if self.charge(size) {
283 tokio::task::yield_now().await;
284 }
285 }
286
287 fn charge(&mut self, size: usize) -> bool {
289 self.outstanding_bytes += size;
290 let exceeded = self.outstanding_bytes > MAX_OUTSTANDING_BYTES;
291 if exceeded {
292 self.outstanding_bytes = 0;
293 }
294 exceeded
295 }
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct Probe<T> {
301 pub probe_ts: mz_repr::Timestamp,
303 pub upstream_frontier: Antichain<T>,
305}
306
307#[derive(
309 Debug,
310 PartialEq,
311 Eq,
312 Hash,
313 PartialOrd,
314 Ord,
315 Clone,
316 Serialize,
317 Deserialize
318)]
319pub struct SourceOutput<FromTime> {
320 pub key: Row,
322 pub value: Row,
324 pub metadata: Row,
326 pub from_time: FromTime,
328}
329
330#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
332pub struct DecodeResult<FromTime> {
333 pub key: Option<Result<Row, DecodeError>>,
335 pub value: Option<Result<Row, DecodeError>>,
339 pub metadata: Row,
341 pub from_time: FromTime,
343}
344
345#[pin_project]
346pub struct SignaledFuture<F> {
347 #[pin]
348 fut: F,
349 semaphore: PollSemaphore,
350}
351
352impl<F: Future> SignaledFuture<F> {
353 pub fn new(semaphore: Arc<Semaphore>, fut: F) -> Self {
354 Self {
355 fut,
356 semaphore: PollSemaphore::new(semaphore),
357 }
358 }
359}
360
361impl<F: Future> Future for SignaledFuture<F> {
362 type Output = F::Output;
363
364 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
365 let this = self.project();
366 let permit = ready!(this.semaphore.poll_acquire(cx));
367 let ret = this.fut.poll(cx);
368 drop(permit);
369 ret
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use mz_storage_types::sources::MzOffset;
376
377 use super::*;
378
379 #[mz_ore::test]
380 fn resume_uppers_display() {
381 let offset = |o| Antichain::from_elem(MzOffset::from(o));
382 let uppers = ResumeUppers {
383 source: Some(offset(10)),
384 exports: BTreeMap::from([
385 (GlobalId::User(3), offset(30)),
386 (GlobalId::User(4), offset(10)),
387 ]),
388 };
389 assert_eq!(
390 uppers.to_string(),
391 "source={10} exports={u3: {30}, u4: {10}}"
392 );
393
394 let pending = ResumeUppers {
395 source: None,
396 exports: BTreeMap::from([(GlobalId::User(3), Antichain::<MzOffset>::new())]),
397 };
398 assert_eq!(pending.to_string(), "source=pending exports={u3: {}}");
399 }
400}