use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::pin::Pin;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, Weak};
use std::task::{Context, Poll, Wake, Waker};
use futures_util::FutureExt;
use tokio::sync::watch;
use super::{Notice, Supervised};
use crate::operations::resident::Lease;
use crate::operations::{OperationManager, OperationPolicy, RestartPolicy};
use crate::services::{Fault, Once, Resident, Service, ServiceDefinition};
use crate::{Application, Result};
use harmos_sidecar::Attach;
static NEXT_CONNECTION: AtomicU64 = AtomicU64::new(1);
type Outcome = std::result::Result<(), Fault<String>>;
type Running = Pin<Box<dyn Future<Output = Outcome> + Send>>;
type Ending = Pin<Box<dyn Future<Output = std::result::Result<(), String>> + Send>>;
type Owner<A, S, H> = std::marker::PhantomData<fn() -> (A, S, H)>;
pub(super) struct Physical<A: Application, S, H> {
original: Arc<Mutex<Original>>,
marker: Owner<A, S, H>,
}
pub(super) struct Polling<A: Application, S, H> {
original: Weak<Mutex<Original>>,
marker: Owner<A, S, H>,
}
struct Original {
signals: Arc<Signals>,
lease: Option<Lease>,
end: watch::Sender<bool>,
running_busy: bool,
ending_busy: bool,
cancelled: bool,
running: Option<Running>,
ending: Option<Ending>,
settled: bool,
ending_failure: Option<String>,
outcome: Option<Outcome>,
}
#[derive(Default)]
struct Signals {
revision: AtomicU64,
waiting: Mutex<Vec<Waker>>,
}
struct Connection<A: Application, S, H> {
supervised: Supervised<A, S, H>,
outcome: watch::Sender<Option<Outcome>>,
end: watch::Receiver<bool>,
}
impl Signals {
fn register(&self, waker: &Waker) {
let mut waiting = self.waiting.lock().expect("physical wake registry is healthy");
if !waiting.iter().any(|current| current.will_wake(waker)) {
waiting.push(waker.clone());
}
}
fn notify(&self) {
self.revision.fetch_add(1, Ordering::AcqRel);
let waiting = std::mem::take(&mut *self.waiting.lock().expect("physical wake registry is healthy"));
for waker in waiting {
waker.wake();
}
}
}
impl Wake for Signals {
fn wake(self: Arc<Self>) {
self.notify();
}
fn wake_by_ref(self: &Arc<Self>) {
self.notify();
}
}
impl<A: Application, S, H> Clone for Polling<A, S, H> {
fn clone(&self) -> Self {
Self { original: self.original.clone(), marker: std::marker::PhantomData }
}
}
impl<A, S, H> Resident<A> for Connection<A, S, H>
where
A: Application,
S: Attach,
H: Fn(&Service<A>, Notice) + Send + Sync + 'static,
{
type Error = String;
async fn run(&mut self, context: Service<A>) -> Outcome {
let mut stopping = context.shared_attempt();
let mut end = self.end.clone();
let running = AssertUnwindSafe(self.supervised.run(context)).catch_unwind();
let ended = tokio::select! {
ended = running => Some(ended.unwrap_or_else(|_| {
Err(Fault::Application("native physical attempt panicked".into()))
})),
_ = end.wait_for(|ended| *ended) => None,
() = stopping.stopping() => None,
};
if let Some(outcome) = ended {
self.outcome.send_replace(Some(outcome));
tokio::select! {
_ = end.wait_for(|ended| *ended) => {}
() = stopping.stopping() => {}
}
}
Ok(())
}
async fn stop(&mut self) -> std::result::Result<(), String> {
self.supervised.stop().await
}
async fn cleanup(&mut self) -> std::result::Result<(), String> {
self.supervised.cleanup().await
}
}
impl<A, S, H> Physical<A, S, H>
where
A: Application,
S: Attach,
H: Fn(&Service<A>, Notice) + Send + Sync + 'static,
{
pub(super) fn new(resident: Supervised<A, S, H>, operations: &OperationManager) -> Result<Self> {
let number = NEXT_CONNECTION.fetch_add(1, Ordering::Relaxed);
let definition = ServiceDefinition {
id: format!("harmos.sidecar.{}.{number}", resident.attach.owner()),
policy: OperationPolicy { restart: RestartPolicy::Never, ..crate::services::POLICY },
};
let (outcome, mut finished) = watch::channel(None);
let (end, ending) = watch::channel(false);
let connection = Connection { supervised: resident, outcome, end: ending };
let lease = operations.start_service::<A>(Once::new(definition, connection), true, false)?;
let running: Running = Box::pin(async move {
match finished.wait_for(Option::is_some).await {
Ok(outcome) => outcome.clone().expect("a finished connection has an outcome"),
Err(_) => Err(Fault::Application("native physical attempt was interrupted".into())),
}
});
Ok(Self {
original: Arc::new(Mutex::new(Original {
signals: Arc::default(),
lease: Some(lease),
end,
running_busy: false,
ending_busy: false,
cancelled: false,
running: Some(running),
ending: None,
settled: false,
ending_failure: None,
outcome: None,
})),
marker: std::marker::PhantomData,
})
}
pub(super) fn polling(&self) -> Polling<A, S, H> {
Polling { original: Arc::downgrade(&self.original), marker: std::marker::PhantomData }
}
fn signals(&self, context: &Context<'_>) -> Arc<Signals> {
let signals = self.original.lock().unwrap_or_else(|error| error.into_inner()).signals.clone();
signals.register(context.waker());
signals
}
pub(super) fn poll(&mut self, context: &mut Context<'_>) -> Poll<Outcome> {
let signals = self.signals(context);
let mut running = {
let mut original = self.original.lock().unwrap_or_else(|error| error.into_inner());
if let Some(outcome) = &original.outcome {
return Poll::Ready(outcome.clone());
}
if original.running_busy {
return Poll::Pending;
}
let Some(running) = original.running.take() else {
return Poll::Ready(Err(Fault::Application("native physical attempt was interrupted".into())));
};
original.running_busy = true;
running
};
let revision = signals.revision.load(Ordering::Acquire);
let waker = Waker::from(signals.clone());
let mut polling = Context::from_waker(&waker);
let result = running.as_mut().poll(&mut polling);
let mut original = self.original.lock().unwrap_or_else(|error| error.into_inner());
original.running_busy = false;
match result {
Poll::Pending => {
if !original.cancelled {
original.running = Some(running);
}
drop(original);
if signals.revision.load(Ordering::Acquire) != revision {
signals.notify();
}
Poll::Pending
}
Poll::Ready(outcome) => {
original.outcome = Some(outcome.clone());
drop(original);
drop(running);
signals.notify();
Poll::Ready(outcome)
}
}
}
pub(super) fn ready(&self) {
let original = self.original.lock().unwrap_or_else(|error| error.into_inner());
if let Some(lease) = &original.lease {
lease.transition(crate::operations::OperationPhase::Running);
}
}
pub(super) fn is_running(&self) -> bool {
let original = self.original.lock().unwrap_or_else(|error| error.into_inner());
original.running.is_some() || original.running_busy
}
pub(super) fn has_outcome(&self) -> bool {
self.original.lock().unwrap_or_else(|error| error.into_inner()).outcome.is_some()
}
pub(super) fn cancel(&mut self) {
let signals = {
let mut original = self.original.lock().unwrap_or_else(|error| error.into_inner());
original.cancelled = true;
original.running.take();
original.end.send_replace(true);
original.signals.clone()
};
signals.notify();
}
pub(super) fn retry_ending(&mut self) {
self.original.lock().unwrap_or_else(|error| error.into_inner()).ending_failure.take();
}
pub(super) fn poll_ending(&mut self, context: &mut Context<'_>) -> Poll<std::result::Result<(), String>> {
let signals = self.signals(context);
let mut ending = {
let mut original = self.original.lock().unwrap_or_else(|error| error.into_inner());
if original.settled {
return Poll::Ready(Ok(()));
}
if let Some(failure) = &original.ending_failure {
return Poll::Ready(Err(failure.clone()));
}
if original.ending_busy || original.running_busy || original.running.is_some() {
return Poll::Pending;
}
original.ending_busy = true;
match original.ending.take() {
Some(ending) => ending,
None => {
original.end.send_replace(true);
let Some(lease) = original.lease.as_mut() else {
original.ending_busy = false;
original.settled = true;
return Poll::Ready(Ok(()));
};
let retiring = lease.retire();
Box::pin(async move { retiring.await.map_err(|error| error.to_string()) })
}
}
};
let revision = signals.revision.load(Ordering::Acquire);
let waker = Waker::from(signals.clone());
let mut polling = Context::from_waker(&waker);
let result = ending.as_mut().poll(&mut polling);
let mut original = self.original.lock().unwrap_or_else(|error| error.into_inner());
original.ending_busy = false;
match result {
Poll::Pending => {
original.ending = Some(ending);
drop(original);
if signals.revision.load(Ordering::Acquire) != revision {
signals.notify();
}
Poll::Pending
}
Poll::Ready(outcome) => {
match &outcome {
Ok(()) => {
original.settled = true;
original.lease.take();
}
Err(failure) => original.ending_failure = Some(failure.clone()),
}
drop(original);
drop(ending);
signals.notify();
Poll::Ready(outcome)
}
}
}
}
impl<A, S, H> Polling<A, S, H>
where
A: Application,
S: Attach,
H: Fn(&Service<A>, Notice) + Send + Sync + 'static,
{
pub(super) fn poll(&self, context: &mut Context<'_>) -> Poll<Outcome> {
let Some(original) = self.original.upgrade() else {
return Poll::Pending;
};
let mut physical = Physical::<A, S, H> { original, marker: std::marker::PhantomData };
if physical.is_running() { physical.poll(context) } else { Poll::Pending }
}
pub(super) fn retry_ending(&self) {
if let Some(original) = self.original.upgrade() {
Physical::<A, S, H> { original, marker: std::marker::PhantomData }.retry_ending();
}
}
pub(super) fn poll_ending(&self, context: &mut Context<'_>) -> Poll<std::result::Result<(), String>> {
let Some(original) = self.original.upgrade() else {
return Poll::Pending;
};
let mut physical = Physical::<A, S, H> { original, marker: std::marker::PhantomData };
if physical.is_running() || physical.original.lock().unwrap_or_else(|error| error.into_inner()).settled {
return Poll::Pending;
}
physical.poll_ending(context)
}
pub(super) fn same(&self, physical: &Physical<A, S, H>) -> bool {
self.original.ptr_eq(&Arc::downgrade(&physical.original))
}
}