orbit-client-pool 0.5.5

Protocol-neutral local and fleet-wide outbound client pools for Orbit process fleets.
Documentation
use std::convert::Infallible;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

use async_trait::async_trait;
use orbit_core::Fleet;
use orbit_pool::{
    Incarnation as PoolIncarnation, Key, LocalFirst, Pool, PoolSpec,
};
use orbit_stream::exchange::{ExchangeSpec, Exchanges, PayloadArenaSpec};
use orbit_stream::{Incarnation as StreamIncarnation, StreamSpec};

use super::*;

const POOL_KIND: u8 = 11;
const CONTROL_KIND: u8 = 12;
const PAYLOAD_KIND: u8 = 13;

struct Manager(Arc<AtomicUsize>);

#[async_trait]
impl ClientManager for Manager {
    type Client = usize;
    type Error = Infallible;

    async fn create(&self) -> Result<Self::Client, Self::Error> {
        Ok(self.0.fetch_add(1, Ordering::Relaxed))
    }

    async fn recycle(
        &self,
        _client: &mut Self::Client,
    ) -> Result<(), Self::Error> {
        Ok(())
    }
}

fn options() -> FleetPoolOptions {
    FleetPoolOptions::new(0, 1, 1, 1, 0, 1)
}

fn pool_with_options<M: ClientManager>(
    manager: M,
    options: FleetPoolOptions,
) -> FleetClientPool<M> {
    let fleet = Arc::new(Fleet::join("client-pool-test", 1).expect("fleet"));
    let shared = Arc::new(
        Pool::with_spec(
            Arc::clone(&fleet),
            PoolIncarnation::new(1),
            PoolSpec::new(POOL_KIND, 4, 4)
                .with_fleet_availability()
                .with_fleet_concurrency(),
        )
        .expect("pool"),
    );
    let exchanges = Arc::new(
        Exchanges::open(
            fleet,
            StreamIncarnation::new(1),
            ExchangeSpec::new(
                StreamSpec::new(CONTROL_KIND, 4, 4_096),
                PayloadArenaSpec::new(PAYLOAD_KIND, 4, 4_096),
            ),
        )
        .expect("exchanges"),
    );
    FleetClientPool::new(
        manager,
        shared,
        exchanges,
        Key::new(1),
        Arc::new(LocalFirst),
        options,
    )
    .expect("client pool")
}

#[path = "fleet/cancellation.rs"]
mod cancellation;

fn pool(manager: Manager) -> FleetClientPool<Manager> {
    pool_with_options(manager, options())
}

#[tokio::test]
async fn returned_local_client_is_published_and_reused() {
    let creates = Arc::new(AtomicUsize::new(0));
    let pool = pool(Manager(Arc::clone(&creates)));

    let FleetClient::Local(first) = pool
        .acquire(b"", ActiveRequestPolicy::Preserve)
        .await
        .expect("first client")
    else {
        panic!("single-node fleet must select its local client");
    };
    assert_eq!(*first, 0);
    drop(first);

    let FleetClient::Local(second) = pool
        .acquire(b"", ActiveRequestPolicy::Preserve)
        .await
        .expect("reused client")
    else {
        panic!("single-node fleet must reuse its local client");
    };
    assert_eq!(*second, 0);
    assert_eq!(creates.load(Ordering::Relaxed), 1);
}

#[tokio::test]
async fn fleet_concurrency_limits_requests_independently_of_live_clients() {
    let pool = pool_with_options(
        Manager(Arc::new(AtomicUsize::new(0))),
        FleetPoolOptions::new(0, 2, 2, 1, 0, 1),
    );
    let first = pool
        .acquire(b"", ActiveRequestPolicy::Preserve)
        .await
        .expect("first request");

    assert!(matches!(
        pool.acquire(b"", ActiveRequestPolicy::Preserve).await,
        Err(FleetAcquireError::QueueFull { max_waiting: 0 })
    ));

    drop(first);
    assert!(pool.acquire(b"", ActiveRequestPolicy::Preserve).await.is_ok());
}

#[tokio::test]
async fn warm_floor_uses_the_fleet_claim_once() {
    let creates = Arc::new(AtomicUsize::new(0));
    let client_pool = pool_with_options(
        Manager(Arc::clone(&creates)),
        FleetPoolOptions::new(1, 1, 1, 1, 0, 1),
    );

    let cancellation = tokio_util::sync::CancellationToken::new();
    assert!(
        client_pool.warm_one(&cancellation).await.expect("first warm claim")
    );
    assert!(
        !client_pool
            .warm_one(&cancellation)
            .await
            .expect("satisfied warm floor")
    );
    assert_eq!(creates.load(Ordering::Relaxed), 1);

    let FleetClient::Local(client) = client_pool
        .acquire(b"", ActiveRequestPolicy::Preserve)
        .await
        .expect("warm client")
    else {
        panic!("single-node fleet must acquire its warm local client");
    };
    assert_eq!(*client, 0);
}

#[tokio::test]
async fn fleet_acquisition_observes_caller_cancellation_while_paused() {
    let pool = pool_with_options(
        Manager(Arc::new(AtomicUsize::new(0))),
        FleetPoolOptions::new(0, 1, 1, 1, 1, 1),
    );
    pool.apply_local(PoolDirective::pause("profile unavailable"));
    let cancellation = tokio_util::sync::CancellationToken::new();
    cancellation.cancel();

    assert!(matches!(
        pool.acquire_cancellable(
            b"",
            ActiveRequestPolicy::Preserve,
            &cancellation
        )
        .await,
        Err(FleetAcquireError::Cancelled)
    ));
}