1use std::collections::{HashMap, HashSet, VecDeque};
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError, RwLock};
6use std::thread::JoinHandle;
7use std::time::{Duration, Instant};
8
9use ciplink::Value;
10use ciplink::cip::TagResult;
11use indexmap::IndexMap;
12use serde_json::{Map, Value as Json, json};
13
14use crate::config::PlcConfig;
15use crate::planner::{self, Read};
16use crate::plc::{Catalog, Factory, Plc};
17use crate::rules::{Group, Rules, split_bit, truthy};
18use crate::values::{Entry, merge, now_ms, to_value};
19
20#[cfg_attr(test, allow(dead_code))]
22const BACKOFF: [f64; 4] = [1.0, 2.0, 5.0, 10.0];
23const MISSING_RETRY: f64 = 10.0;
25const GROUP_RETRY: f64 = 60.0;
27
28#[cfg(test)]
31struct Hooks {
32 backoff: Vec<f64>,
33 missing_retry: f64,
34 group_retry: f64,
35 time: Option<Arc<dyn FakeTime>>,
36}
37
38#[cfg(test)]
39impl Default for Hooks {
40 fn default() -> Self {
41 Hooks {
42 backoff: vec![0.01, 0.02, 0.05, 0.1], missing_retry: MISSING_RETRY,
44 group_retry: GROUP_RETRY,
45 time: None,
46 }
47 }
48}
49
50#[cfg(test)]
52trait FakeTime: Send + Sync {
53 fn now(&self) -> f64;
54 fn wait(&self, shared: &Shared, seconds: f64) -> bool;
55}
56
57#[derive(Clone, Debug, PartialEq)]
59pub enum Event {
60 Values(String, Vec<(String, Entry)>),
62 Status(String, &'static str),
64 Meta(String),
66 Info(String, Vec<String>),
68}
69
70#[derive(Clone, Debug, PartialEq)]
72pub struct WriteResult {
73 pub ok: bool,
75 pub error: Option<String>,
77 pub kind: Option<String>,
79 pub value: Json,
81}
82
83impl WriteResult {
84 pub fn failed(error: impl Into<String>, kind: &str) -> Self {
86 WriteResult {
87 ok: false,
88 error: Some(error.into()),
89 kind: Some(kind.to_owned()),
90 value: Json::Null,
91 }
92 }
93}
94
95pub type WriteDone = Box<dyn FnOnce(WriteResult) + Send>;
97
98struct WriteRequest {
99 tag: String,
100 value: Value,
101 done: WriteDone,
102}
103
104#[derive(Clone, Debug, Default, PartialEq, Eq)]
106pub struct TypeInfo {
107 pub type_: Option<String>,
109 pub dims: Option<Vec<u64>>,
111}
112
113#[derive(Debug, Default)]
115pub struct Structure {
116 pub catalog: Catalog,
118 pub tag_list: Vec<Json>,
120 pub types: Map<String, Json>,
122}
123
124#[derive(Clone, Debug, Default)]
126pub struct Stats {
127 pub last_cycle_ms: Option<f64>,
129 pub last_ok_ts: Option<i64>,
131 pub reads_per_cycle: usize,
133 pub tags_per_cycle: usize,
135 pub group_fallbacks: u64,
137 pub overruns: u64,
139 pub reconnects: u64,
141 pub reloads: u64,
143}
144
145#[derive(Default)]
146struct Inner {
147 tiers: IndexMap<String, u8>,
149 grace: IndexMap<String, (f64, Option<u8>)>,
151 tier2: Option<Vec<String>>,
153 slice: usize,
154 last_read: HashMap<String, f64>,
156 last_cycle: Option<f64>,
158 groups: HashMap<String, Option<Group>>,
160 group_reads: HashMap<String, (Option<f64>, f64)>,
162 group_rates: IndexMap<String, u64>,
164 plans: HashMap<Vec<String>, Arc<Vec<Vec<Read>>>>,
166 no_group: HashMap<String, f64>,
168 new_tier2: HashSet<String>,
170 cache: HashMap<String, Entry>,
172 published: HashMap<String, Entry>,
174 missing: HashMap<String, f64>,
176 want_descriptions: HashSet<String>,
178}
179
180#[derive(Clone)]
182pub struct WorkerOptions {
183 pub cycle: f64,
185 pub tier2_period: f64,
187 pub grace_s: f64,
189 pub planner: bool,
191 pub whole_max_bytes: u64,
193}
194
195struct Shared {
196 name: String,
197 cfg: PlcConfig,
198 factory: Factory,
199 options: WorkerOptions,
200 rules: Arc<Rules>,
201 on_event: Arc<dyn Fn(Event) + Send + Sync>,
202 epoch: Instant,
203 state: Mutex<&'static str>,
204 inner: Mutex<Inner>,
205 writes: Mutex<VecDeque<WriteRequest>>,
206 wake: Condvar,
207 stop: AtomicBool,
208 structure: RwLock<Arc<Structure>>,
209 version: Mutex<Json>,
210 descriptions: Mutex<HashMap<String, Option<String>>>,
211 stats: Mutex<Stats>,
212 #[cfg(test)]
213 hooks: Mutex<Hooks>,
214}
215
216pub struct PlcWorker {
218 shared: Arc<Shared>,
219 thread: Mutex<Option<JoinHandle<()>>>,
220}
221
222fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
223 m.lock().unwrap_or_else(PoisonError::into_inner)
224}
225
226fn round1(x: f64) -> f64 {
228 (x * 10.0).round_ties_even() / 10.0
229}
230
231impl PlcWorker {
232 pub fn new(
234 cfg: PlcConfig,
235 factory: Factory,
236 options: WorkerOptions,
237 rules: Arc<Rules>,
238 on_event: Arc<dyn Fn(Event) + Send + Sync>,
239 ) -> Self {
240 PlcWorker {
241 shared: Arc::new(Shared {
242 name: cfg.name.clone(),
243 cfg,
244 factory,
245 options,
246 rules,
247 on_event,
248 epoch: Instant::now(),
249 state: Mutex::new("connecting"),
250 inner: Mutex::new(Inner::default()),
251 writes: Mutex::new(VecDeque::new()),
252 wake: Condvar::new(),
253 stop: AtomicBool::new(false),
254 structure: RwLock::new(Arc::new(Structure::default())),
255 version: Mutex::new(Json::Null),
256 descriptions: Mutex::new(HashMap::new()),
257 stats: Mutex::new(Stats::default()),
258 #[cfg(test)]
259 hooks: Mutex::default(),
260 }),
261 thread: Mutex::new(None),
262 }
263 }
264
265 pub fn start(&self) {
267 let shared = Arc::clone(&self.shared);
268 let handle = std::thread::Builder::new()
269 .name(format!("plc-{}", self.shared.name))
270 .spawn(move || shared.run())
271 .ok();
272 *lock(&self.thread) = handle;
273 }
274
275 pub fn stop(&self) {
277 self.shared.stop.store(true, Ordering::SeqCst);
278 {
279 let _writes = lock(&self.shared.writes);
280 self.shared.wake.notify_all();
281 }
282 if let Some(handle) = lock(&self.thread).take() {
283 let _ = handle.join();
284 }
285 self.shared.fail_writes("server stopping"); }
287
288 pub fn state(&self) -> &'static str {
290 *lock(&self.shared.state)
291 }
292
293 pub fn version(&self) -> Json {
295 lock(&self.shared.version).clone()
296 }
297
298 pub fn structure(&self) -> Arc<Structure> {
300 Arc::clone(
301 &self
302 .shared
303 .structure
304 .read()
305 .unwrap_or_else(PoisonError::into_inner),
306 )
307 }
308
309 pub fn stats(&self) -> Stats {
311 lock(&self.shared.stats).clone()
312 }
313
314 pub fn subscriptions(&self) -> usize {
316 lock(&self.shared.inner).tiers.len()
317 }
318
319 pub fn tier_counts(&self) -> Map<String, Json> {
321 let inner = lock(&self.shared.inner);
322 (1..=3u8)
323 .map(|n| {
324 let count = inner.tiers.values().filter(|&&t| t == n).count();
325 (format!("tier{n}"), json!(count))
326 })
327 .collect()
328 }
329
330 pub fn group_stats(&self) -> Map<String, Json> {
333 let shared = &self.shared;
334 let (counts, reads, rates) = {
335 let mut inner = lock(&shared.inner);
336 let mut counts: HashMap<String, u64> = HashMap::new();
337 let tier1: Vec<String> = inner
338 .tiers
339 .iter()
340 .filter(|(_, t)| **t == 1)
341 .map(|(tag, _)| tag.clone())
342 .collect();
343 for tag in tier1 {
344 if let Some(group) = shared.group(&mut inner, &tag) {
345 *counts.entry(group.name).or_default() += 1;
346 }
347 }
348 let mut rates: IndexMap<String, u64> = IndexMap::new();
349 for g in shared.rules.groups(&shared.name) {
350 if let (Some(name), Some(rate)) =
351 (g.get("name"), g.get("rate_ms").and_then(Json::as_u64))
352 {
353 let name = name
354 .as_str()
355 .map_or_else(|| name.to_string(), str::to_owned);
356 rates.insert(name, rate);
357 }
358 }
359 for (name, rate) in &inner.group_rates {
360 rates.insert(name.clone(), *rate);
361 }
362 (counts, inner.group_reads.clone(), rates)
363 };
364 rates
365 .into_iter()
366 .map(|(name, rate)| {
367 let period = match reads.get(&name) {
368 Some((Some(prev), last)) => json!(round1((last - prev) * 1000.0)),
369 _ => Json::Null,
370 };
371 let tags = counts.get(&name).copied().unwrap_or(0);
372 (
373 name,
374 json!({"rate_ms": rate, "tags": tags, "last_period_ms": period}),
375 )
376 })
377 .collect()
378 }
379
380 pub fn set_tier(&self, tag: &str, tier: Option<u8>) {
383 let shared = &self.shared;
384 let mut inner = lock(&shared.inner);
385 if tier.is_some_and(|t| t <= 2) || shared.options.grace_s <= 0.0 {
386 shared.set(&mut inner, tag, tier);
387 } else if inner.tiers.get(tag) == Some(&1) {
388 shared.set(&mut inner, tag, Some(2));
389 let deadline = shared.clock() + shared.options.grace_s;
390 inner.grace.insert(tag.to_owned(), (deadline, tier));
391 } else if let Some(grace) = inner.grace.get_mut(tag) {
392 grace.1 = tier;
393 } else {
394 shared.set(&mut inner, tag, tier);
395 }
396 }
397
398 pub fn snapshot(&self, tag: &str) -> Option<Entry> {
400 lock(&self.shared.inner).cache.get(tag).cloned()
401 }
402
403 pub fn write(&self, tag: &str, value: Value, done: WriteDone) {
405 if self.state() != "connected" {
406 done(WriteResult::failed("PLC not connected", "refused"));
407 return;
408 }
409 let mut writes = lock(&self.shared.writes);
410 writes.push_back(WriteRequest {
411 tag: tag.to_owned(),
412 value,
413 done,
414 });
415 self.shared.wake.notify_all();
416 }
417
418 pub fn type_info(&self, tag: &str) -> TypeInfo {
420 if split_bit(tag).is_some() {
421 return TypeInfo {
422 type_: Some("BOOL".into()),
423 dims: None,
424 };
425 }
426 if self.state() != "connected" {
427 return TypeInfo::default();
428 }
429 let structure = self.structure();
430 let Some(node) = structure.catalog.info(tag) else {
431 return TypeInfo::default();
432 };
433 let type_ = Some(node.client_type().to_owned()).filter(|t| !t.is_empty());
434 let dims = (!tag.ends_with(']') && !node.dims.is_empty()).then(|| node.client_dims());
435 TypeInfo { type_, dims }
436 }
437
438 pub fn description(&self, tag: &str) -> Option<String> {
440 lock(&self.shared.descriptions).get(tag).cloned().flatten()
441 }
442}
443
444#[cfg(test)]
446impl PlcWorker {
447 pub(crate) fn tiers(&self) -> IndexMap<String, u8> {
449 lock(&self.shared.inner).tiers.clone()
450 }
451
452 pub(crate) fn load(&self, plc: &mut dyn Plc) {
454 self.shared.check_version(plc, true);
455 *lock(&self.shared.state) = "connected";
456 }
457
458 pub(crate) fn set_state(&self, state: &'static str) {
460 *lock(&self.shared.state) = state;
461 }
462
463 pub(crate) fn set_cache(&self, tag: &str, entry: Entry) {
465 lock(&self.shared.inner).cache.insert(tag.to_owned(), entry);
466 }
467
468 pub(crate) fn set_description(&self, tag: &str, description: Option<&str>) {
470 lock(&self.shared.descriptions).insert(tag.to_owned(), description.map(str::to_owned));
471 }
472
473 pub(crate) fn run_writes(&self, plc: &mut dyn Plc) -> Result<(), String> {
475 self.shared.run_writes(plc)
476 }
477}
478
479impl Shared {
480 fn clock(&self) -> f64 {
481 #[cfg(test)]
482 if let Some(time) = self.fake_time() {
483 return time.now();
484 }
485 self.epoch.elapsed().as_secs_f64()
486 }
487
488 #[cfg(test)]
489 fn fake_time(&self) -> Option<Arc<dyn FakeTime>> {
490 lock(&self.hooks).time.clone()
491 }
492
493 fn backoff(&self, attempt: usize) -> f64 {
494 #[cfg(test)]
495 {
496 let hooks = lock(&self.hooks);
497 hooks.backoff[attempt.min(hooks.backoff.len() - 1)]
498 }
499 #[cfg(not(test))]
500 BACKOFF[attempt.min(BACKOFF.len() - 1)]
501 }
502
503 fn missing_retry(&self) -> f64 {
504 #[cfg(test)]
505 return lock(&self.hooks).missing_retry;
506 #[cfg(not(test))]
507 MISSING_RETRY
508 }
509
510 fn group_retry(&self) -> f64 {
511 #[cfg(test)]
512 return lock(&self.hooks).group_retry;
513 #[cfg(not(test))]
514 GROUP_RETRY
515 }
516
517 fn emit(&self, event: Event) {
518 (self.on_event)(event);
519 }
520
521 fn set_state(&self, state: &'static str) {
522 let changed = {
523 let mut current = lock(&self.state);
524 let changed = *current != state;
525 *current = state;
526 changed
527 };
528 if changed {
529 log::info!("{}: {state}", self.name);
530 self.emit(Event::Status(self.name.clone(), state));
531 }
532 }
533
534 fn set(&self, inner: &mut Inner, tag: &str, tier: Option<u8>) {
536 inner.grace.shift_remove(tag);
537 match tier {
538 None => {
539 inner.tiers.shift_remove(tag);
540 inner.cache.remove(tag);
541 inner.published.remove(tag);
542 inner.missing.remove(tag);
543 inner.groups.remove(tag);
544 inner.want_descriptions.remove(tag);
545 }
546 Some(t) => {
547 if t == 2 && matches!(inner.tiers.get(tag), None | Some(3)) {
548 inner.new_tier2.insert(tag.to_owned()); }
550 if !inner.tiers.contains_key(tag) && split_bit(tag).is_none() {
551 inner.want_descriptions.insert(tag.to_owned());
552 }
553 inner.tiers.insert(tag.to_owned(), t);
554 }
555 }
556 if tier != Some(2) {
557 inner.new_tier2.remove(tag);
558 }
559 if tier != Some(1) {
560 inner.last_read.remove(tag); }
562 inner.tier2 = None;
563 inner.plans.clear();
564 }
565
566 fn group(&self, inner: &mut Inner, tag: &str) -> Option<Group> {
567 if let Some(g) = inner.groups.get(tag) {
568 return g.clone();
569 }
570 let g = self.rules.group(&self.name, tag);
571 inner.groups.insert(tag.to_owned(), g.clone());
572 g
573 }
574
575 fn rate(&self, inner: &mut Inner, tag: &str) -> f64 {
576 self.group(inner, tag)
577 .map_or(self.options.cycle, |g| g.rate_ms as f64 / 1000.0)
578 }
579
580 fn cycle_tags(&self, inner: &mut Inner, now: f64) -> (Vec<String>, f64, bool) {
583 let cycle = self.options.cycle;
584 let tier1 = |inner: &Inner| -> Vec<String> {
585 inner
586 .tiers
587 .iter()
588 .filter(|(_, t)| **t == 1)
589 .map(|(tag, _)| tag.clone())
590 .collect()
591 };
592 let mut tier1_tags = tier1(inner);
593 let mut tick = cycle;
594 for tag in &tier1_tags {
595 tick = tick.min(self.rate(inner, tag));
596 }
597 let half = tick / 2.0;
598 let boundary = inner
599 .last_cycle
600 .is_none_or(|last| now + half >= last + cycle);
601 let mut tags = Vec::new();
602 if boundary {
603 inner.last_cycle = Some(now);
604 let expired: Vec<(String, Option<u8>)> = inner
605 .grace
606 .iter()
607 .filter(|(_, (deadline, _))| *deadline <= now)
608 .map(|(tag, (_, tier))| (tag.clone(), *tier))
609 .collect();
610 for (tag, tier) in expired {
611 self.set(inner, &tag, tier);
612 }
613 tier1_tags = tier1(inner);
614 }
615 let mut groups = HashSet::new();
616 for tag in tier1_tags {
617 let rate = self.rate(inner, &tag);
618 let last = inner
619 .last_read
620 .get(&tag)
621 .copied()
622 .unwrap_or(f64::NEG_INFINITY);
623 if now + half >= last + rate {
624 inner.last_read.insert(tag.clone(), now);
625 if let Some(group) = self.group(inner, &tag) {
626 inner.group_rates.insert(group.name.clone(), group.rate_ms);
627 groups.insert(group.name);
628 }
629 tags.push(tag);
630 }
631 }
632 for name in groups {
633 let prev = inner.group_reads.get(&name).map(|r| r.1);
634 inner.group_reads.insert(name, (prev, now));
635 }
636 if boundary {
637 if inner.tier2.is_none() {
638 let mut tier2: Vec<String> = inner
640 .tiers
641 .iter()
642 .filter(|(_, t)| **t == 2)
643 .map(|(tag, _)| tag.clone())
644 .collect();
645 tier2.sort_by_key(|t| t.to_lowercase());
646 inner.tier2 = Some(tier2);
647 }
648 let tier2 = inner.tier2.clone().unwrap_or_default();
649 if !tier2.is_empty() {
650 let slices = if cycle > 0.0 {
651 ((self.options.tier2_period / cycle).round_ties_even() as usize).max(1)
652 } else {
653 1
654 };
655 let size = tier2.len().div_ceil(slices);
656 let i = inner.slice % slices;
657 inner.slice += 1;
658 let start = (i * size).min(tier2.len());
659 let end = ((i + 1) * size).min(tier2.len());
660 tags.extend_from_slice(&tier2[start..end]);
661 }
662 }
663 if !inner.new_tier2.is_empty() {
664 let have: HashSet<&String> = tags.iter().collect();
665 let mut new: Vec<String> = inner
666 .new_tier2
667 .iter()
668 .filter(|t| !have.contains(t))
669 .cloned()
670 .collect();
671 new.sort();
672 tags.extend(new);
673 inner.new_tier2.clear();
674 }
675 let tags = tags
676 .into_iter()
677 .filter(|tag| inner.missing.get(tag).is_none_or(|&at| at <= now))
678 .collect();
679 (tags, tick, boundary)
680 }
681
682 fn wait(&self, seconds: f64) -> bool {
686 #[cfg(test)]
687 if let Some(time) = self.fake_time() {
688 return time.wait(self, seconds);
689 }
690 let writes = lock(&self.writes);
691 if writes.is_empty() && !self.stop.load(Ordering::SeqCst) {
692 let _ = self
693 .wake
694 .wait_timeout(writes, Duration::from_secs_f64(seconds.max(0.0)))
695 .unwrap_or_else(PoisonError::into_inner);
696 }
697 self.stop.load(Ordering::SeqCst)
698 }
699
700 fn idle(&self, plc: &mut dyn Plc, start: f64, tick: f64) -> Result<bool, String> {
702 loop {
703 let remaining = tick - (self.clock() - start);
704 if remaining <= 1e-4 {
705 return Ok(false);
706 }
707 if self.wait(remaining) {
708 return Ok(true);
709 }
710 self.run_writes(plc)?;
711 }
712 }
713
714 fn run(&self) {
715 let mut attempt = 0;
716 while !self.stop.load(Ordering::SeqCst) {
717 match (self.factory)(&self.cfg) {
718 Ok(mut plc) => {
719 if let Err(err) = self.connected(plc.as_mut(), &mut attempt) {
720 log::warn!("{}: {err}", self.name);
721 }
722 plc.close();
723 }
724 Err(err) => log::warn!("{}: {err}", self.name),
725 }
726 if self.stop.load(Ordering::SeqCst) {
727 break;
728 }
729 self.fail_writes("PLC disconnected");
730 self.mark_stale();
731 self.set_state("disconnected");
732 let delay = self.backoff(attempt);
733 attempt += 1;
734 if self.wait(delay) {
735 break;
736 }
737 lock(&self.stats).reconnects += 1;
738 self.set_state("connecting");
739 }
740 *lock(&self.state) = "stopped"; self.fail_writes("server stopping");
742 }
743
744 fn connected(&self, plc: &mut dyn Plc, attempt: &mut usize) -> Result<(), String> {
745 plc.open()?;
746 self.fail_writes("PLC disconnected"); self.check_version(plc, true);
748 *attempt = 0;
749 self.set_state("connected");
750 self.poll(plc)
751 }
752
753 fn poll(&self, plc: &mut dyn Plc) -> Result<(), String> {
754 while !self.stop.load(Ordering::SeqCst) {
755 let start = self.clock();
756 let (tags, tick, boundary) = self.cycle_tags(&mut lock(&self.inner), start);
757 let mut services = 0;
758 if !tags.is_empty() {
759 let (results, sent) = self.read(plc, &tags, start)?;
760 services = sent;
761 self.apply(results, start, true);
762 }
763 {
764 let mut stats = lock(&self.stats);
765 stats.reads_per_cycle = services;
766 stats.tags_per_cycle = tags.len();
767 }
768 self.describe(plc);
769 if boundary {
770 self.check_version(plc, false);
771 }
772 self.run_writes(plc)?;
773 let elapsed = self.clock() - start;
774 {
775 let mut stats = lock(&self.stats);
776 stats.last_ok_ts = Some(now_ms());
777 stats.last_cycle_ms = Some(round1(elapsed * 1000.0));
778 if elapsed > tick {
779 stats.overruns += 1;
780 }
781 }
782 if elapsed <= tick && self.idle(plc, start, tick)? {
783 break;
784 }
785 }
786 self.run_writes(plc) }
788
789 fn apply(&self, results: HashMap<String, TagResult>, start: f64, deadband: bool) {
792 let t = now_ms();
793 let mut changes = Vec::new();
794 {
795 let mut inner = lock(&self.inner);
796 let mut ordered: Vec<(String, TagResult)> = results.into_iter().collect();
797 ordered.sort_by(|a, b| a.0.cmp(&b.0));
798 for (tag, result) in ordered {
799 if !inner.tiers.contains_key(&tag) {
800 continue; }
802 let old = inner.cache.get(&tag).cloned();
803 let entry = merge(old.as_ref(), &result, t);
804 if entry.quality == "missing" {
805 inner
806 .missing
807 .insert(tag.clone(), start + self.missing_retry()); } else {
809 inner.missing.remove(&tag);
810 }
811 if old.as_ref().is_none_or(|o| !o.same(&entry)) {
812 inner.cache.insert(tag.clone(), entry.clone());
813 } else if deadband {
814 continue;
815 }
816 if let Some(published) = inner.published.get(&tag) {
817 let skip = if deadband {
818 self.within_deadband(&tag, published, &entry)
819 } else {
820 published.same(&entry)
821 };
822 if skip {
823 continue;
824 }
825 }
826 inner.published.insert(tag.clone(), entry.clone());
827 changes.push((tag, entry));
828 }
829 }
830 if !changes.is_empty() {
831 self.emit(Event::Values(self.name.clone(), changes));
832 }
833 }
834
835 fn within_deadband(&self, tag: &str, published: &Entry, entry: &Entry) -> bool {
836 if published.quality != "good" || entry.quality != "good" || published.error != entry.error
837 {
838 return false;
839 }
840 let settings = self.rules.effective(&self.name, tag);
841 let band = settings
842 .get("deadband")
843 .and_then(Json::as_f64)
844 .unwrap_or(0.0);
845 match (published.value.as_f64(), entry.value.as_f64()) {
846 (Some(a), Some(b)) if truthy(settings.get("deadband")) => (b - a).abs() < band,
847 _ => false,
848 }
849 }
850
851 fn read(
854 &self,
855 plc: &mut dyn Plc,
856 tags: &[String],
857 start: f64,
858 ) -> Result<(HashMap<String, TagResult>, usize), String> {
859 let parts: Arc<Vec<Vec<Read>>> = if self.options.planner {
860 let structure = Arc::clone(
861 &self
862 .structure
863 .read()
864 .unwrap_or_else(PoisonError::into_inner),
865 );
866 let mut inner = lock(&self.inner);
867 let group_retry = self.group_retry();
868 let retry: Vec<String> = inner
869 .no_group
870 .iter()
871 .filter(|(_, at)| start - **at >= group_retry)
872 .map(|(name, _)| name.clone())
873 .collect();
874 if !retry.is_empty() {
875 for name in retry {
876 inner.no_group.remove(&name); }
878 inner.plans.clear();
879 }
880 let mut key = tags.to_vec();
881 key.sort();
882 if let Some(parts) = inner.plans.get(&key) {
883 Arc::clone(parts)
884 } else {
885 if inner.plans.len() > 100 {
886 inner.plans.clear(); }
888 let budget = match plc.connection_size() {
889 0 => planner::PACKET_BYTES,
890 n => n,
891 };
892 let no_group: HashSet<String> = inner.no_group.keys().cloned().collect();
893 let together =
894 |path: &str| truthy(self.rules.effective(&self.name, path).get("together"));
895 let reads = planner::plan(
896 tags,
897 &structure.catalog,
898 self.options.whole_max_bytes,
899 &no_group,
900 budget,
901 Some(&together),
902 );
903 let parts = Arc::new(planner::chunks(reads, &structure.catalog, budget));
904 inner.plans.insert(key, Arc::clone(&parts));
905 parts
906 }
907 } else {
908 let reads = tags
909 .iter()
910 .map(|tag| Read {
911 name: tag.clone(),
912 members: vec![(tag.clone(), None)],
913 })
914 .collect();
915 Arc::new(vec![reads])
916 };
917 let mut between =
918 |plc: &mut dyn Plc, partial: HashMap<String, TagResult>| -> Result<(), String> {
919 self.apply(partial, start, true);
920 self.run_writes(plc)
921 };
922 let (results, failed, services) = planner::execute(plc, &parts, &mut between)?;
923 if !failed.is_empty() {
924 lock(&self.stats).group_fallbacks += failed.len() as u64;
925 log::warn!(
926 "{}: whole read of {} failed, reading the members instead",
927 self.name,
928 failed.join(", ")
929 );
930 let mut inner = lock(&self.inner);
931 for name in failed {
932 inner.no_group.insert(name.to_lowercase(), start);
933 }
934 inner.plans.clear();
935 }
936 Ok((results, services))
937 }
938
939 fn run_writes(&self, plc: &mut dyn Plc) -> Result<(), String> {
940 loop {
941 let Some(request) = lock(&self.writes).pop_front() else {
942 return Ok(());
943 };
944 let WriteRequest { tag, value, done } = request;
945 let result = match plc.write(&tag, &value) {
946 Ok(result) => result,
947 Err(err) => {
948 done(WriteResult::failed(err.clone(), "comm"));
949 return Err(err);
950 }
951 };
952 if let Some(error) = result.error {
953 done(WriteResult::failed(error.message, error.kind.as_str()));
954 continue;
955 }
956 let mut names = vec![tag.clone()];
957 if let Some((word, _)) = split_bit(&tag) {
958 names.push(word.to_owned());
959 }
960 let refs: Vec<&str> = names.iter().map(String::as_str).collect();
961 let results = match plc.read(&refs) {
962 Ok(results) => results,
963 Err(err) => {
964 done(WriteResult {
965 ok: true,
966 error: None,
967 kind: None,
968 value: Json::Null,
969 });
970 return Err(err);
971 }
972 };
973 let results: HashMap<String, TagResult> = names.into_iter().zip(results).collect();
974 let value = results.get(&tag).map_or(Json::Null, |r| {
975 let (value, quality, _) = to_value(r);
976 if quality == "good" { value } else { Json::Null }
977 });
978 done(WriteResult {
979 ok: true,
980 error: None,
981 kind: None,
982 value,
983 });
984 self.apply(results, self.clock(), false);
985 }
986 }
987
988 fn describe(&self, plc: &mut dyn Plc) {
990 let mut tags: Vec<String> = {
991 let mut inner = lock(&self.inner);
992 inner.want_descriptions.drain().collect()
993 };
994 tags.sort();
995 tags.retain(|t| !truthy(self.rules.effective(&self.name, t).get("description")));
997 {
998 let known = lock(&self.descriptions);
999 tags.retain(|t| !known.contains_key(t));
1000 }
1001 if tags.is_empty() || !plc.describes() {
1002 return; }
1004 let refs: Vec<&str> = tags.iter().map(String::as_str).collect();
1005 let found = match plc.descriptions(&refs) {
1006 Ok(found) => found,
1007 Err(err) => {
1008 log::info!("{}: descriptions not read: {err}", self.name);
1009 vec![None; tags.len()]
1010 }
1011 };
1012 {
1013 let mut known = lock(&self.descriptions);
1014 for (tag, description) in tags.iter().zip(found) {
1015 known.insert(tag.clone(), description);
1016 }
1017 }
1018 self.emit(Event::Info(self.name.clone(), tags));
1019 }
1020
1021 fn check_version(&self, plc: &mut dyn Plc, connecting: bool) {
1023 let version = plc.version();
1024 let (changed, reloaded) = {
1025 let mut current = lock(&self.version);
1026 let changed = version != *current;
1027 let reloaded = changed && !current.is_null();
1028 *current = version.clone();
1029 (changed, reloaded)
1030 };
1031 if reloaded {
1032 lock(&self.stats).reloads += 1;
1033 log::info!("{}: tag table reloaded (version {version})", self.name);
1034 }
1035 if changed || connecting {
1036 let catalog = plc.catalog();
1037 let tag_list = catalog.tag_list();
1038 let types = catalog.type_map();
1039 self.rules.set_types(&self.name, &tag_list, &types); *self
1041 .structure
1042 .write()
1043 .unwrap_or_else(PoisonError::into_inner) = Arc::new(Structure {
1044 catalog,
1045 tag_list,
1046 types,
1047 });
1048 let mut inner = lock(&self.inner);
1049 inner.groups.clear();
1050 inner.missing.clear(); inner.no_group.clear();
1052 inner.plans.clear(); if changed {
1054 lock(&self.descriptions).clear();
1056 let tags: Vec<String> = inner
1057 .tiers
1058 .keys()
1059 .filter(|t| split_bit(t).is_none())
1060 .cloned()
1061 .collect();
1062 inner.want_descriptions.extend(tags);
1063 }
1064 }
1065 if reloaded {
1066 self.emit(Event::Meta(self.name.clone()));
1067 }
1068 }
1069
1070 fn fail_writes(&self, error: &str) {
1071 loop {
1072 let Some(request) = lock(&self.writes).pop_front() else {
1073 return;
1074 };
1075 (request.done)(WriteResult::failed(error, "comm"));
1076 }
1077 }
1078
1079 fn mark_stale(&self) {
1080 let mut changes = Vec::new();
1081 {
1082 let mut inner = lock(&self.inner);
1083 let Inner {
1084 cache, published, ..
1085 } = &mut *inner;
1086 for (tag, entry) in cache.iter_mut() {
1087 if entry.quality != "stale" {
1088 entry.quality = "stale";
1089 entry.error = Json::Null;
1090 published.insert(tag.clone(), entry.clone());
1091 changes.push((tag.clone(), entry.clone()));
1092 }
1093 }
1094 }
1095 if !changes.is_empty() {
1096 changes.sort_by(|a, b| a.0.cmp(&b.0));
1097 self.emit(Event::Values(self.name.clone(), changes));
1098 }
1099 }
1100}
1101
1102#[cfg(test)]
1103#[path = "worker_tests.rs"]
1104mod tests;