use std::sync::Arc;
use std::time::Duration;
use bytes::Bytes;
use crate::{TranscodeError, Transcoder};
use tokio::sync::watch;
use tonic::transport::Channel;
use tonic::Status;
use crate::reflect;
const FIRST_LOAD_WAIT: Duration = Duration::from_secs(10);
const LOAD_TIMEOUT: Duration = Duration::from_secs(30);
const RETRY_INTERVAL: Duration = Duration::from_secs(30);
#[derive(Clone, Default)]
pub enum SchemaSource {
#[default]
None,
Reflection,
Bundled(Bytes),
ReflectionOrBundled(Bytes),
}
#[derive(Clone)]
pub struct Schema {
inner: Inner,
}
#[derive(Clone)]
enum Inner {
None,
Bundled(Arc<Transcoder>),
Reflection(Arc<ReflectionState>),
ReflectionOrBundled { reflection: Arc<ReflectionState>, bundle: Arc<Transcoder> },
}
struct ReflectionState {
channel: Channel,
ttl: Duration,
tx: watch::Sender<Option<Arc<Transcoder>>>,
rx: watch::Receiver<Option<Arc<Transcoder>>>,
}
impl ReflectionState {
async fn load_and_swap(&self) -> Result<(), Status> {
let tc = match tokio::time::timeout(LOAD_TIMEOUT, reflect::load_all(&self.channel)).await {
Ok(result) => result?,
Err(_) => return Err(Status::deadline_exceeded("upstream reflection load timed out")),
};
let _ = self.tx.send(Some(Arc::new(tc)));
Ok(())
}
fn current(&self) -> Option<Arc<Transcoder>> {
self.rx.borrow().clone()
}
async fn transcoder(&self) -> Result<Arc<Transcoder>, Status> {
let mut rx = self.rx.clone();
loop {
if let Some(tc) = rx.borrow_and_update().clone() {
return Ok(tc);
}
if tokio::time::timeout(FIRST_LOAD_WAIT, rx.changed()).await.is_err() {
return Err(Status::unavailable("descriptors not yet loaded from upstream reflection"));
}
}
}
}
async fn refresh_loop(state: Arc<ReflectionState>) {
loop {
let wait = match state.load_and_swap().await {
Ok(()) => {
tracing::info!("proxy: loaded +json descriptors from upstream reflection");
state.ttl
}
Err(e) => {
tracing::warn!("proxy: reflection load failed ({e}); will retry");
RETRY_INTERVAL.min(state.ttl)
}
};
tokio::time::sleep(wait).await;
}
}
impl Schema {
pub fn from_transcoder(transcoder: Option<Arc<Transcoder>>) -> Self {
Self { inner: transcoder.map(Inner::Bundled).unwrap_or(Inner::None) }
}
pub fn build(source: SchemaSource, channel: Channel, ttl: Duration) -> Result<Self, TranscodeError> {
let inner = match source {
SchemaSource::None => Inner::None,
SchemaSource::Bundled(bytes) => {
Inner::Bundled(Arc::new(Transcoder::from_file_descriptor_set(&bytes)?))
}
SchemaSource::Reflection => {
let (tx, rx) = watch::channel(None);
Inner::Reflection(Arc::new(ReflectionState { channel, ttl, tx, rx }))
}
SchemaSource::ReflectionOrBundled(bytes) => {
let bundle = Arc::new(Transcoder::from_file_descriptor_set(&bytes)?);
let (tx, rx) = watch::channel(None);
Inner::ReflectionOrBundled {
reflection: Arc::new(ReflectionState { channel, ttl, tx, rx }),
bundle,
}
}
};
Ok(Self { inner })
}
pub fn start(&self) {
let state = match &self.inner {
Inner::Reflection(state) => state,
Inner::ReflectionOrBundled { reflection, .. } => reflection,
_ => return,
};
tokio::spawn(refresh_loop(state.clone()));
}
pub async fn reload(&self) -> Result<(), Status> {
match &self.inner {
Inner::Reflection(state) | Inner::ReflectionOrBundled { reflection: state, .. } => {
state.load_and_swap().await
}
Inner::None => Err(Status::failed_precondition("no descriptor source to reload")),
Inner::Bundled(_) => {
Err(Status::failed_precondition("bundled descriptors are static; nothing to reload"))
}
}
}
pub fn enabled(&self) -> bool {
!matches!(self.inner, Inner::None)
}
pub async fn transcoder_any(&self) -> Result<Arc<Transcoder>, Status> {
match &self.inner {
Inner::None => Err(Status::unimplemented(
"no +json transcoder configured (pass a transcoder in-process, or enable upstream reflection / bundle a descriptor set on the proxy)",
)),
Inner::Bundled(tc) => Ok(tc.clone()),
Inner::Reflection(state) => state.transcoder().await,
Inner::ReflectionOrBundled { reflection, bundle } => {
Ok(reflection.current().unwrap_or_else(|| bundle.clone()))
}
}
}
pub async fn transcoder(&self, method_path: &str) -> Result<Arc<Transcoder>, Status> {
let tc = self.transcoder_any().await?;
if tc.has_method(method_path) {
Ok(tc)
} else {
Err(Status::unimplemented(format!("no descriptor for method {method_path}")))
}
}
}