use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use std::task::{Context, Poll, Waker};
use crate::error::{Error, ErrorKind};
#[derive(Debug)]
pub(crate) struct Notify {
generation: AtomicU64,
waiting: Mutex<Vec<Waker>>,
}
impl Notify {
pub(crate) const fn new() -> Self {
Self {
generation: AtomicU64::new(0),
waiting: Mutex::new(Vec::new()),
}
}
pub(crate) fn generation(&self) -> u64 {
self.generation.load(Ordering::Acquire)
}
pub(crate) fn bump(&self) {
self.generation.fetch_add(1, Ordering::Release);
let woken = {
let mut waiting = self.lock();
std::mem::take(&mut *waiting)
};
for waker in woken {
waker.wake();
}
}
fn register(&self, waker: &Waker) {
let mut waiting = self.lock();
if waiting.iter().any(|existing| existing.will_wake(waker)) {
return;
}
waiting.push(waker.clone());
}
fn lock(&self) -> std::sync::MutexGuard<'_, Vec<Waker>> {
self.waiting
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
pub struct Changes<T: Send + Sync + 'static> {
cell: &'static crate::ConfigCell<T>,
seen: u64,
}
impl<T: Send + Sync + 'static> Changes<T> {
pub(crate) fn new(cell: &'static crate::ConfigCell<T>) -> Self {
Self {
seen: cell.notify().generation(),
cell,
}
}
pub fn changed(&mut self) -> impl Future<Output = Arc<T>> + '_ {
Changed { changes: self }
}
#[must_use]
pub fn seen(&self) -> u64 {
self.seen
}
}
impl<T: Send + Sync + 'static> std::fmt::Debug for Changes<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Changes")
.field("seen", &self.seen)
.finish_non_exhaustive()
}
}
struct Changed<'a, T: Send + Sync + 'static> {
changes: &'a mut Changes<T>,
}
impl<T: Send + Sync + 'static> Future for Changed<'_, T> {
type Output = Arc<T>;
fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Arc<T>> {
let changes = &mut self.get_mut().changes;
let notify = changes.cell.notify();
if let Some(value) = take(changes, notify) {
return Poll::Ready(value);
}
notify.register(context.waker());
match take(changes, notify) {
Some(value) => Poll::Ready(value),
None => Poll::Pending,
}
}
}
fn take<T: Send + Sync + 'static>(changes: &mut Changes<T>, notify: &Notify) -> Option<Arc<T>> {
let current = notify.generation();
if current == changes.seen {
return None;
}
changes.seen = current;
changes.cell.load()
}
pub trait BlockingExecutor: Send + Sync + 'static {
fn execute(&self, work: Box<dyn FnOnce() + Send + 'static>);
}
static EXECUTOR: OnceLock<Box<dyn BlockingExecutor>> = OnceLock::new();
pub fn set_blocking_executor(
executor: impl BlockingExecutor,
) -> Result<(), Box<dyn BlockingExecutor>> {
match EXECUTOR.set(Box::new(executor)) {
Ok(()) => Ok(()),
Err(rejected) => Err(rejected),
}
}
fn dispatch(work: Box<dyn FnOnce() + Send + 'static>) {
if let Some(executor) = EXECUTOR.get() {
executor.execute(work);
return;
}
#[cfg(feature = "tokio")]
if let Ok(handle) = tokio::runtime::Handle::try_current() {
handle.spawn_blocking(work);
return;
}
if let Err(error) = std::thread::Builder::new()
.name("dynamic-config-load".to_owned())
.spawn(work)
{
crate::log::warning!("could not spawn a thread to load configuration: {error}");
}
}
pub async fn off_thread<T, F>(work: F) -> Result<T, Error>
where
F: FnOnce() -> Result<T, Error> + Send + 'static,
T: Send + 'static,
{
let slot = Arc::new(Slot::<Result<T, Error>>::default());
let guard = Guard {
slot: Some(Arc::clone(&slot)),
};
dispatch(Box::new(move || {
let mut guard = guard;
let outcome = work();
guard.disarm().fill(outcome);
}));
Awaiting { slot }.await
}
struct Slot<T> {
value: Mutex<Option<T>>,
waker: Mutex<Option<Waker>>,
}
impl<T> Default for Slot<T> {
fn default() -> Self {
Self {
value: Mutex::new(None),
waker: Mutex::new(None),
}
}
}
impl<T> Slot<T> {
fn fill(&self, value: T) {
*self
.value
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value);
let waker = self
.waker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
if let Some(waker) = waker {
waker.wake();
}
}
fn take(&self) -> Option<T> {
self.value
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take()
}
}
struct Guard<T> {
slot: Option<Arc<Slot<Result<T, Error>>>>,
}
impl<T> Guard<T> {
fn disarm(&mut self) -> Arc<Slot<Result<T, Error>>> {
self.slot
.take()
.expect("a guard is disarmed at most once, right before filling")
}
}
impl<T> Drop for Guard<T> {
fn drop(&mut self) {
if let Some(slot) = self.slot.take() {
slot.fill(Err(Error::new(
ErrorKind::Backend,
"the configuration load did not finish; the task panicked or was cancelled",
)));
}
}
}
struct Awaiting<T> {
slot: Arc<Slot<T>>,
}
impl<T> Future for Awaiting<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<T> {
if let Some(value) = self.slot.take() {
return Poll::Ready(value);
}
*self
.slot
.waker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(context.waker().clone());
match self.slot.take() {
Some(value) => Poll::Ready(value),
None => Poll::Pending,
}
}
}