mod channel;
mod http;
mod lines;
mod sse;
mod timer;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Condvar, Mutex, OnceLock, Weak};
use std::time::{Duration, Instant};
use bytes::Bytes;
use super::fabric::{Fabric, Inner, Outcome};
use super::neuron::Neuron;
use super::signal::{Feature, NeuronId, Payload, Signal, Urgency};
pub use channel::{ChannelIn, ChannelSender};
pub use http::{DEADLINE_HEADER, Http};
pub use lines::Lines;
pub use sse::SseIn;
pub use timer::Timer;
pub mod kinds {
pub const BYTES: u16 = 1;
pub const TEXT: u16 = 2;
pub const TIMER: u16 = 3;
pub const SSE: u16 = 4;
}
#[derive(Default)]
pub(crate) struct Stop {
raised: AtomicBool,
lock: Mutex<()>,
wake: Condvar,
notify: tokio::sync::Notify,
}
impl Stop {
pub(crate) fn raise(&self) {
self.raised.store(true, Ordering::Release);
drop(self.lock.lock().unwrap_or_else(|e| e.into_inner()));
self.wake.notify_all();
self.notify.notify_waiters();
}
fn is_raised(&self) -> bool {
self.raised.load(Ordering::Acquire)
}
}
#[derive(Clone)]
pub struct Afferent {
inner: Weak<Inner>,
id: NeuronId,
stop: Arc<Stop>,
}
impl Afferent {
pub(crate) fn new(inner: Weak<Inner>, id: NeuronId, stop: Arc<Stop>) -> Self {
Self { inner, id, stop }
}
pub fn is_stopped(&self) -> bool {
self.stop.is_raised()
}
pub fn sleep(&self, duration: Duration) -> bool {
let deadline = Instant::now() + duration;
let mut guard = self.stop.lock.lock().unwrap_or_else(|e| e.into_inner());
while !self.stop.is_raised() {
let Some(left) = deadline.checked_duration_since(Instant::now()) else {
return true;
};
guard = self
.stop
.wake
.wait_timeout(guard, left)
.unwrap_or_else(|e| e.into_inner())
.0;
}
false
}
pub async fn stopped(&self) {
loop {
let notified = self.stop.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
if self.stop.is_raised() {
return;
}
notified.await;
}
}
pub fn id(&self) -> NeuronId {
self.id
}
pub fn fabric(&self) -> Option<Fabric> {
self.inner.upgrade().map(Fabric)
}
pub fn emit(&self, signal: Signal) -> Option<Outcome> {
let fabric = self.fabric()?;
Some(fabric.stimulate(self.id, signal))
}
pub fn runtime(&self) -> Option<tokio::runtime::Handle> {
self.fabric().map(|f| f.afferent().clone())
}
}
pub trait Receptor: Neuron {
fn start(&self, _afferent: Afferent) {}
fn mount(&self) -> Option<HttpMount> {
None
}
fn ingest(&self, _request: Ingest<'_>) -> Option<Signal> {
None
}
fn respond_within(&self) -> Duration {
Duration::ZERO
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HttpMount {
pub path: String,
pub method: HttpMethod,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Post,
}
pub struct Ingest<'a> {
pub body: Bytes,
pub content_type: Option<&'a str>,
pub headers: &'a axum::http::HeaderMap,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct UrgencyRule {
pub deadline: Option<Duration>,
pub intervals: Option<f32>,
}
impl UrgencyRule {
pub const NONE: Self = Self {
deadline: None,
intervals: None,
};
pub fn within(deadline: Duration) -> Self {
Self {
deadline: Some(deadline),
intervals: None,
}
}
pub fn before_next(intervals: f32) -> Self {
Self {
deadline: None,
intervals: Some(intervals),
}
}
fn stamp(&self, rate: f32, now: Instant) -> Urgency {
let from_rate = self
.intervals
.filter(|_| rate > 0.0)
.map(|k| Duration::from_secs_f32((k / rate).clamp(1e-6, 3600.0)));
Urgency {
deadline: from_rate.or(self.deadline).map(|d| now + d),
rate,
}
}
}
pub(crate) struct RateMeter {
last: AtomicU64,
interval: AtomicU64,
epoch: Instant,
}
impl Default for RateMeter {
fn default() -> Self {
Self::new()
}
}
impl RateMeter {
pub fn new() -> Self {
Self {
last: AtomicU64::new(0),
interval: AtomicU64::new(0f64.to_bits()),
epoch: Instant::now(),
}
}
pub fn tick(&self, now: Instant) -> f32 {
let nanos = super::nanos_since(self.epoch, now).max(1);
let last = self.last.swap(nanos, Ordering::Relaxed);
if last == 0 {
return 0.0;
}
let dt = nanos.saturating_sub(last) as f64;
let previous = f64::from_bits(self.interval.load(Ordering::Relaxed));
let interval = if previous == 0.0 {
dt
} else {
previous * 0.8 + dt * 0.2
};
self.interval.store(interval.to_bits(), Ordering::Relaxed);
if interval > 0.0 {
(1e9 / interval) as f32
} else {
0.0
}
}
pub fn rate(&self) -> f32 {
let interval = f64::from_bits(self.interval.load(Ordering::Relaxed));
if interval > 0.0 {
(1e9 / interval) as f32
} else {
0.0
}
}
}
struct Backoff {
initial: Duration,
next: Duration,
}
impl Backoff {
fn new(initial: Duration) -> Self {
Self {
initial,
next: initial,
}
}
fn reset(&mut self) {
self.next = self.initial;
}
fn next(&mut self) -> Duration {
let wait = self.next;
self.next = (wait * 2).min(Duration::from_secs(10));
wait
}
}
struct Source {
name: String,
rule: UrgencyRule,
meter: RateMeter,
afferent: OnceLock<Afferent>,
}
impl Source {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
rule: UrgencyRule::NONE,
meter: RateMeter::new(),
afferent: OnceLock::new(),
}
}
fn signal(&self, feature: Feature, payload: Payload) -> Signal {
let now = Instant::now();
let rate = self.meter.tick(now);
let mut signal = Signal::new(feature, payload);
signal.at = now;
signal.urgency = self.rule.stamp(rate, now);
signal
}
fn emit(&self, signal: Signal) -> Option<Outcome> {
self.afferent.get()?.emit(signal)
}
fn describe(&self, extra: serde_json::Value) -> serde_json::Value {
let mut value = serde_json::json!({
"rate_per_s": self.meter.rate(),
"deadline_ms": self.rule.deadline.map(|d| d.as_secs_f64() * 1e3),
"deadline_intervals": self.rule.intervals,
});
if let (Some(a), Some(b)) = (value.as_object_mut(), extra.as_object()) {
a.extend(b.clone());
}
value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_stop_signal_wakes_a_sleeping_receptor() {
let stop = Arc::new(Stop::default());
let afferent = Afferent::new(Weak::new(), NeuronId(0), stop.clone());
assert!(afferent.sleep(Duration::from_millis(1)));
let sleeper = std::thread::spawn({
let afferent = afferent.clone();
move || afferent.sleep(Duration::from_secs(3600))
});
stop.raise();
assert!(!sleeper.join().unwrap());
assert!(afferent.is_stopped());
assert!(!afferent.sleep(Duration::from_secs(3600)));
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(afferent.stopped());
}
}