use std::sync::{Arc, Condvar, Mutex};
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
use crate::context::WeakContext;
use crate::error::{CamelError, Result};
use crate::message::Exchange;
use crate::spi::Processor;
#[derive(Default)]
pub struct StopFlag {
stopped: Mutex<bool>,
cond: Condvar,
fast: std::sync::atomic::AtomicBool,
}
impl StopFlag {
pub fn new() -> Arc<StopFlag> {
Arc::new(StopFlag::default())
}
pub fn stop(&self) {
let mut s = self.stopped.lock().unwrap();
*s = true;
self.fast.store(true, std::sync::atomic::Ordering::SeqCst);
self.cond.notify_all();
}
pub fn reset(&self) {
let mut s = self.stopped.lock().unwrap();
*s = false;
self.fast.store(false, std::sync::atomic::Ordering::SeqCst);
}
pub fn is_stopped(&self) -> bool {
self.fast.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn sleep(&self, d: Duration) -> bool {
match deadline_after(d) {
Some(deadline) => self.sleep_until(deadline),
None => {
let mut s = self.stopped.lock().unwrap();
while !*s {
s = self.cond.wait(s).unwrap();
}
true
}
}
}
pub fn sleep_until(&self, deadline: Instant) -> bool {
let mut s = self.stopped.lock().unwrap();
loop {
if *s {
return true;
}
let now = Instant::now();
if now >= deadline {
return false;
}
s = self.cond.wait_timeout(s, deadline - now).unwrap().0;
}
}
}
#[derive(Default)]
pub struct SuspendState {
suspended: Mutex<bool>,
cond: Condvar,
fast: std::sync::atomic::AtomicBool,
}
impl SuspendState {
pub fn new() -> Arc<SuspendState> {
Arc::new(SuspendState::default())
}
pub fn set(&self, suspended: bool) {
let mut s = self.suspended.lock().unwrap();
*s = suspended;
self.fast.store(suspended, std::sync::atomic::Ordering::SeqCst);
self.cond.notify_all();
}
pub fn get(&self) -> bool {
self.fast.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn wait_while_suspended(&self, flag: &StopFlag) -> bool {
let mut s = self.suspended.lock().unwrap();
while *s {
if flag.is_stopped() {
return true;
}
s = self.cond.wait_timeout(s, Duration::from_millis(20)).unwrap().0;
}
flag.is_stopped()
}
}
impl crate::spi::SuspendableService for SuspendState {
fn suspend(&self) -> Result<()> {
self.set(true);
Ok(())
}
fn resume(&self) -> Result<()> {
self.set(false);
Ok(())
}
fn is_suspended(&self) -> bool {
self.get()
}
}
pub fn deadline_after(d: Duration) -> Option<Instant> {
Instant::now().checked_add(d)
}
#[derive(Default)]
pub struct BackgroundThreads {
inner: Arc<ThreadsInner>,
}
#[derive(Default)]
struct ThreadsInner {
flag: Mutex<Option<Arc<StopFlag>>>,
handles: Mutex<Vec<JoinHandle<()>>>,
}
impl ThreadsInner {
fn flag(&self) -> Arc<StopFlag> {
self.flag.lock().unwrap().get_or_insert_with(StopFlag::new).clone()
}
fn spawn(&self, name: &str, f: impl FnOnce(Arc<StopFlag>) + Send + 'static) -> Result<()> {
self.spawn_with(name, self.flag(), f)
}
fn spawn_with(&self, name: &str, flag: Arc<StopFlag>, f: impl FnOnce(Arc<StopFlag>) + Send + 'static) -> Result<()> {
let h = std::thread::Builder::new()
.name(name.to_string())
.spawn(move || f(flag))
.map_err(|e| CamelError::illegal_state(format!("Cannot spawn thread {}: {}", name, e)))?;
let mut handles = self.handles.lock().unwrap();
handles.retain(|h| !h.is_finished());
handles.push(h);
Ok(())
}
}
#[derive(Clone)]
pub struct ThreadSpawner {
inner: Arc<ThreadsInner>,
}
impl ThreadSpawner {
pub fn spawn(&self, name: &str, flag: Arc<StopFlag>, f: impl FnOnce(Arc<StopFlag>) + Send + 'static) -> Result<()> {
if flag.is_stopped() {
return Ok(());
}
self.inner.spawn_with(name, flag, f)
}
}
impl BackgroundThreads {
pub fn new() -> Self {
Self::default()
}
pub fn flag(&self) -> Arc<StopFlag> {
self.inner.flag()
}
pub fn spawn(&self, name: &str, f: impl FnOnce(Arc<StopFlag>) + Send + 'static) -> Result<()> {
self.inner.spawn(name, f)
}
pub fn spawner(&self) -> ThreadSpawner {
ThreadSpawner { inner: self.inner.clone() }
}
pub fn is_current_thread(&self) -> bool {
let me = std::thread::current().id();
self.inner.handles.lock().unwrap().iter().any(|h| h.thread().id() == me)
}
pub fn is_running(&self) -> bool {
self.inner.handles.lock().unwrap().iter().any(|h| !h.is_finished())
}
pub fn stop(&self) {
let flag = self.inner.flag.lock().unwrap().take();
if let Some(f) = &flag {
f.stop();
}
let me = std::thread::current().id();
loop {
let handles: Vec<JoinHandle<()>> = std::mem::take(&mut *self.inner.handles.lock().unwrap());
if handles.is_empty() {
break;
}
for h in handles {
if h.thread().id() == me {
continue; }
let _ = h.join();
}
}
}
}
impl Drop for BackgroundThreads {
fn drop(&mut self) {
if let Ok(f) = self.inner.flag.lock() {
if let Some(f) = f.as_ref() {
f.stop();
}
}
}
}
pub fn new_exchange(ctx: &WeakContext) -> Exchange {
Exchange::new(ctx.upgrade())
}
pub fn process_consumer_exchange(logger: &str, processor: &dyn Processor, exchange: &mut Exchange) -> bool {
if let Err(e) = processor.process(exchange) {
exchange.set_exception(e);
}
if let Some(e) = &exchange.exception {
crate::camel_warn!(
logger,
"Error processing exchange. Exchange[{}]. Caused by: [{} - {}]",
exchange.exchange_id,
e.kind,
e.message
);
false
} else {
true
}
}
pub fn copy_results(target: &mut Exchange, source: &Exchange) {
target.message = source.message.clone();
target.properties = source.properties.clone();
target.exception = source.exception.clone();
target.rollback_only = source.rollback_only;
}
pub fn time_unit_millis(unit: &str) -> Result<f64> {
Ok(match unit.to_ascii_uppercase().as_str() {
"NANOSECONDS" => 0.000_001,
"MICROSECONDS" => 0.001,
"MILLISECONDS" => 1.0,
"SECONDS" => 1000.0,
"MINUTES" => 60_000.0,
"HOURS" => 3_600_000.0,
"DAYS" => 86_400_000.0,
other => return Err(CamelError::illegal_argument(format!("Invalid timeUnit: {}", other))),
})
}
pub fn parse_duration_with_unit(value: &str, unit_ms: f64) -> Result<Duration> {
let v = value.trim();
if let Ok(n) = v.parse::<f64>() {
if n <= 0.0 {
return Ok(Duration::ZERO);
}
return Ok(Duration::from_micros((n * unit_ms * 1000.0) as u64));
}
crate::util::parse_duration(v)
}
pub fn split_args(s: &str) -> Vec<String> {
let mut out = Vec::new();
let mut cur = String::new();
let mut quote: Option<char> = None;
let mut has = false;
for c in s.chars() {
match quote {
Some(q) if c == q => quote = None,
Some(_) => cur.push(c),
None if c == '"' || c == '\'' => {
quote = Some(c);
has = true;
}
None if c.is_whitespace() => {
if has || !cur.is_empty() {
out.push(std::mem::take(&mut cur));
has = false;
}
}
None => cur.push(c),
}
}
if has || !cur.is_empty() {
out.push(cur);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stop_flag_wakes_sleepers() {
let threads = BackgroundThreads::new();
threads
.spawn("t", |flag| {
flag.sleep(Duration::from_secs(60));
})
.unwrap();
let start = Instant::now();
threads.stop();
assert!(start.elapsed() < Duration::from_secs(2));
assert!(!threads.is_running());
}
#[test]
fn args_split() {
assert_eq!(split_args("a \"b c\" d"), vec!["a", "b c", "d"]);
assert_eq!(split_args(" x '' "), vec!["x", ""]);
}
#[test]
fn durations_with_unit() {
assert_eq!(parse_duration_with_unit("2", 1000.0).unwrap(), Duration::from_secs(2));
assert_eq!(parse_duration_with_unit("-1", 1.0).unwrap(), Duration::ZERO);
assert_eq!(parse_duration_with_unit("3s", 1.0).unwrap(), Duration::from_secs(3));
}
}