1use crate::lifecycle::EVENT_PLUG_DOWN_PREFIX;
8use crate::plugin::{PANIC_LIMIT, Plugin};
9use crate::registry::{EntryStats, PluginStateCell, PluginStateCode};
10use crossbeam_channel::Receiver;
11use ek_event::{Event, EventEngine};
12use std::panic::{AssertUnwindSafe, catch_unwind};
13use std::sync::Arc;
14use std::sync::atomic::Ordering;
15use std::thread::JoinHandle;
16
17pub(crate) fn spawn_worker(
18 plugin: Arc<dyn Plugin>,
19 rx: Receiver<Event>,
20 state: PluginStateCell,
21 stats: Arc<EntryStats>,
22 bus: Arc<EventEngine>,
23) -> JoinHandle<()> {
24 let name = plugin.name().to_string();
25 std::thread::Builder::new()
26 .name(format!("ek-worker.{name}"))
27 .spawn(move || worker_loop(plugin, rx, state, stats, bus))
28 .expect("ek-core: spawn worker thread")
29}
30
31fn worker_loop(
32 plugin: Arc<dyn Plugin>,
33 rx: Receiver<Event>,
34 state: PluginStateCell,
35 stats: Arc<EntryStats>,
36 bus: Arc<EventEngine>,
37) {
38 let mut consecutive: u64 = 0;
41 loop {
42 match rx.recv() {
43 Ok(event) => {
44 let _ = stats
47 .pending
48 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |v| {
49 Some(v.saturating_sub(1))
50 });
51 let result = catch_unwind(AssertUnwindSafe(|| plugin.on_mail(&event)));
52 match result {
53 Ok(()) => consecutive = 0,
54 Err(_panic) => {
55 consecutive += 1;
56 let _ = stats.panics.fetch_add(1, Ordering::AcqRel);
57 if consecutive >= PANIC_LIMIT as u64 {
58 if state.cas(PluginStateCode::Running, PluginStateCode::Quarantined) {
59 eprintln!(
61 "ek-core: plugin {:?} quarantined after {} consecutive handler panics",
62 plugin.name(),
63 consecutive
64 );
65 let _ = bus.put(Event::new(format!(
67 "{EVENT_PLUG_DOWN_PREFIX}{}",
68 plugin.name()
69 )));
70 }
71 stats.pending.store(0, Ordering::Release);
74 break;
75 }
76 eprintln!(
78 "ek-core: plugin {:?} handler panicked ({consecutive}/{PANIC_LIMIT})",
79 plugin.name()
80 );
81 }
82 }
83 drop(event);
85 }
86 Err(_disconnected) => {
88 state.finish();
89 break;
90 }
91 }
92 }
93 stats.done.store(true, Ordering::Release);
94}