use event_listener::{Event, Listener};
use http::HeaderMap;
use std::{
sync::{Arc, OnceLock},
time::Duration,
};
#[derive(Clone, Debug)]
pub struct Trailer {
shared: Arc<Shared>,
}
#[derive(Debug)]
struct Shared {
headers: OnceLock<HeaderMap>,
ready: Event,
}
impl Trailer {
pub(crate) fn empty() -> &'static Self {
static EMPTY: OnceLock<Trailer> = OnceLock::new();
EMPTY.get_or_init(|| Self {
shared: Arc::new(Shared {
headers: OnceLock::from(HeaderMap::new()),
ready: Event::new(),
}),
})
}
#[inline]
pub fn is_ready(&self) -> bool {
self.try_get().is_some()
}
#[inline]
pub fn try_get(&self) -> Option<&HeaderMap> {
self.shared.headers.get()
}
pub fn wait(&self) -> &HeaderMap {
loop {
if let Some(headers) = self.try_get() {
return headers;
}
let listener = self.shared.ready.listen();
if let Some(headers) = self.try_get() {
return headers;
}
listener.wait();
if let Some(headers) = self.try_get() {
return headers;
}
}
}
pub fn wait_timeout(&self, timeout: Duration) -> Option<&HeaderMap> {
if let Some(headers) = self.try_get() {
return Some(headers);
}
let listener = self.shared.ready.listen();
if let Some(headers) = self.try_get() {
return Some(headers);
}
if listener.wait_timeout(timeout).is_some() {
self.try_get()
} else {
None
}
}
pub async fn wait_async(&self) -> &HeaderMap {
loop {
if let Some(headers) = self.try_get() {
return headers;
}
let listener = self.shared.ready.listen();
if let Some(headers) = self.try_get() {
return headers;
}
listener.await;
if let Some(headers) = self.try_get() {
return headers;
}
}
}
}
pub(crate) struct TrailerWriter {
shared: Arc<Shared>,
headers: Option<HeaderMap>,
}
impl TrailerWriter {
pub(crate) fn new() -> Self {
Self {
shared: Arc::new(Shared {
headers: Default::default(),
ready: Event::new(),
}),
headers: Some(HeaderMap::new()),
}
}
pub(crate) fn trailer(&self) -> Trailer {
Trailer {
shared: self.shared.clone(),
}
}
pub(crate) fn get_mut(&mut self) -> Option<&mut HeaderMap> {
self.headers.as_mut()
}
#[inline]
pub(crate) fn flush(&mut self) {
if !self.flush_impl() {
tracing::warn!("tried to flush trailer multiple times");
}
}
fn flush_impl(&mut self) -> bool {
if let Some(headers) = self.headers.take() {
let _ = self.shared.headers.set(headers);
self.shared.ready.notify(usize::max_value());
true
} else {
false
}
}
}
impl Drop for TrailerWriter {
fn drop(&mut self) {
self.flush_impl();
}
}