use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::collections::hash_map::Entry;
use std::sync::{Arc, Mutex, Weak};
use std::task::{Context, Poll};
use std::collections::HashMap;
use std::ops::{Deref, RangeBounds};
use std::pin::{pin, Pin};
use std::time::Duration;
use std::borrow::Cow;
use std::mem;
use futures::stream::{BoxStream, Stream, StreamExt};
use futures::future::{self, Either};
use futures::executor::block_on;
use tracing::{debug, info, warn};
use crate::backend::{default_backend, Backend, BackendServer, BackendServerConfig};
use crate::database::{DatabaseProxy, ExportDevInfo};
use crate::dev::{AttrInfo, AttrRead, AttrReady, AttrValueEvent};
use crate::error::{Error, ErrorKind, Result, reason};
use crate::name::{self, ArcName, Name};
use crate::future::block_on_timeout;
use crate::testing::TestContext;
use crate::value::Value;
use crate::future::Slot;
use super::class::{AnyClass, Class};
use super::admin::{self, Admin};
use super::def::ClassDef;
use super::Model;
use super::dev::{AnyDev, Dev};
use super::polling::Poller;
use super::terminate;
#[cfg(feature = "cli")]
use super::cli::{self, ServerArgs};
const ADMIN_DEV_PREFIX: &str = "dserver/";
const DEFAULT_BOOT_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug)]
pub struct Server {
core: Weak<Core>,
pub(super) admin_dev_name: Arc<str>,
database: DatabaseProxy,
events: Mutex<HashMap<ArcName, HashMap<ArcName, AttrEvents>>>,
pub(super) local: bool,
}
#[derive(Debug)]
pub(super) struct Core {
server: Arc<Server>,
backend: Arc<dyn BackendServer>,
classes: Mutex<HashMap<ArcName, Arc<dyn AnyClass>>>,
class_count: AtomicUsize,
pub(super) devs: Mutex<HashMap<ArcName, Arc<dyn AnyDev>>>,
pub(super) pending: Mutex<Pending>,
sync_lock: async_lock::Mutex<()>,
stopped: AtomicBool,
poller: Poller,
}
impl Drop for Core {
fn drop(&mut self) {
self.backend.stop();
self.poller.shutdown();
let removed = &self.pending.get_mut().unwrap().removed;
for dev in self.devs.get_mut().unwrap().values().chain(removed) {
dev.withdraw_now();
}
}
}
#[derive(Debug, Default)]
struct AttrEvents {
values: HashMap<AttrValueEvent, Subscribers<AttrRead<Value>>>,
ready: Subscribers<AttrReady>,
conf: Subscribers<AttrInfo>,
}
type Subscribers<T> = Vec<Weak<Slot<Result<T>>>>;
#[derive(Debug, Default)]
pub struct Pending {
pub added: Vec<Arc<dyn AnyDev>>,
pub unexported: Vec<(Arc<dyn AnyDev>, Option<Arc<str>>)>,
pub removed: Vec<Arc<dyn AnyDev>>,
}
impl Server {
#[inline]
pub fn name(&self) -> &str {
&self.admin_dev_name[ADMIN_DEV_PREFIX.len()..]
}
#[inline]
pub fn admin_dev_name(&self) -> &str {
&self.admin_dev_name
}
#[inline]
pub fn database(&self) -> &DatabaseProxy {
&self.database
}
pub(super) fn core_checked(&self) -> Result<Arc<Core>> {
self.core.upgrade().ok_or_else(|| Error::new(ErrorKind::Reason(reason::API_ServerNotRunning))
.with_description(format!("Server {:?} is not served anymore", self.name())))
}
pub(super) fn poller(&self) -> Option<Poller> {
self.core.upgrade().map(|core| core.poller.clone())
}
pub(super) fn backend_checked(&self) -> Result<Arc<dyn BackendServer>> {
Ok(Arc::clone(&self.core_checked()?.backend))
}
#[inline]
pub fn backend(&self) -> Option<Arc<dyn BackendServer>> {
self.core.upgrade().map(|core| Arc::clone(&core.backend))
}
pub fn add_class<M: Model>(&self, def: ClassDef<M>) -> Result<Arc<Class<M>>> {
if !name::check_tango_class_name(&def.name) {
return Err(Error::new(ErrorKind::ClassNameInvalid)
.with_description(format!("Class {:?} has an invalid name, it should be made of \
letters, digits, '_' and '-'", def.name)));
}
let core = self.core_checked()?;
let def = Arc::new(def);
let mut classes = core.classes.lock().unwrap();
let name = ArcName::from(&*def.name);
let Entry::Vacant(entry) = classes.entry(name) else {
return Err(Error::new(ErrorKind::ClassAlreadyDefined)
.with_description(format!("Class {:?} is already defined on the server", def.name)));
};
let index = core.class_count.fetch_add(1, Ordering::Relaxed);
let class = Class::new(Arc::clone(&core.server), def, index);
entry.insert(Arc::clone(&class) as Arc<dyn AnyClass>);
Ok(class)
}
pub fn get_class<M: Model>(&self, name: &str) -> Option<Arc<Class<M>>> {
self.any_class(name)?.into_any().downcast().ok()
}
pub(super) fn any_class(&self, name: &str) -> Option<Arc<dyn AnyClass>> {
let core = self.core.upgrade()?;
core.classes.lock().unwrap().get(Name::new(name)).map(Arc::clone)
}
pub fn add_dev(&self, class: &str, name: &str) -> Result<()> {
let class = self.core_checked()?.classes.lock().unwrap()
.get(Name::new(class))
.map(Arc::clone)
.ok_or_else(|| Error::new(ErrorKind::ClassNotFound)
.with_description(format!("Class {class:?} is not defined on the server")))?;
class.add_dev(name).map(drop)
}
pub fn remove_dev(&self, name: &str) -> bool {
let Some(core) = self.core.upgrade() else {
return false;
};
let Some(dev) = core.devs.lock().unwrap().remove(Name::new(name)) else {
return false;
};
let mut pending = core.pending.lock().unwrap();
if let Some(index) = pending.added.iter().position(|added| Arc::ptr_eq(added, &dev)) {
pending.added.remove(index);
} else {
pending.unexported.retain(|(unexported, _)| !Arc::ptr_eq(unexported, &dev));
pending.removed.push(dev);
}
true
}
pub async fn sync_async(&self) -> Result<()> {
let Some(core) = self.core.upgrade() else {
return Ok(());
};
let _sync = core.sync_lock.lock().await;
if core.stopped.load(Ordering::Acquire) {
return Ok(());
}
let mut first_err = None;
let removed = mem::take(&mut core.pending.lock().unwrap().removed);
for dev in removed {
if let Err(err) = self.database.unexport_dev_async(dev.name()).await {
warn!(dev = %dev.name(), %err, "Failed to unexport the removed device");
first_err.get_or_insert(err);
}
if let Err(err) = dev.withdraw().await {
warn!(dev = %dev.name(), %err, "Failed to delete the removed device");
first_err.get_or_insert(err);
}
if !core.devs.lock().unwrap().contains_key(Name::new(dev.name())) {
self.end_dev_events(dev.name());
}
debug!(dev = %dev.name(), "Removed the device");
}
loop {
let mut added = mem::take(&mut core.pending.lock().unwrap().added);
if added.is_empty() {
break;
}
added.sort_by_key(|dev| dev.class().index());
for dev in added {
if let Err(err) = dev.init().await {
warn!(dev = %dev.name(), %err, "Failed to complete the initialization of the device, its init function panicked");
}
core.pending.lock().unwrap().unexported.push((dev, None));
}
}
let unexported = mem::take(&mut core.pending.lock().unwrap().unexported);
let mut failed = Vec::new();
for (dev, itr) in unexported {
let served = match itr {
Some(itr) => Ok(itr),
None => dev.serve(),
};
let itr = match served {
Ok(itr) => itr,
Err(err) => {
warn!(dev = %dev.name(), %err, "Failed to serve the device");
first_err.get_or_insert(err);
failed.push((dev, None));
continue;
}
};
if let Err(err) = self.export_dev(&*core.backend, &*dev, &itr).await {
warn!(dev = %dev.name(), %err, "Failed to export the device");
first_err.get_or_insert(err);
failed.push((dev, Some(itr)));
continue;
}
debug!(dev = %dev.name(), "Exported the device");
}
core.pending.lock().unwrap().unexported.extend(failed);
first_err.map_or(Ok(()), Err)
}
pub fn sync(&self) -> Result<()> {
block_on(self.sync_async())
}
async fn export_dev(&self, backend: &dyn BackendServer, dev: &dyn AnyDev, itr: &str) -> Result<()> {
self.database.export_dev_async(ExportDevInfo {
name: Cow::Borrowed(dev.name()),
itr: Cow::Borrowed(itr),
host: Cow::Borrowed(&backend.host()),
version: Cow::Owned(backend.version().to_string()),
pid: std::process::id() as i32,
}).await
}
pub fn class_names(&self) -> Vec<Arc<str>> {
self.any_classes().iter().map(|class| Arc::clone(class.name())).collect()
}
pub(super) fn any_classes(&self) -> Vec<Arc<dyn AnyClass>> {
let Some(core) = self.core.upgrade() else {
return Vec::new();
};
let mut classes = core.classes.lock().unwrap().values().map(Arc::clone).collect::<Vec<_>>();
classes.sort_by_key(|class| class.index());
classes
}
pub fn dev_names(&self) -> Vec<Arc<str>> {
self.any_devs().iter().map(|dev| Arc::clone(dev.name())).collect()
}
pub(super) fn any_devs(&self) -> Vec<Arc<dyn AnyDev>> {
let Some(core) = self.core.upgrade() else {
return Vec::new();
};
let mut devs = core.devs.lock().unwrap().values().map(Arc::clone).collect::<Vec<_>>();
devs.sort_by(|a, b| a.class().index().cmp(&b.class().index()).then_with(|| a.name().cmp(b.name())));
devs
}
pub fn has_dev(&self, name: &str) -> bool {
self.core.upgrade().is_some_and(|core| core.devs.lock().unwrap().contains_key(Name::new(name)))
}
pub fn get_dev<M: Model>(&self, name: &str) -> Option<Arc<Dev<M>>> {
self.any_dev(name)?.into_any().downcast().ok()
}
pub(super) fn any_dev(&self, name: &str) -> Option<Arc<dyn AnyDev>> {
let core = self.core.upgrade()?;
core.devs.lock().unwrap().get(Name::new(name)).map(Arc::clone)
}
pub async fn stop_async(&self) -> Result<()> {
let Some(core) = self.core.upgrade() else {
return Ok(());
};
let _sync = core.sync_lock.lock().await;
if core.stopped.swap(true, Ordering::AcqRel) {
return Ok(());
}
info!(server = %self.name(), "Stopping the server");
let unexported = self.database.unexport_server_async(self.name()).await;
if let Err(err) = &unexported {
warn!(server = %self.name(), %err, "Failed to unexport the server");
}
core.backend.stop();
core.poller.shutdown();
self.end_events();
let removed = mem::take(&mut *core.pending.lock().unwrap()).removed;
let mut devs = mem::take(&mut *core.devs.lock().unwrap())
.into_values()
.collect::<Vec<_>>();
devs.sort_by_key(|dev| std::cmp::Reverse(dev.class().index()));
let mut first_err = unexported.err();
for dev in removed.into_iter().chain(devs) {
if let Err(err) = dev.withdraw().await {
warn!(dev = %dev.name(), %err, "Failed to delete the device");
first_err.get_or_insert(err);
}
}
info!(server = %self.name(), "Stopped the server");
first_err.map_or(Ok(()), Err)
}
pub fn stop(&self) -> Result<()> {
block_on(self.stop_async())
}
pub async fn wait_async(&self) {
let Some(core) = self.core.upgrade() else {
return;
};
core.backend.wait().await;
drop(core.sync_lock.lock().await);
}
pub fn wait(&self) {
block_on(self.wait_async())
}
pub fn wait_timeout(&self, timeout: Duration) -> Result<()> {
let Some(core) = self.core.upgrade() else {
return Ok(());
};
block_on_timeout(core.backend.wait(), timeout)
.map_err(|err| err.with_description(format!("Server {:?} is still answering requests after {timeout:?}", self.name())))?;
block_on(core.sync_lock.lock());
Ok(())
}
pub(super) fn subscribe_attr_value(&self, dev: &str, attr: &str, event: AttrValueEvent) -> BoxStream<'static, Result<AttrRead<Value>>> {
self.subscribe_event(dev, attr, |events| events.values.entry(event).or_default()).boxed()
}
pub(super) fn subscribe_attr_ready(&self, dev: &str, attr: &str) -> BoxStream<'static, Result<AttrReady>> {
self.subscribe_event(dev, attr, |events| &mut events.ready).boxed()
}
pub(super) fn subscribe_attr_config(&self, dev: &str, attr: &str) -> BoxStream<'static, Result<AttrInfo>> {
self.subscribe_event(dev, attr, |events| &mut events.conf).boxed()
}
pub(super) fn push_attr_value(&self, dev: &str, attr: &str, event: AttrValueEvent, read: Result<AttrRead<Value>>) {
self.push_event(dev, attr, |events| events.values.entry(event).or_default(), read);
}
pub(super) fn push_attr_ready(&self, dev: &str, attr: &str, ready: Result<AttrReady>) {
self.push_event(dev, attr, |events| &mut events.ready, ready);
}
pub(super) fn push_attr_config(&self, dev: &str, attr: &str, info: Result<AttrInfo>) {
self.push_event(dev, attr, |events| &mut events.conf, info);
}
fn subscribe_event<T>(&self, dev: &str, attr: &str, select: impl FnOnce(&mut AttrEvents) -> &mut Subscribers<T>) -> EventStream<T> {
let slot = Arc::new(Slot::new());
let dev = Arc::<str>::from(dev);
let mut devs = self.events.lock().unwrap();
let attrs = devs.entry(ArcName::from(Arc::clone(&dev))).or_default();
let events = attrs.entry(ArcName::from(attr)).or_default();
let subscribers = select(events);
subscribers.retain(|slot| slot.strong_count() != 0);
subscribers.push(Arc::downgrade(&slot));
EventStream {
dev,
slot,
ended: false,
}
}
fn push_event<T: Clone>(&self, dev: &str, attr: &str, select: impl FnOnce(&mut AttrEvents) -> &mut Subscribers<T>, event: Result<T>) {
let mut devs = self.events.lock().unwrap();
let Some(attrs) = devs.get_mut(Name::new(dev)) else { return };
let Some(events) = attrs.get_mut(Name::new(attr)) else { return };
let subscribers = select(events);
subscribers.retain(|slot| slot.strong_count() != 0);
if let Some((last, others)) = subscribers.split_last() {
for slot in others.iter().filter_map(Weak::upgrade) {
slot.push(event.clone());
}
if let Some(slot) = last.upgrade() {
slot.push(event);
}
}
events.values.retain(|_, subscribers| !subscribers.is_empty());
if events.values.is_empty() && events.ready.is_empty() && events.conf.is_empty() {
attrs.remove(Name::new(attr));
if attrs.is_empty() {
devs.remove(Name::new(dev));
}
}
}
fn end_dev_events(&self, dev: &str) {
let attrs = self.events.lock().unwrap().remove(Name::new(dev));
for events in attrs.into_iter().flat_map(HashMap::into_values) {
events.end();
}
}
fn end_events(&self) {
let devs = mem::take(&mut *self.events.lock().unwrap());
for events in devs.into_values().flat_map(HashMap::into_values) {
events.end();
}
}
}
impl AttrEvents {
fn end(self) {
for subscribers in self.values.into_values() {
end_subscribers(subscribers);
}
end_subscribers(self.ready);
end_subscribers(self.conf);
}
}
fn end_subscribers<T>(subscribers: Subscribers<T>) {
for slot in subscribers.iter().filter_map(Weak::upgrade) {
slot.end();
}
}
struct EventStream<T> {
dev: Arc<str>,
slot: Arc<Slot<Result<T>>>,
ended: bool,
}
impl<T> Stream for EventStream<T> {
type Item = Result<T>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if this.ended {
return Poll::Ready(None);
}
this.slot.poll_take(cx).map(|event| event.or_else(|| {
this.ended = true;
Some(Err(Error::new(ErrorKind::DevNotConnected)
.with_description(format!("Device {:?} is no longer served", this.dev))))
}))
}
}
pub struct ServerBuilder {
exe: String,
inst: String,
backend: Option<Arc<dyn Backend>>,
database: Option<DatabaseProxy>,
config: BackendServerConfig,
local: bool,
stop_on_terminate: bool,
#[cfg(feature = "cli")]
terminate_on_signals: bool,
boot_timeout: Duration,
test_context: Option<TestContext>,
classes: Vec<(ArcName, Arc<dyn AnyClassDef>)>,
}
trait AnyClassDef: Send + Sync {
fn new_class(self: Arc<Self>, server: Arc<Server>, index: usize) -> Arc<dyn AnyClass>;
}
impl<M: Model> AnyClassDef for ClassDef<M> {
fn new_class(self: Arc<Self>, server: Arc<Server>, index: usize) -> Arc<dyn AnyClass> {
Class::new(server, self, index)
}
}
impl ServerBuilder {
pub fn new(exe: &str, inst: &str) -> Self {
Self {
exe: exe.to_string(),
inst: inst.to_string(),
backend: None,
database: None,
config: BackendServerConfig::default(),
local: true,
stop_on_terminate: false,
#[cfg(feature = "cli")]
terminate_on_signals: false,
boot_timeout: DEFAULT_BOOT_TIMEOUT,
test_context: None,
classes: Vec::new(),
}
}
pub fn backend(mut self, backend: Arc<dyn Backend>) -> Self {
self.backend = Some(backend);
self
}
pub fn database(mut self, database: DatabaseProxy) -> Self {
self.database = Some(database);
self
}
pub fn host(mut self, host: &str) -> Self {
self.config = self.config.host(host);
self
}
pub fn port<R: RangeBounds<u16>>(mut self, port: R) -> Self {
self.config = self.config.port(port);
self
}
pub fn zmq_heartbeat_port<R: RangeBounds<u16>>(mut self, port: R) -> Self {
self.config = self.config.zmq_heartbeat_port(port);
self
}
pub fn zmq_event_port<R: RangeBounds<u16>>(mut self, port: R) -> Self {
self.config = self.config.zmq_event_port(port);
self
}
pub fn local(mut self, local: bool) -> Self {
self.local = local;
self
}
#[track_caller]
pub fn class<M: Model>(mut self, def: ClassDef<M>) -> Self {
if !name::check_tango_class_name(&def.name) {
panic!("the class name {:?} is invalid", def.name);
}
if def.name.eq_ignore_ascii_case(admin::CLASS_NAME) {
panic!("the class name {:?} is reserved to the admin device", def.name);
}
if self.classes.iter().any(|(name, _)| **name == *Name::new(&def.name)) {
panic!("the class {:?} is already defined", def.name);
}
self.classes.push((ArcName::from(&*def.name), Arc::new(def)));
self
}
pub fn stop_on_terminate(mut self, stop_on_terminate: bool) -> Self {
self.stop_on_terminate = stop_on_terminate;
self
}
pub fn boot_timeout(mut self, boot_timeout: Duration) -> Self {
self.boot_timeout = boot_timeout;
self
}
async fn boot(self) -> Result<ServerHandle> {
let backend = self.backend.unwrap_or_else(default_backend);
let database = self.database
.unwrap_or_else(|| DatabaseProxy::new_from_env_with_backend(Arc::clone(&backend)));
let admin_dev_name = Arc::<str>::from(format!("{ADMIN_DEV_PREFIX}{}/{}", self.exe, self.inst));
let (tango_host, tango_port) = database.host_and_port_async().await?;
let config = self.config.clone()
.admin_name(Arc::clone(&admin_dev_name))
.tango_host(tango_host, tango_port);
let backend = backend.serve(&config).await?;
let poller = Poller::spawn()?;
let core = Arc::new_cyclic(|core| Core {
server: Arc::new(Server {
core: core.clone(),
admin_dev_name,
database,
events: Mutex::new(HashMap::new()),
local: self.local,
}),
backend,
classes: Mutex::new(HashMap::new()),
class_count: AtomicUsize::new(1 + self.classes.len()),
devs: Mutex::new(HashMap::new()),
pending: Mutex::new(Pending::default()),
sync_lock: async_lock::Mutex::new(()),
stopped: AtomicBool::new(false),
poller,
});
let admin = Class::new(Arc::clone(&core.server), Arc::new(Admin::def()), 0);
let mut classes = HashMap::new();
classes.insert(ArcName::from(admin::CLASS_NAME), Arc::clone(&admin) as Arc<dyn AnyClass>);
for (index, (name, def)) in self.classes.into_iter().enumerate() {
classes.insert(name, def.new_class(Arc::clone(&core.server), 1 + index));
}
*core.classes.lock().unwrap() = classes;
let handle = ServerHandle { core, _test_context: self.test_context };
let admin_dev = admin.add_dev(handle.admin_dev_name())?;
let booted = match handle.sync_async().await {
Ok(()) => admin_dev.check_model().await,
Err(err) => Err(err),
};
if let Err(err) = booted {
let _ = handle.stop_async().await;
handle.wait_async().await;
return Err(err);
}
drop(admin_dev);
info!(server = %handle.name(),
host = %handle.core.backend.host(),
port = handle.core.backend.port(),
devs = handle.core.devs.lock().unwrap().len() - 1, "Started the server");
Ok(handle)
}
pub fn serve(self) -> Result<ServerHandle> {
let boot_timeout = self.boot_timeout;
let server_name = format!("{}/{}", self.exe, self.inst);
block_on_timeout(self.serve_async(), boot_timeout)
.map_err(|err| err.with_description(format!("Server {server_name:?} did not boot within {boot_timeout:?}")))?
}
pub async fn serve_async(self) -> Result<ServerHandle> {
let server_name = format!("{}/{}", self.exe, self.inst);
if !name::check_tango_server_part_name(&self.exe)
|| !name::check_tango_server_part_name(&self.inst) {
return Err(Error::new(ErrorKind::ServerNameInvalid)
.with_description(format!("Server {server_name:?} has an invalid name, it should be \
exe/inst, each part made of letters, digits, '_' and '-'")));
}
#[cfg(feature = "cli")]
if self.terminate_on_signals {
cli::install_signal_handler()?;
}
let stop_on_terminate = self.stop_on_terminate;
let boot = pin!(self.boot());
if !stop_on_terminate {
return boot.await;
}
let handle = match future::select(boot, pin!(terminate::terminating())).await {
Either::Left((booted, _)) => booted?,
Either::Right(((), _)) => return Err(Error::new(ErrorKind::Aborted)
.with_description(format!("Server {server_name:?} did not finish booting, the process is terminating"))),
};
if terminate::register(&handle.core.server) {
let _ = handle.stop_async().await;
}
Ok(handle)
}
pub fn run(self) -> Result<()> {
self.serve()?.wait();
Ok(())
}
pub async fn run_async(self) -> Result<()> {
self.serve_async().await?.wait_async().await;
Ok(())
}
}
#[cfg(feature = "cli")]
impl ServerBuilder {
pub fn terminate_on_signals(mut self, terminate_on_signals: bool) -> Self {
self.terminate_on_signals = terminate_on_signals;
if terminate_on_signals {
self = self.stop_on_terminate(true);
}
self
}
pub fn from_args() -> Self {
Self::from_raw_args(None, None)
}
pub fn from_raw_args(exec: Option<&str>, args: Option<&ServerArgs>) -> Self {
use std::process;
use clap::{Args, Command, FromArgMatches};
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::EnvFilter;
let exec = exec.map(Cow::Borrowed).unwrap_or_else(|| Cow::Owned(cli::exe()));
let args = match args {
Some(args) => Cow::Borrowed(args),
None => {
let matches = ServerArgs::augment_args(Command::new(exec.to_string())).get_matches();
let args = ServerArgs::from_arg_matches(&matches).unwrap_or_else(|err| err.exit());
Cow::Owned(args)
}
};
let level = match args.verbose {
0 => LevelFilter::INFO,
1 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
};
let _ = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::builder()
.with_default_directive(level.into())
.from_env_lossy())
.try_init();
let mut builder = Self::new(&exec, &args.inst)
.terminate_on_signals(true);
if let Some(host) = &args.host {
builder = builder.host(host);
}
if let Some((start, end)) = args.port {
builder = builder.port(start..=end);
}
if let Some((start, end)) = args.zmq_heartbeat_port {
builder = builder.zmq_heartbeat_port(start..=end);
}
if let Some((start, end)) = args.zmq_event_port {
builder = builder.zmq_event_port(start..=end);
}
if !args.devs.is_empty() {
let ctx = cli::test_context(&exec, &args).unwrap_or_else(|err| {
eprintln!("Error: {err:#}");
process::exit(1)
});
builder = builder.database(ctx.database().clone());
builder.test_context = Some(ctx);
}
builder
}
}
#[derive(Debug)]
pub struct ServerHandle {
core: Arc<Core>,
_test_context: Option<TestContext>,
}
impl ServerHandle {
#[inline]
pub fn server(&self) -> &Server {
&self.core.server
}
pub(crate) fn server_arc(&self) -> Arc<Server> {
Arc::clone(&self.core.server)
}
}
impl Deref for ServerHandle {
type Target = Server;
#[inline]
fn deref(&self) -> &Server {
&self.core.server
}
}
impl Drop for ServerHandle {
fn drop(&mut self) {
self.core.backend.stop();
self.core.server.end_events();
}
}
#[cfg(test)]
mod tests {
use futures::executor::block_on;
use futures::FutureExt;
use crate::dev::{AttrQuality, DevState};
use crate::value::AttrDataKind;
use crate::testing::TestContext;
use super::super::def::ClassDef;
use super::*;
fn serve() -> (TestContext, Arc<Server>) {
let ctx = TestContext::new().unwrap();
ctx.database().add_server("Test/test", &[("a/b/c", "A")]).unwrap();
let server = ctx.serve(ServerBuilder::new("Test", "test")
.class(ClassDef::<()>::new_default("A")))
.unwrap();
(ctx, server)
}
fn read(value: f64) -> AttrRead<Value> {
AttrRead {
value: Some(Value::from(value)),
w_value: None,
quality: AttrQuality::Valid,
time: std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(1),
}
}
fn now<T>(stream: &mut BoxStream<'static, T>) -> Option<Option<T>> {
stream.next().now_or_never()
}
#[test]
fn latest_only() {
let (_ctx, server) = serve();
let mut sub = server.subscribe_attr_value("a/b/c", "Attr", AttrValueEvent::Change);
assert!(now(&mut sub).is_none());
server.push_attr_value("a/b/c", "attr", AttrValueEvent::Change, Ok(read(1.0)));
server.push_attr_value("A/B/C", "ATTR", AttrValueEvent::Change, Ok(read(2.0)));
server.push_attr_value("a/b/c", "attr", AttrValueEvent::Archive, Ok(read(3.0)));
server.push_attr_value("a/b/c", "other", AttrValueEvent::Change, Ok(read(4.0)));
assert_eq!(now(&mut sub).unwrap().unwrap().unwrap(), read(2.0));
assert!(now(&mut sub).is_none());
}
#[test]
fn every_subscriber() {
let (_ctx, server) = serve();
let mut first = server.subscribe_attr_value("a/b/c", "attr", AttrValueEvent::Change);
let mut second = server.subscribe_attr_value("a/b/c", "attr", AttrValueEvent::Change);
server.push_attr_value("a/b/c", "attr", AttrValueEvent::Change, Err(Error::new(ErrorKind::Reason(reason::API_AttrNotAllowed))));
for sub in [&mut first, &mut second] {
let err = now(sub).unwrap().unwrap().unwrap_err();
assert_eq!(err.kind(), ErrorKind::Reason(reason::API_AttrNotAllowed));
}
}
#[test]
fn unsubscribe() {
let (_ctx, server) = serve();
let sub = server.subscribe_attr_ready("a/b/c", "attr");
drop(sub);
server.push_attr_ready("a/b/c", "attr", Ok(AttrReady { data_kind: AttrDataKind::Long, counter: 1 }));
assert!(server.events.lock().unwrap().is_empty());
}
#[test]
fn restart() {
let (_ctx, server) = serve();
let mut sub = server.subscribe_attr_config("a/b/c", "attr");
assert!(server.remove_dev("a/b/c"));
server.add_dev("A", "a/b/c").unwrap();
server.sync().unwrap();
assert!(now(&mut sub).is_none());
assert!(server.remove_dev("a/b/c"));
server.sync().unwrap();
assert_eq!(now(&mut sub).unwrap().unwrap().unwrap_err().kind(), ErrorKind::DevNotConnected);
assert!(now(&mut sub).unwrap().is_none());
}
#[test]
fn stop() {
let (_ctx, server) = serve();
let mut sub = server.subscribe_attr_ready("a/b/c", "attr");
server.push_attr_ready("a/b/c", "attr", Ok(AttrReady { data_kind: AttrDataKind::Long, counter: 1 }));
server.stop().unwrap();
assert!(now(&mut sub).unwrap().unwrap().is_ok());
assert_eq!(now(&mut sub).unwrap().unwrap().unwrap_err().kind(), ErrorKind::DevNotConnected);
assert!(now(&mut sub).unwrap().is_none());
}
#[test]
fn weak() {
let (_ctx, server) = serve();
let weak = Arc::downgrade(&server.any_dev("a/b/c").unwrap());
assert_eq!(&**weak.upgrade().unwrap().name(), "a/b/c");
assert!(server.remove_dev("a/b/c"));
server.add_dev("A", "a/b/c").unwrap();
server.sync().unwrap();
assert!(weak.upgrade().is_none());
}
#[test]
fn dropped() {
let (ctx, server) = serve();
let mut sub = server.subscribe_attr_ready("a/b/c", "attr");
drop(ctx);
assert_eq!(block_on(sub.next()).unwrap().unwrap_err().kind(), ErrorKind::DevNotConnected);
}
#[test]
fn freed() {
let (ctx, server) = serve();
server.add_dev("A", "a/b/d").unwrap();
let served = Arc::downgrade(&server.any_dev("a/b/c").unwrap());
let pending = Arc::downgrade(&server.any_dev("a/b/d").unwrap());
drop(ctx);
assert!(served.upgrade().is_none());
assert!(pending.upgrade().is_none());
assert!(!server.has_dev("a/b/c"));
assert_eq!(server.add_dev("A", "a/b/e").unwrap_err().kind(), ErrorKind::Reason(reason::API_ServerNotRunning));
server.wait();
}
#[test]
fn withdrawn() {
let (ctx, server) = serve();
let dev = server.get_dev::<()>("a/b/c").unwrap();
assert!(!dev.is_withdrawn());
dev.push_user_event("state", DevState::On).unwrap();
assert!(server.remove_dev("a/b/c"));
server.add_dev("A", "a/b/c").unwrap();
server.sync().unwrap();
assert!(dev.is_withdrawn());
assert_eq!(dev.push_user_event("state", DevState::On).unwrap_err().kind(), ErrorKind::DevNotConnected);
let restarted = server.get_dev::<()>("a/b/c").unwrap();
assert!(!restarted.is_withdrawn());
drop(ctx);
assert!(restarted.is_withdrawn());
}
#[test]
fn withdrawn_local() {
let (ctx, server) = serve();
let client = ctx.dev("a/b/c");
client.ping().unwrap();
assert!(ctx.is_local("a/b/c").unwrap());
let dev = server.get_dev::<()>("a/b/c").unwrap();
assert!(server.remove_dev("a/b/c"));
server.sync().unwrap();
assert!(dev.is_withdrawn());
assert_eq!(client.ping().unwrap_err().kind(), ErrorKind::DevNotConnected);
assert_eq!(client.state().unwrap_err().kind(), ErrorKind::DevNotExported);
}
#[test]
fn wait_twice() {
let (_ctx, server) = serve();
server.stop().unwrap();
server.wait();
server.wait();
assert!(server.dev_names().is_empty());
}
}