use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_util::sync::CancellationToken;
use tonic::transport::Channel;
use tracing::instrument;
use crate::databricks::zerobus::zerobus_client::ZerobusClient;
use crate::landing_zone::LandingZone;
use crate::{
HeadersProvider, OffsetId, OffsetIdGenerator, StreamConfigurationOptions, StreamType,
TableProperties, ZerobusError, ZerobusResult,
};
mod acks;
mod callback_handler;
mod close;
mod connection;
mod ingest;
mod receiver;
mod sender;
mod supervisor;
mod types;
use types::{IngestRequest, OneshotMap, RecordLandingZone};
#[cfg(feature = "testing")]
pub use callback_handler::CallbackHandlerHarness;
pub(super) const STREAM_TEARDOWN_DRAIN_TIMEOUT_MS: u64 = 500;
#[non_exhaustive]
pub struct ZerobusStream {
pub(crate) stream_id: Option<String>,
pub stream_type: StreamType,
pub headers_provider: Arc<dyn HeadersProvider>,
pub options: StreamConfigurationOptions,
pub(crate) table_properties: TableProperties,
landing_zone: RecordLandingZone,
oneshot_map: Arc<tokio::sync::Mutex<OneshotMap>>,
supervisor_task: tokio::task::JoinHandle<Result<(), ZerobusError>>,
logical_offset_id_generator: OffsetIdGenerator,
logical_last_received_offset_id_tx: tokio::sync::watch::Sender<Option<OffsetId>>,
_logical_last_received_offset_id_rx: tokio::sync::watch::Receiver<Option<OffsetId>>,
failed_records: Arc<RwLock<Vec<crate::EncodedBatch>>>,
is_closed: Arc<AtomicBool>,
sync_mutex: Arc<tokio::sync::Mutex<()>>,
server_error_rx: tokio::sync::watch::Receiver<Option<ZerobusError>>,
cancellation_token: CancellationToken,
callback_handler_task: Option<tokio::task::JoinHandle<()>>,
}
impl ZerobusStream {
#[instrument(level = "debug", skip_all)]
pub(crate) async fn new_stream(
channel: ZerobusClient<Channel>,
table_properties: TableProperties,
headers_provider: Arc<dyn HeadersProvider>,
options: StreamConfigurationOptions,
) -> ZerobusResult<Self> {
let (stream_init_result_tx, stream_init_result_rx) =
tokio::sync::oneshot::channel::<ZerobusResult<String>>();
let (logical_last_received_offset_id_tx, _logical_last_received_offset_id_rx) =
tokio::sync::watch::channel(None);
let landing_zone = Arc::new(LandingZone::<Box<IngestRequest>>::new(
options.max_inflight_requests,
));
let oneshot_map = Arc::new(tokio::sync::Mutex::new(HashMap::new()));
let is_closed = Arc::new(AtomicBool::new(false));
let failed_records = Arc::new(RwLock::new(Vec::new()));
let logical_offset_id_generator = OffsetIdGenerator::default();
let (server_error_tx, server_error_rx) = tokio::sync::watch::channel(None);
let cancellation_token = CancellationToken::new();
let (callback_tx, callback_handler_task) = if options.ack_callback.is_some() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let task = Self::spawn_callback_handler_task(
rx,
options.ack_callback.clone(),
cancellation_token.clone(),
);
(Some(tx), Some(task))
} else {
(None, None)
};
let supervisor_task = tokio::task::spawn(Self::supervisor_task(
channel,
table_properties.clone(),
Arc::clone(&headers_provider),
options.clone(),
Arc::clone(&landing_zone),
Arc::clone(&oneshot_map),
logical_last_received_offset_id_tx.clone(),
Arc::clone(&is_closed),
Arc::clone(&failed_records),
stream_init_result_tx,
server_error_tx,
cancellation_token.clone(),
callback_tx.clone(),
));
let stream_id = Some(stream_init_result_rx.await.map_err(|_| {
ZerobusError::UnexpectedStreamResponseError(
"Supervisor task died before stream creation".to_string(),
)
})??);
let stream = Self {
stream_type: StreamType::Ephemeral,
headers_provider,
options: options.clone(),
table_properties,
stream_id,
landing_zone,
oneshot_map,
supervisor_task,
logical_offset_id_generator,
logical_last_received_offset_id_tx,
_logical_last_received_offset_id_rx,
failed_records,
is_closed,
sync_mutex: Arc::new(tokio::sync::Mutex::new(())),
server_error_rx,
cancellation_token,
callback_handler_task,
};
Ok(stream)
}
}
impl Drop for ZerobusStream {
fn drop(&mut self) {
self.is_closed.store(true, Ordering::Relaxed);
self.cancellation_token.cancel();
self.supervisor_task.abort();
if let Some(callback_handler_task) = self.callback_handler_task.take() {
callback_handler_task.abort();
}
}
}