durable-actors 0.4.0

Standalone regional durable-actors control plane, host, and durability runtime
Documentation
use crate::{
    clock::{Clock, SystemClock},
    grpc::{
        proto::{Empty, replica_service_client::ReplicaServiceClient},
        transport::{MAX_STORAGE_MESSAGE_BYTES, request},
    },
    replication::{
        ReplicaAccess, ReplicaGrant, ReplicaStream, ReplicaTarget, SessionHead, StreamHead,
    },
    state_transport::{GrpcStateTransport, StateTransport},
};
use anyhow::Result;
use async_trait::async_trait;

#[async_trait]
pub trait ReplicaPeers: Send + Sync {
    async fn initialize(&self, peer: &ReplicaTarget, session: &str) -> Result<()>;
    async fn head(&self, peer: &ReplicaTarget, stream: &ReplicaStream) -> Result<StreamHead>;
    async fn seal(&self, peer: &ReplicaTarget, session: &str) -> Result<SessionHead>;
    async fn read(&self, peer: &ReplicaTarget, object: &str) -> Result<Vec<u8>>;
}

pub struct GrpcReplicaPeers {
    access: ReplicaAccess,
    transport: GrpcStateTransport,
}

impl GrpcReplicaPeers {
    pub fn new(access: ReplicaAccess) -> Result<Self> {
        Ok(Self::with_transport(access, GrpcStateTransport::new()))
    }

    pub(crate) fn with_transport(access: ReplicaAccess, transport: GrpcStateTransport) -> Self {
        Self { access, transport }
    }

    async fn client(
        &self,
        peer: &ReplicaTarget,
        grant: ReplicaGrant,
    ) -> Result<(
        ReplicaServiceClient<tonic::transport::Channel>,
        tonic::Request<Empty>,
    )> {
        let address = self.access.url(
            &peer.url,
            &ReplicaGrant {
                host_id: peer.host_id.clone(),
                ..grant
            },
        )?;
        let (channel, token) = self.transport.capability(&address).await?;
        Ok((
            ReplicaServiceClient::new(channel)
                .max_decoding_message_size(MAX_STORAGE_MESSAGE_BYTES)
                .max_encoding_message_size(MAX_STORAGE_MESSAGE_BYTES),
            request(Empty {}, &token)?,
        ))
    }
}

#[async_trait]
impl ReplicaPeers for GrpcReplicaPeers {
    async fn initialize(&self, peer: &ReplicaTarget, session: &str) -> Result<()> {
        let (mut client, request) = self
            .client(
                peer,
                grant("INITIALIZE_SESSION", &peer.region, session, 60_000)?,
            )
            .await?;
        client.initialize(request).await?;
        Ok(())
    }

    async fn head(&self, peer: &ReplicaTarget, stream: &ReplicaStream) -> Result<StreamHead> {
        let (mut client, request) = self
            .client(
                peer,
                ReplicaGrant {
                    stream: Some(stream.clone()),
                    ..grant("HEAD", &peer.region, &stream.prefix, 60_000)?
                },
            )
            .await?;
        client.head(request).await?.into_inner().try_into()
    }

    async fn seal(&self, peer: &ReplicaTarget, session: &str) -> Result<SessionHead> {
        let (mut client, request) = self
            .client(peer, grant("SEAL_SESSION", &peer.region, session, 60_000)?)
            .await?;
        client.seal(request).await?.into_inner().try_into()
    }

    async fn read(&self, peer: &ReplicaTarget, object: &str) -> Result<Vec<u8>> {
        let address = self.access.url(
            &peer.url,
            &ReplicaGrant {
                host_id: peer.host_id.clone(),
                ..grant("GET", &peer.region, object, 60_000)?
            },
        )?;
        Ok(self.transport.read(&address).await?.to_vec())
    }
}

pub(super) fn grant(
    operation: &str,
    region: &str,
    object: &str,
    duration_ms: u64,
) -> Result<ReplicaGrant> {
    Ok(ReplicaGrant {
        operation: operation.into(),
        region: region.into(),
        object: object.into(),
        host_id: String::new(),
        expires_at_ms: SystemClock.now_ms()?.saturating_add(duration_ms),
        stream: None,
    })
}