122 lines
3.6 KiB
Rust
122 lines
3.6 KiB
Rust
use std::sync::Arc;
|
|
|
|
use tokio::sync::{oneshot, watch};
|
|
|
|
use crate::bus::CommittedTurnDelta;
|
|
use crate::channels::{ChannelManager, TurnTarget};
|
|
use crate::delivery::{DeliveryCoordinator, DeliveryError};
|
|
use crate::session::TurnSnapshot;
|
|
use crate::task_supervisor::TaskSupervisor;
|
|
|
|
use super::coordinator::SinkRoute;
|
|
|
|
/// Gateway-owned facade that resolves a target Channel and starts exactly one
|
|
/// TurnSink lifecycle. Session workers depend on this abstraction rather than
|
|
/// on Channel implementations or WebSocket/Feishu protocols.
|
|
#[derive(Clone)]
|
|
pub struct TurnDeliveryService {
|
|
coordinator: DeliveryCoordinator,
|
|
channels: ChannelManager,
|
|
supervisor: TaskSupervisor,
|
|
}
|
|
|
|
/// Completion handle for one TurnSink lifecycle.
|
|
///
|
|
/// Creating a sink only proves that delivery started. Callers consume this
|
|
/// handle after publishing a terminal snapshot to learn whether the terminal
|
|
/// write actually reached the Channel.
|
|
pub struct TurnDeliveryHandle {
|
|
pub(crate) completion: oneshot::Receiver<Result<(), DeliveryError>>,
|
|
}
|
|
|
|
impl TurnDeliveryHandle {
|
|
pub async fn wait(self) -> Result<(), DeliveryError> {
|
|
self.completion
|
|
.await
|
|
.unwrap_or(Err(DeliveryError::CompletionLost))
|
|
}
|
|
}
|
|
|
|
impl TurnDeliveryService {
|
|
pub fn new(
|
|
coordinator: DeliveryCoordinator,
|
|
channels: ChannelManager,
|
|
supervisor: TaskSupervisor,
|
|
) -> Self {
|
|
Self {
|
|
coordinator,
|
|
channels,
|
|
supervisor,
|
|
}
|
|
}
|
|
|
|
pub async fn start(
|
|
&self,
|
|
target: TurnTarget,
|
|
snapshots: watch::Receiver<Arc<TurnSnapshot>>,
|
|
) -> Result<TurnDeliveryHandle, DeliveryError> {
|
|
let channel = self
|
|
.channels
|
|
.get_channel(&target.channel)
|
|
.await
|
|
.ok_or_else(|| DeliveryError::ChannelNotFound(target.channel.clone()))?;
|
|
let live_policy = channel.live_policy();
|
|
let presentation = channel.presentation_policy();
|
|
let sink = channel
|
|
.open_turn(target.clone())
|
|
.await
|
|
.map_err(DeliveryError::OpenFailed)?;
|
|
let completion = self.coordinator.spawn_sink(
|
|
&self.supervisor,
|
|
SinkRoute {
|
|
channel: target.channel,
|
|
chat_id: target.chat_id,
|
|
live_policy,
|
|
presentation,
|
|
},
|
|
snapshots,
|
|
sink,
|
|
)?;
|
|
Ok(TurnDeliveryHandle { completion })
|
|
}
|
|
|
|
pub async fn commit(
|
|
&self,
|
|
target: &TurnTarget,
|
|
delta: CommittedTurnDelta,
|
|
) -> Result<(), DeliveryError> {
|
|
let channel = self
|
|
.channels
|
|
.get_channel(&target.channel)
|
|
.await
|
|
.ok_or_else(|| DeliveryError::ChannelNotFound(target.channel.clone()))?;
|
|
channel
|
|
.commit_turn(target, delta)
|
|
.await
|
|
.map_err(DeliveryError::FinalFailed)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn handle_reports_terminal_delivery_failure() {
|
|
let (sender, completion) = oneshot::channel();
|
|
sender.send(Err(DeliveryError::FinalTimedOut)).unwrap();
|
|
|
|
let result = TurnDeliveryHandle { completion }.wait().await;
|
|
assert!(matches!(result, Err(DeliveryError::FinalTimedOut)));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn handle_reports_lost_delivery_task() {
|
|
let (sender, completion) = oneshot::channel();
|
|
drop(sender);
|
|
|
|
let result = TurnDeliveryHandle { completion }.wait().await;
|
|
assert!(matches!(result, Err(DeliveryError::CompletionLost)));
|
|
}
|
|
}
|