liboxia 0.0.2

Liboxia is a Rust library designed for both native Rust applications and for integration with other languages via its C Foreign Function Interface (FFI). It serves as a client SDK for Oxia, a distributed key-value store, enabling robust, asynchronous data operations.
Documentation
use crate::batch::{Batch, ReadBatch, WriteBatch};
use crate::errors::OxiaError;
use crate::errors::OxiaError::UnexpectedStatus;
use crate::operations::Operation;
use crate::provider_manager::ProviderManager;
use crate::shard_manager::ShardManager;
use crate::write_stream_manager::WriteStreamManager;
use log::info;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use tokio::sync::{mpsc, Mutex};
use tokio::task::JoinHandle;
use tokio::time::interval;
use tokio_util::sync::CancellationToken;

#[derive(Clone)]
pub enum Batcher {
    Read,
    Write,
}

impl Batcher {
    fn create_batch(
        &self,
        shard_id: i64,
        shard_manager: Arc<ShardManager>,
        provider_manager: Arc<ProviderManager>,
        write_stream_manager: Arc<WriteStreamManager>,
    ) -> Batch {
        match self {
            Batcher::Read => Batch::Read(ReadBatch::new(shard_id, shard_manager, provider_manager)),
            Batcher::Write => Batch::Write(WriteBatch::new(shard_id, write_stream_manager)),
        }
    }
}

pub struct BatchManager {
    context: CancellationToken,
    tx: UnboundedSender<Operation>,
    batch_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
}

impl Drop for BatchManager {
    fn drop(&mut self) {
        self.context.cancel();
    }
}

impl BatchManager {
    pub fn new(
        shard_id: i64,
        batcher: Batcher,
        shard_manager: Arc<ShardManager>,
        provider_manager: Arc<ProviderManager>,
        write_stream_manager: Arc<WriteStreamManager>,
        batch_linger: Duration,
        batch_max_size: u32,
    ) -> Self {
        let (tx, rx) = mpsc::unbounded_channel();
        let context = CancellationToken::new();
        let handle = tokio::spawn(start_batcher(
            context.clone(),
            rx,
            shard_id,
            batcher,
            shard_manager,
            provider_manager,
            write_stream_manager,
            batch_linger,
            batch_max_size,
        ));
        BatchManager {
            context,
            tx,
            batch_handle: Arc::new(Mutex::new(Some(handle))),
        }
    }

    pub fn add(&self, operation: Operation) -> Result<(), OxiaError> {
        self.tx
            .send(operation)
            .map_err(|err| UnexpectedStatus(err.to_string()))?;
        Ok(())
    }

    pub async fn shutdown(self) -> Result<(), OxiaError> {
        self.context.cancel();
        let mut handle_guard = self.batch_handle.lock().await;
        if let Some(handle) = handle_guard.take() {
            handle
                .await
                .map_err(|err| UnexpectedStatus(err.to_string()))?;
        }
        Ok(())
    }
}

async fn start_batcher(
    context: CancellationToken,
    mut rx: UnboundedReceiver<Operation>,
    shard_id: i64,
    batcher: Batcher,
    shard_manager: Arc<ShardManager>,
    provider_manager: Arc<ProviderManager>,
    write_stream_manager: Arc<WriteStreamManager>,
    batch_linger: Duration,
    _batch_max_size: u32,
) {
    let mut buffer = Vec::new();
    let mut batch = batcher.create_batch(
        shard_id,
        shard_manager.clone(),
        provider_manager.clone(),
        write_stream_manager.clone(),
    );
    let mut interval = interval(batch_linger);
    loop {
        tokio::select! {
             _ = context.cancelled() => {
                info!("Exit batcher due to context canceled.");
                return
            },
            _ = interval.tick() => {
                if batch.is_empty() {
                   continue
                }
                batch.flush().await;
                batch = batcher.create_batch( shard_id, shard_manager.clone(), provider_manager.clone(), write_stream_manager.clone());
            }
            size = rx.recv_many(&mut buffer, usize::MAX) => {
                if size == 0 {
                    info!("Exit batcher due to channel closed.");
                    return
                }
                for operation  in buffer.drain(..) {
                    if !batch.can_add(&operation) {
                        batch.flush().await;
                        batch = batcher.create_batch( shard_id, shard_manager.clone(), provider_manager.clone(), write_stream_manager.clone());
                        interval.reset();
                    }
                    batch.add(operation);
                }
            }
        }
    }
}