1use std::collections::{BTreeMap, HashMap, HashSet};
24use std::path::PathBuf;
25use std::sync::atomic::{AtomicBool, Ordering};
26use std::sync::Arc;
27use std::time::Duration;
28
29use crate::defaults::{config_paths, Defaults};
30use crate::msg_tree::{MsgTree, MsgTreeMode};
31use crate::node_set::NodeSet;
32use crate::propagation::PropagationTreeRouter;
33use crate::topology::TopologyParser;
34use crate::worker::exec::ExecWorker;
35use crate::worker::{EventHandler, Worker, WorkerError, WorkerState};
36
37#[derive(Debug, thiserror::Error)]
39pub enum TaskError {
40 #[error("worker error: {0}")]
42 Worker(#[from] WorkerError),
43 #[error("task timeout")]
45 Timeout,
46 #[error("task is already running")]
48 AlreadyRunning,
49 #[error("{0}_msgtree not set")]
51 MsgTreeDisabled(String),
52 #[error("topology error: {0}")]
54 Topology(String),
55 #[error("task error: {0}")]
57 General(String),
58}
59
60pub type Result<T> = std::result::Result<T, TaskError>;
62
63pub type Source = (usize, String);
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum TaskState {
72 Idle,
74 Running,
76 Done,
78 Aborted,
80}
81
82#[derive(Debug, Clone)]
86pub struct TaskDefaults {
87 pub stderr: bool,
89 pub stdin: bool,
91 pub stdout_msgtree: bool,
93 pub stderr_msgtree: bool,
95 pub auto_tree: bool,
98}
99
100impl Default for TaskDefaults {
101 fn default() -> Self {
102 Self {
103 stderr: false,
104 stdin: true,
105 stdout_msgtree: true,
106 stderr_msgtree: true,
107 auto_tree: true,
108 }
109 }
110}
111
112impl TaskDefaults {
113 pub fn from_defaults(defaults: &Defaults) -> Self {
115 Self {
116 stderr: defaults.stderr(),
117 stdin: defaults.stdin(),
118 stdout_msgtree: true,
119 stderr_msgtree: true,
120 auto_tree: defaults.get("auto_tree").map_or(true, |v| v.as_bool()),
121 }
122 }
123}
124
125#[derive(Debug, Clone)]
129pub struct TaskInfo {
130 pub debug: bool,
132 pub fanout: usize,
134 pub grooming_delay: f64,
137 pub connect_timeout: f64,
139 pub command_timeout: f64,
141}
142
143impl Default for TaskInfo {
144 fn default() -> Self {
145 Self {
146 debug: false,
147 fanout: 64,
148 grooming_delay: 0.25,
149 connect_timeout: 10.0,
150 command_timeout: 0.0,
151 }
152 }
153}
154
155impl TaskInfo {
156 pub fn from_defaults(defaults: &Defaults) -> Self {
158 Self {
159 debug: false,
160 fanout: defaults.fanout() as usize,
161 grooming_delay: defaults.grooming_delay(),
162 connect_timeout: defaults.connect_timeout(),
163 command_timeout: defaults.command_timeout(),
164 }
165 }
166}
167
168fn topology_configs() -> Vec<PathBuf> {
170 config_paths("topology.conf")
171}
172
173fn get_short_hostname() -> String {
175 hostname::get()
176 .map(|h: std::ffi::OsString| {
177 h.to_string_lossy()
178 .to_string()
179 .split('.')
180 .next()
181 .unwrap_or("localhost")
182 .to_string()
183 })
184 .unwrap_or_else(|_| "localhost".to_string())
185}
186
187struct ManagedWorker {
189 worker: Box<dyn Worker>,
191 update_task_rc: bool,
193 autoclose: bool,
195}
196
197struct TaskGatheringHandler {
203 #[allow(dead_code)]
205 worker_id: usize,
206 stdout: HashMap<String, Vec<Vec<u8>>>,
208 stderr: HashMap<String, Vec<Vec<u8>>>,
210 retcodes: HashMap<String, i32>,
212 timeouts: HashSet<String>,
214 user_handler: Option<Box<dyn EventHandler>>,
216}
217
218impl TaskGatheringHandler {
219 fn new(worker_id: usize, user_handler: Option<Box<dyn EventHandler>>) -> Self {
220 Self {
221 worker_id,
222 stdout: HashMap::new(),
223 stderr: HashMap::new(),
224 retcodes: HashMap::new(),
225 timeouts: HashSet::new(),
226 user_handler,
227 }
228 }
229}
230
231impl EventHandler for TaskGatheringHandler {
232 fn on_start(&mut self, worker: &dyn Worker) {
233 if let Some(ref mut h) = self.user_handler {
234 h.on_start(worker);
235 }
236 }
237
238 fn on_read(&mut self, node: &str, fd: std::os::unix::io::RawFd, msg: &[u8]) {
239 let map = if fd == 2 {
241 &mut self.stderr
242 } else {
243 &mut self.stdout
244 };
245 map.entry(node.to_string()).or_default().push(msg.to_vec());
246
247 if let Some(ref mut h) = self.user_handler {
248 h.on_read(node, fd, msg);
249 }
250 }
251
252 fn on_close(&mut self, node: &str, rc: i32) {
253 self.retcodes.insert(node.to_string(), rc);
254
255 if let Some(ref mut h) = self.user_handler {
256 h.on_close(node, rc);
257 }
258 }
259
260 fn on_timeout(&mut self, node: &str) {
261 self.timeouts.insert(node.to_string());
262
263 if let Some(ref mut h) = self.user_handler {
264 h.on_timeout(node);
265 }
266 }
267
268 fn on_error(&mut self, node: &str, error: &WorkerError) {
269 if let Some(ref mut h) = self.user_handler {
270 h.on_error(node, error);
271 }
272 }
273
274 fn take_buffers(
275 &mut self,
276 ) -> (
277 HashMap<String, Vec<Vec<u8>>>,
278 HashMap<String, Vec<Vec<u8>>>,
279 HashSet<String>,
280 ) {
281 (
282 std::mem::take(&mut self.stdout),
283 std::mem::take(&mut self.stderr),
284 std::mem::take(&mut self.timeouts),
285 )
286 }
287}
288
289#[derive(Debug, Clone)]
297pub struct TaskAbortHandle {
298 quit: Arc<AtomicBool>,
300 kill: Arc<AtomicBool>,
302}
303
304impl TaskAbortHandle {
305 pub fn abort(&self, kill: bool) {
311 if kill {
312 self.kill.store(true, Ordering::SeqCst);
313 }
314 self.quit.store(true, Ordering::SeqCst);
315 }
316}
317
318pub struct Task {
331 state: TaskState,
333 defaults: TaskDefaults,
335 info: TaskInfo,
337 timeout: Option<Duration>,
339
340 quit: Arc<AtomicBool>,
344 kill_requested: Arc<AtomicBool>,
347 terminated_kill: bool,
352
353 next_worker_id: usize,
356 workers: Vec<(usize, ManagedWorker)>,
358
359 stdout_tree: Option<MsgTree<Source>>,
362 stderr_tree: Option<MsgTree<Source>>,
364 d_source_rc: HashMap<Source, i32>,
366 d_rc_sources: BTreeMap<i32, HashSet<Source>>,
368 max_rc: Option<i32>,
370 timeout_sources: HashSet<Source>,
372
373 topology_parser: Option<TopologyParser>,
376 router: Option<PropagationTreeRouter>,
378 #[allow(dead_code)]
380 gateways: HashMap<String, usize>,
381}
382
383impl Task {
384 pub fn new() -> Self {
386 let defaults = TaskDefaults::default();
387 let info = TaskInfo::default();
388 Self::with_config(defaults, info)
389 }
390
391 pub fn from_defaults(defaults: &Defaults) -> Self {
393 let task_defaults = TaskDefaults::from_defaults(defaults);
394 let task_info = TaskInfo::from_defaults(defaults);
395 Self::with_config(task_defaults, task_info)
396 }
397
398 pub fn with_config(defaults: TaskDefaults, info: TaskInfo) -> Self {
400 let stdout_tree = if defaults.stdout_msgtree {
401 Some(MsgTree::new(MsgTreeMode::Shift))
402 } else {
403 None
404 };
405 let stderr_tree = if defaults.stderr_msgtree {
406 Some(MsgTree::new(MsgTreeMode::Shift))
407 } else {
408 None
409 };
410
411 Self {
412 state: TaskState::Idle,
413 defaults,
414 info,
415 timeout: None,
416 quit: Arc::new(AtomicBool::new(false)),
417 kill_requested: Arc::new(AtomicBool::new(false)),
418 terminated_kill: false,
419 next_worker_id: 0,
420 workers: Vec::new(),
421 stdout_tree,
422 stderr_tree,
423 d_source_rc: HashMap::new(),
424 d_rc_sources: BTreeMap::new(),
425 max_rc: None,
426 timeout_sources: HashSet::new(),
427 topology_parser: None,
428 router: None,
429 gateways: HashMap::new(),
430 }
431 }
432
433 pub fn defaults(&self) -> &TaskDefaults {
439 &self.defaults
440 }
441
442 pub fn defaults_mut(&mut self) -> &mut TaskDefaults {
444 &mut self.defaults
445 }
446
447 pub fn info(&self) -> &TaskInfo {
449 &self.info
450 }
451
452 pub fn info_mut(&mut self) -> &mut TaskInfo {
454 &mut self.info
455 }
456
457 pub fn set_fanout(&mut self, fanout: usize) {
459 self.info.fanout = fanout;
460 }
461
462 pub fn fanout(&self) -> usize {
464 self.info.fanout
465 }
466
467 pub fn load_topology(&mut self, path: &str) -> Result<()> {
475 let hostname = get_short_hostname();
476 let mut parser = TopologyParser::new();
477 parser
478 .load(path)
479 .map_err(|e| TaskError::Topology(format!("failed to load topology: {}", e)))?;
480 parser
482 .tree(&hostname)
483 .map_err(|e| TaskError::Topology(format!("failed to build tree: {}", e)))?;
484 self.topology_parser = Some(parser);
485 Ok(())
486 }
487
488 fn try_load_default_topology(&mut self) -> bool {
490 if self.topology_parser.is_some() {
491 return true;
492 }
493 let configs = topology_configs();
494 for path in configs.iter().rev() {
495 if path.exists() {
496 if let Ok(()) = self.load_topology(&path.to_string_lossy()) {
497 return true;
498 }
499 }
500 }
501 false
502 }
503
504 pub fn default_tree_is_enabled(&mut self) -> bool {
506 if self.topology_parser.is_none() {
507 self.try_load_default_topology();
508 }
509 self.topology_parser.is_some() && self.defaults.auto_tree
510 }
511
512 pub fn default_router(&mut self) -> Result<&PropagationTreeRouter> {
514 if self.router.is_none() {
515 let hostname = get_short_hostname();
516 let parser = self
517 .topology_parser
518 .as_mut()
519 .ok_or_else(|| TaskError::Topology("no topology loaded".to_string()))?;
520 let topology = parser
521 .tree(&hostname)
522 .map_err(|e| TaskError::Topology(format!("failed to build tree: {}", e)))?;
523 let root_ns = topology
524 .root()
525 .ok_or_else(|| TaskError::Topology("topology has no root".to_string()))?;
526 let root_str = root_ns.nodeset.to_string();
527 let fanout = self.info.fanout;
528 let router = PropagationTreeRouter::new(&root_str, topology, fanout)
529 .map_err(|e| TaskError::Topology(format!("router creation failed: {}", e)))?;
530 self.router = Some(router);
531 }
532 Ok(self.router.as_ref().unwrap())
533 }
534
535 pub fn has_topology(&self) -> bool {
537 self.topology_parser.is_some()
538 }
539
540 pub fn shell(
551 &mut self,
552 command: &str,
553 nodes: Option<&str>,
554 handler: Option<Box<dyn EventHandler>>,
555 ) -> usize {
556 self.shell_with_opts(command, nodes, handler, None, false)
557 }
558
559 pub fn shell_with_opts(
563 &mut self,
564 command: &str,
565 nodes: Option<&str>,
566 handler: Option<Box<dyn EventHandler>>,
567 timeout: Option<Duration>,
568 autoclose: bool,
569 ) -> usize {
570 let node_list: Vec<String> = match nodes {
571 Some(ns) => {
572 match NodeSet::parse(ns) {
574 Ok(nodeset) => nodeset.iter().collect::<Vec<String>>(),
575 Err(_) => {
576 vec![ns.to_string()]
578 }
579 }
580 }
581 None => {
582 vec!["localhost".to_string()]
584 }
585 };
586
587 let fanout = self.info.fanout;
588 let worker_timeout = timeout.or_else(|| {
589 if self.info.command_timeout > 0.0 {
590 Some(Duration::from_secs_f64(self.info.command_timeout))
591 } else {
592 None
593 }
594 });
595
596 let mut worker = ExecWorker::new(node_list, command.to_string(), fanout, worker_timeout);
597 worker = worker.with_stderr(self.defaults.stderr);
598
599 self.schedule(Box::new(worker), handler, autoclose)
600 }
601
602 pub fn schedule(
607 &mut self,
608 worker: Box<dyn Worker>,
609 handler: Option<Box<dyn EventHandler>>,
610 autoclose: bool,
611 ) -> usize {
612 let worker_id = self.next_worker_id;
613 self.next_worker_id += 1;
614
615 let gathering = TaskGatheringHandler::new(worker_id, handler);
617 let mut managed_worker = ManagedWorker {
618 worker,
619 update_task_rc: true,
620 autoclose,
621 };
622 managed_worker.worker.set_handler(Box::new(gathering));
623
624 self.workers.push((worker_id, managed_worker));
625 worker_id
626 }
627
628 pub fn run(&mut self, timeout: Option<Duration>) -> Result<()> {
638 self.timeout = timeout;
639 self.resume()
640 }
641
642 pub fn run_command(
644 &mut self,
645 command: &str,
646 nodes: Option<&str>,
647 handler: Option<Box<dyn EventHandler>>,
648 timeout: Option<Duration>,
649 ) -> Result<usize> {
650 let wid = self.shell(command, nodes, handler);
651 self.timeout = timeout;
652 self.resume()?;
653 Ok(wid)
654 }
655
656 pub fn resume(&mut self) -> Result<()> {
658 if self.state == TaskState::Running {
659 return Err(TaskError::AlreadyRunning);
660 }
661
662 self.reset();
663 self.state = TaskState::Running;
664
665 let mut _running_count = 0;
667 let mut errors: Vec<TaskError> = Vec::new();
668
669 for (_wid, mw) in &mut self.workers {
671 if mw.worker.state() == WorkerState::Pending {
672 match mw.worker.start() {
673 Ok(()) => _running_count += 1,
674 Err(e) => errors.push(TaskError::Worker(e)),
675 }
676 }
677 }
678
679 let start = std::time::Instant::now();
681 loop {
682 if self.quit.load(Ordering::SeqCst) {
687 let kill = self.kill_requested.load(Ordering::SeqCst);
688 self.terminate(kill);
689 return Ok(());
690 }
691
692 let mut any_running = false;
693 for (_wid, mw) in &mut self.workers {
694 if !mw.autoclose && !mw.worker.is_done() {
695 let fds = mw.worker.read_fds();
697 for fd in &fds {
698 let _ = mw.worker.handle_read(*fd);
699 }
700 if !mw.worker.is_done() {
701 any_running = true;
702 }
703 }
704 }
705 if !any_running {
706 break;
707 }
708
709 if let Some(timeout) = self.timeout {
711 if start.elapsed() >= timeout {
712 for (_wid, mw) in &mut self.workers {
714 if !mw.worker.is_done() {
715 mw.worker.abort(true);
716 }
717 }
718 self.state = TaskState::Done;
719 return Err(TaskError::Timeout);
720 }
721 }
722
723 std::thread::sleep(Duration::from_millis(10));
725 }
726
727 self.collect_results();
729
730 self.state = TaskState::Done;
731
732 if let Some(err) = errors.into_iter().next() {
733 Err(err)
734 } else {
735 Ok(())
736 }
737 }
738
739 pub fn abort(&mut self, kill: bool) {
748 if kill {
751 self.kill_requested.store(true, Ordering::SeqCst);
752 }
753 self.quit.store(true, Ordering::SeqCst);
754 self.terminate(kill);
755 }
756
757 pub fn abort_handle(&self) -> TaskAbortHandle {
763 TaskAbortHandle {
764 quit: Arc::clone(&self.quit),
765 kill: Arc::clone(&self.kill_requested),
766 }
767 }
768
769 fn terminate(&mut self, kill: bool) {
776 if self.terminated_kill {
778 return;
779 }
780
781 for (_wid, mw) in &mut self.workers {
783 if !mw.worker.is_done() {
784 mw.worker.abort(kill);
785 }
786 }
787
788 if kill {
789 self.terminated_kill = true;
791 }
792
793 self.reset();
795
796 self.state = TaskState::Aborted;
797 }
798
799 pub fn running(&self) -> bool {
801 self.state == TaskState::Running
802 }
803
804 pub fn state(&self) -> TaskState {
806 self.state
807 }
808
809 fn reset(&mut self) {
815 if let Some(ref mut tree) = self.stdout_tree {
816 tree.clear();
817 }
818 if let Some(ref mut tree) = self.stderr_tree {
819 tree.clear();
820 }
821 self.d_source_rc.clear();
822 self.d_rc_sources.clear();
823 self.max_rc = None;
824 self.timeout_sources.clear();
825 }
826
827 fn collect_results(&mut self) {
829 for (wid, mw) in &mut self.workers {
830 let worker_id = *wid;
831
832 if mw.update_task_rc {
834 for (node, &rc) in mw.worker.retcodes() {
835 let source: Source = (worker_id, node.clone());
836 self.d_source_rc.insert(source.clone(), rc);
837 self.d_rc_sources.entry(rc).or_default().insert(source);
838
839 if self.max_rc.map_or(true, |max| rc > max) {
840 self.max_rc = Some(rc);
841 }
842 }
843 }
844
845 if let Some(mut handler) = mw.worker.take_handler() {
849 let (stdout, stderr, timeouts) = handler.take_buffers();
850
851 if let Some(ref mut tree) = self.stdout_tree {
853 for (node, chunks) in stdout {
854 let source: Source = (worker_id, node);
855 for chunk in chunks {
856 tree.add(source.clone(), chunk);
857 }
858 }
859 }
860
861 if let Some(ref mut tree) = self.stderr_tree {
863 for (node, chunks) in stderr {
864 let source: Source = (worker_id, node);
865 for chunk in chunks {
866 tree.add(source.clone(), chunk);
867 }
868 }
869 }
870
871 for node in timeouts {
873 self.timeout_sources.insert((worker_id, node));
874 }
875 }
876 }
877 }
878
879 pub fn max_retcode(&self) -> Option<i32> {
887 self.max_rc
888 }
889
890 pub fn node_retcode(&self, node: &str) -> Option<i32> {
895 let mut max_rc: Option<i32> = None;
896 for ((_, n), &rc) in &self.d_source_rc {
897 if n == node {
898 max_rc = Some(max_rc.map_or(rc, |m: i32| m.max(rc)));
899 }
900 }
901 max_rc
902 }
903
904 pub fn iter_retcodes(&self, match_keys: Option<&HashSet<String>>) -> Vec<(i32, Vec<String>)> {
909 let mut result = Vec::new();
910 for (&rc, sources) in &self.d_rc_sources {
911 let nodes: Vec<String> = sources
912 .iter()
913 .filter(|(_, n)| match_keys.map_or(true, |keys| keys.contains(n)))
914 .map(|(_, n)| n.clone())
915 .collect();
916 if !nodes.is_empty() {
917 result.push((rc, nodes));
918 }
919 }
920 result
921 }
922
923 pub fn iter_retcodes_by_worker(
925 &self,
926 worker_id: usize,
927 match_keys: Option<&HashSet<String>>,
928 ) -> Vec<(i32, Vec<String>)> {
929 let mut result = Vec::new();
930 for (&rc, sources) in &self.d_rc_sources {
931 let nodes: Vec<String> = sources
932 .iter()
933 .filter(|(wid, n)| {
934 *wid == worker_id && match_keys.map_or(true, |keys| keys.contains(n))
935 })
936 .map(|(_, n)| n.clone())
937 .collect();
938 if !nodes.is_empty() {
939 result.push((rc, nodes));
940 }
941 }
942 result
943 }
944
945 pub fn num_timeout(&self) -> usize {
947 self.timeout_sources.len()
948 }
949
950 pub fn iter_keys_timeout(&self) -> impl Iterator<Item = &str> {
952 self.timeout_sources.iter().map(|(_, n)| n.as_str())
953 }
954
955 pub fn num_timeout_by_worker(&self, worker_id: usize) -> usize {
957 self.timeout_sources
958 .iter()
959 .filter(|(wid, _)| *wid == worker_id)
960 .count()
961 }
962
963 pub fn iter_keys_timeout_by_worker(&self, worker_id: usize) -> impl Iterator<Item = &str> {
965 self.timeout_sources
966 .iter()
967 .filter(move |(wid, _)| *wid == worker_id)
968 .map(|(_, n)| n.as_str())
969 }
970
971 pub fn node_buffer(&self, node: &str) -> Result<Vec<u8>> {
979 let tree = self
980 .stdout_tree
981 .as_ref()
982 .ok_or_else(|| TaskError::MsgTreeDisabled("stdout".to_string()))?;
983 let mut buf = Vec::new();
984 for key in tree.keys() {
985 if key.1 == node {
986 if let Some(elem) = tree.get(key) {
987 let msg = elem.message();
988 if !buf.is_empty() {
989 buf.push(b'\n');
990 }
991 buf.extend_from_slice(&msg);
992 }
993 }
994 }
995 Ok(buf)
996 }
997
998 pub fn node_error(&self, node: &str) -> Result<Vec<u8>> {
1000 let tree = self
1001 .stderr_tree
1002 .as_ref()
1003 .ok_or_else(|| TaskError::MsgTreeDisabled("stderr".to_string()))?;
1004 let mut buf = Vec::new();
1005 for key in tree.keys() {
1006 if key.1 == node {
1007 if let Some(elem) = tree.get(key) {
1008 let msg = elem.message();
1009 if !buf.is_empty() {
1010 buf.push(b'\n');
1011 }
1012 buf.extend_from_slice(&msg);
1013 }
1014 }
1015 }
1016 Ok(buf)
1017 }
1018
1019 pub fn flush_buffers(&mut self) {
1021 if let Some(ref mut tree) = self.stdout_tree {
1022 tree.clear();
1023 }
1024 }
1025
1026 pub fn flush_errors(&mut self) {
1028 if let Some(ref mut tree) = self.stderr_tree {
1029 tree.clear();
1030 }
1031 }
1032
1033 pub fn num_workers(&self) -> usize {
1039 self.workers.len()
1040 }
1041
1042 pub fn worker(&self, worker_id: usize) -> Option<&dyn Worker> {
1044 self.workers
1045 .iter()
1046 .find(|(wid, _)| *wid == worker_id)
1047 .map(|(_, mw)| mw.worker.as_ref())
1048 }
1049
1050 pub fn worker_retcodes(&self, worker_id: usize) -> Option<&HashMap<String, i32>> {
1052 self.workers
1053 .iter()
1054 .find(|(wid, _)| *wid == worker_id)
1055 .map(|(_, mw)| mw.worker.retcodes())
1056 }
1057}
1058
1059impl Default for Task {
1060 fn default() -> Self {
1061 Self::new()
1062 }
1063}
1064
1065#[cfg(test)]
1070mod tests {
1071 use super::*;
1072 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1073 use std::sync::Arc;
1074
1075 #[test]
1078 fn test_task_state_variants() {
1079 assert_eq!(TaskState::Idle, TaskState::Idle);
1080 assert_eq!(TaskState::Running, TaskState::Running);
1081 assert_eq!(TaskState::Done, TaskState::Done);
1082 assert_eq!(TaskState::Aborted, TaskState::Aborted);
1083 assert_ne!(TaskState::Idle, TaskState::Running);
1084 }
1085
1086 #[test]
1087 fn test_task_state_debug() {
1088 assert_eq!(format!("{:?}", TaskState::Idle), "Idle");
1089 assert_eq!(format!("{:?}", TaskState::Running), "Running");
1090 assert_eq!(format!("{:?}", TaskState::Done), "Done");
1091 assert_eq!(format!("{:?}", TaskState::Aborted), "Aborted");
1092 }
1093
1094 #[test]
1095 fn test_task_state_clone_copy() {
1096 let s = TaskState::Running;
1097 let s2 = s;
1098 assert_eq!(s, s2);
1099 }
1100
1101 #[test]
1104 fn test_task_error_display() {
1105 let e = TaskError::Timeout;
1106 assert_eq!(e.to_string(), "task timeout");
1107
1108 let e = TaskError::AlreadyRunning;
1109 assert_eq!(e.to_string(), "task is already running");
1110
1111 let e = TaskError::MsgTreeDisabled("stdout".to_string());
1112 assert_eq!(e.to_string(), "stdout_msgtree not set");
1113
1114 let e = TaskError::General("test error".to_string());
1115 assert_eq!(e.to_string(), "task error: test error");
1116
1117 let e = TaskError::Topology("no topology".to_string());
1118 assert_eq!(e.to_string(), "topology error: no topology");
1119 }
1120
1121 #[test]
1122 fn test_task_error_from_worker_error() {
1123 let we = WorkerError::Timeout;
1124 let te: TaskError = we.into();
1125 assert!(matches!(te, TaskError::Worker(WorkerError::Timeout)));
1126 }
1127
1128 #[test]
1131 fn test_task_defaults_default() {
1132 let d = TaskDefaults::default();
1133 assert!(!d.stderr);
1134 assert!(d.stdin);
1135 assert!(d.stdout_msgtree);
1136 assert!(d.stderr_msgtree);
1137 assert!(d.auto_tree);
1139 }
1140
1141 #[test]
1142 fn test_task_defaults_from_defaults() {
1143 let defaults = Defaults::new();
1144 let d = TaskDefaults::from_defaults(&defaults);
1145 assert!(!d.stderr);
1146 assert!(d.stdin);
1147 assert!(d.auto_tree);
1148 }
1149
1150 #[test]
1153 fn test_task_info_default() {
1154 let info = TaskInfo::default();
1155 assert!(!info.debug);
1156 assert_eq!(info.fanout, 64);
1157 assert_eq!(info.grooming_delay, 0.25);
1159 assert_eq!(info.connect_timeout, 10.0);
1160 assert_eq!(info.command_timeout, 0.0);
1161 }
1162
1163 #[test]
1164 fn test_task_info_from_defaults() {
1165 let defaults = Defaults::new();
1166 let info = TaskInfo::from_defaults(&defaults);
1167 assert_eq!(info.fanout, defaults.fanout() as usize);
1168 assert_eq!(info.grooming_delay, defaults.grooming_delay());
1169 assert_eq!(info.connect_timeout, defaults.connect_timeout());
1170 assert_eq!(info.command_timeout, defaults.command_timeout());
1171 }
1172
1173 #[test]
1176 fn test_task_new() {
1177 let task = Task::new();
1178 assert_eq!(task.state(), TaskState::Idle);
1179 assert_eq!(task.num_workers(), 0);
1180 assert_eq!(task.max_retcode(), None);
1181 assert_eq!(task.num_timeout(), 0);
1182 assert!(!task.running());
1183 }
1184
1185 #[test]
1186 fn test_task_default() {
1187 let task = Task::default();
1188 assert_eq!(task.state(), TaskState::Idle);
1189 }
1190
1191 #[test]
1192 fn test_task_from_defaults() {
1193 let defaults = Defaults::new();
1194 let task = Task::from_defaults(&defaults);
1195 assert_eq!(task.state(), TaskState::Idle);
1196 assert_eq!(task.fanout(), defaults.fanout() as usize);
1197 }
1198
1199 #[test]
1200 fn test_task_with_config() {
1201 let mut td = TaskDefaults::default();
1202 td.stderr = true;
1203 td.stdout_msgtree = false;
1204
1205 let mut ti = TaskInfo::default();
1206 ti.fanout = 32;
1207
1208 let task = Task::with_config(td, ti);
1209 assert!(task.defaults().stderr);
1210 assert!(!task.defaults().stdout_msgtree);
1211 assert_eq!(task.fanout(), 32);
1212 assert!(task.stdout_tree.is_none());
1214 assert!(task.stderr_tree.is_some());
1215 }
1216
1217 #[test]
1220 fn test_task_set_fanout() {
1221 let mut task = Task::new();
1222 assert_eq!(task.fanout(), 64);
1223 task.set_fanout(128);
1224 assert_eq!(task.fanout(), 128);
1225 }
1226
1227 #[test]
1230 fn test_task_shell_local() {
1231 let mut task = Task::new();
1232 let wid = task.shell("echo hello", None, None);
1233 assert_eq!(wid, 0);
1234 assert_eq!(task.num_workers(), 1);
1235 }
1236
1237 #[test]
1238 fn test_task_shell_remote() {
1239 let mut task = Task::new();
1240 let wid = task.shell("hostname", Some("node[1-3]"), None);
1241 assert_eq!(wid, 0);
1242 assert_eq!(task.num_workers(), 1);
1243 }
1244
1245 #[test]
1246 fn test_task_shell_multiple() {
1247 let mut task = Task::new();
1248 let wid1 = task.shell("echo 1", None, None);
1249 let wid2 = task.shell("echo 2", None, None);
1250 assert_eq!(wid1, 0);
1251 assert_eq!(wid2, 1);
1252 assert_eq!(task.num_workers(), 2);
1253 }
1254
1255 #[test]
1256 fn test_task_shell_with_opts() {
1257 let mut task = Task::new();
1258 let wid = task.shell_with_opts(
1259 "echo test",
1260 Some("node[1-5]"),
1261 None,
1262 Some(Duration::from_secs(30)),
1263 true,
1264 );
1265 assert_eq!(wid, 0);
1266 assert_eq!(task.num_workers(), 1);
1267 }
1268
1269 #[test]
1272 fn test_task_run_local_command() {
1273 let mut task = Task::new();
1274 task.shell("echo hello", None, None);
1275 let result = task.run(None);
1276 assert!(result.is_ok());
1277 assert_eq!(task.state(), TaskState::Done);
1278 }
1279
1280 #[test]
1281 fn test_task_run_returns_retcode() {
1282 let mut task = Task::new();
1283 task.shell("true", None, None);
1284 task.run(None).unwrap();
1285 assert_eq!(task.max_retcode(), Some(0));
1286 }
1287
1288 #[test]
1289 fn test_task_run_failing_command() {
1290 let mut task = Task::new();
1291 task.shell("false", None, None);
1292 task.run(None).unwrap();
1293 assert_eq!(task.max_retcode(), Some(1));
1294 }
1295
1296 #[test]
1297 fn test_task_run_mixed_retcodes() {
1298 let mut task = Task::new();
1299 task.shell("true", None, None);
1300 task.shell("exit 42", None, None);
1301 task.run(None).unwrap();
1302 assert_eq!(task.max_retcode(), Some(42));
1303 }
1304
1305 #[test]
1306 fn test_task_run_command_convenience() {
1307 let mut task = Task::new();
1308 let wid = task.run_command("echo test", None, None, None).unwrap();
1309 assert_eq!(wid, 0);
1310 assert_eq!(task.max_retcode(), Some(0));
1311 }
1312
1313 #[test]
1314 fn test_task_run_multiple_nodes() {
1315 let mut task = Task::new();
1316 task.shell("echo hi", Some("node[1-3]"), None);
1317 task.run(None).unwrap();
1318 assert_eq!(task.max_retcode(), Some(0));
1320
1321 let retcodes = task.iter_retcodes(None);
1322 for (rc, _nodes) in &retcodes {
1324 assert_eq!(*rc, 0);
1325 }
1326 }
1327
1328 #[test]
1331 fn test_task_node_retcode() {
1332 let mut task = Task::new();
1333 task.shell("true", None, None);
1334 task.run(None).unwrap();
1335 assert_eq!(task.node_retcode("localhost"), Some(0));
1337 assert_eq!(task.node_retcode("nonexistent"), None);
1338 }
1339
1340 #[test]
1341 fn test_task_iter_retcodes_with_filter() {
1342 let mut task = Task::new();
1343 task.shell("echo hi", Some("node[1-3]"), None);
1344 task.run(None).unwrap();
1345
1346 let mut filter = HashSet::new();
1347 filter.insert("node1".to_string());
1348 let retcodes = task.iter_retcodes(Some(&filter));
1349 for (_rc, nodes) in &retcodes {
1350 for n in nodes {
1351 assert_eq!(n, "node1");
1352 }
1353 }
1354 }
1355
1356 #[test]
1357 fn test_task_iter_retcodes_by_worker() {
1358 let mut task = Task::new();
1359 let wid1 = task.shell("true", None, None);
1360 let wid2 = task.shell("exit 7", None, None);
1361 task.run(None).unwrap();
1362
1363 let rc1 = task.iter_retcodes_by_worker(wid1, None);
1364 assert_eq!(rc1.len(), 1);
1365 assert_eq!(rc1[0].0, 0);
1366
1367 let rc2 = task.iter_retcodes_by_worker(wid2, None);
1368 assert_eq!(rc2.len(), 1);
1369 assert_eq!(rc2[0].0, 7);
1370 }
1371
1372 #[test]
1375 fn test_task_abort() {
1376 let mut task = Task::new();
1377 task.shell("sleep 100", None, None);
1378 task.abort(true);
1380 assert_eq!(task.state(), TaskState::Aborted);
1381 }
1382
1383 struct CountingWorker {
1389 abort_count: Arc<AtomicUsize>,
1390 aborted: bool,
1391 never_done: bool,
1392 retcodes: HashMap<String, i32>,
1393 }
1394
1395 impl CountingWorker {
1396 fn new(abort_count: Arc<AtomicUsize>, never_done: bool) -> Self {
1397 Self {
1398 abort_count,
1399 aborted: false,
1400 never_done,
1401 retcodes: HashMap::new(),
1402 }
1403 }
1404 }
1405
1406 impl Worker for CountingWorker {
1407 fn start(&mut self) -> crate::worker::Result<()> {
1408 Ok(())
1409 }
1410
1411 fn abort(&mut self, _kill: bool) {
1412 self.abort_count.fetch_add(1, Ordering::SeqCst);
1413 self.aborted = true;
1414 }
1415
1416 fn state(&self) -> WorkerState {
1417 if self.aborted {
1418 WorkerState::Aborted
1419 } else {
1420 WorkerState::Running
1421 }
1422 }
1423
1424 fn set_handler(&mut self, _handler: Box<dyn EventHandler>) {}
1425
1426 fn read_fds(&self) -> Vec<std::os::unix::io::RawFd> {
1427 Vec::new()
1428 }
1429
1430 fn write_fds(&self) -> Vec<std::os::unix::io::RawFd> {
1431 Vec::new()
1432 }
1433
1434 fn handle_read(&mut self, _fd: std::os::unix::io::RawFd) -> crate::worker::Result<()> {
1435 Ok(())
1436 }
1437
1438 fn handle_write(&mut self, _fd: std::os::unix::io::RawFd) -> crate::worker::Result<()> {
1439 Ok(())
1440 }
1441
1442 fn is_done(&self) -> bool {
1443 !self.never_done && self.aborted
1444 }
1445
1446 fn retcodes(&self) -> &HashMap<String, i32> {
1447 &self.retcodes
1448 }
1449
1450 fn num_nodes(&self) -> usize {
1451 1
1452 }
1453
1454 fn take_handler(&mut self) -> Option<Box<dyn EventHandler>> {
1455 None
1456 }
1457 }
1458
1459 #[test]
1460 fn test_task_terminate_only_once_on_repeated_abort() {
1461 let count = Arc::new(AtomicUsize::new(0));
1465 let mut task = Task::new();
1466 task.schedule(
1467 Box::new(CountingWorker::new(count.clone(), true)),
1468 None,
1469 false,
1470 );
1471
1472 task.abort(true);
1473 assert_eq!(task.state(), TaskState::Aborted);
1474 assert_eq!(count.load(Ordering::SeqCst), 1);
1475 assert!(task.terminated_kill);
1476
1477 task.abort(true);
1481 task.abort(false);
1482 assert_eq!(count.load(Ordering::SeqCst), 1);
1483 assert_eq!(task.state(), TaskState::Aborted);
1484 }
1485
1486 #[test]
1487 fn test_task_terminate_guard_direct() {
1488 let count = Arc::new(AtomicUsize::new(0));
1492 let mut task = Task::new();
1493 task.schedule(
1494 Box::new(CountingWorker::new(count.clone(), true)),
1495 None,
1496 false,
1497 );
1498
1499 task.terminate(true);
1500 task.terminate(true);
1501 task.terminate(false);
1502 assert_eq!(count.load(Ordering::SeqCst), 1);
1503 }
1504
1505 #[test]
1506 fn test_task_repeated_soft_abort_then_kill() {
1507 let count = Arc::new(AtomicUsize::new(0));
1511 let mut task = Task::new();
1512 task.schedule(
1513 Box::new(CountingWorker::new(count.clone(), false)),
1514 None,
1515 false,
1516 );
1517
1518 task.abort(false);
1519 assert_eq!(task.state(), TaskState::Aborted);
1520 assert!(!task.terminated_kill);
1521 task.abort(false);
1522 assert_eq!(count.load(Ordering::SeqCst), 1);
1523
1524 task.abort(true);
1525 assert!(task.terminated_kill);
1526 task.abort(true);
1527 assert_eq!(count.load(Ordering::SeqCst), 1);
1528 assert_eq!(task.state(), TaskState::Aborted);
1529 }
1530
1531 #[test]
1532 fn test_task_abort_clears_results() {
1533 let mut task = Task::new();
1536 task.shell("echo hello", None, None);
1537 task.run(None).unwrap();
1538 assert_eq!(task.max_retcode(), Some(0));
1539 assert!(!task.node_buffer("localhost").unwrap().is_empty());
1540
1541 task.abort(false);
1542 assert_eq!(task.max_retcode(), None);
1543 assert!(task.node_buffer("localhost").unwrap().is_empty());
1544 assert_eq!(task.num_timeout(), 0);
1545 }
1546
1547 #[test]
1548 fn test_task_terminate_once_on_running_abort() {
1549 let mut abort_handles = Vec::new();
1555 let mut join_handles = Vec::new();
1556
1557 for i in 1..=4u64 {
1558 let mut task = Task::new();
1559 task.shell(&format!("sleep {}", 4 + i), None, None);
1560 abort_handles.push(task.abort_handle());
1561 join_handles.push(std::thread::spawn(move || {
1562 let result = task.run(None);
1563 (result, task.state())
1564 }));
1565 }
1566
1567 std::thread::sleep(Duration::from_millis(300));
1569
1570 for handle in &abort_handles {
1572 handle.abort(true);
1573 }
1574
1575 for join in join_handles {
1579 let (result, state) = join.join().expect("task thread panicked on abort");
1580 assert!(result.is_ok(), "aborted run should unwind normally");
1581 assert_eq!(state, TaskState::Aborted);
1582 }
1583 }
1584
1585 #[test]
1586 fn test_task_abort_handle_repeated_abort_during_run() {
1587 let mut task = Task::new();
1590 task.shell("sleep 8", None, None);
1591 let handle = task.abort_handle();
1592
1593 let join = std::thread::spawn(move || {
1594 let result = task.run(None);
1595 (result, task.state())
1596 });
1597
1598 std::thread::sleep(Duration::from_millis(300));
1599 handle.abort(true);
1600 handle.abort(true);
1601 handle.abort(false);
1602
1603 let (result, state) = join.join().expect("task thread panicked on abort");
1604 assert!(result.is_ok());
1605 assert_eq!(state, TaskState::Aborted);
1606 }
1607
1608 #[test]
1611 fn test_task_run_with_timeout() {
1612 let mut task = Task::new();
1613 task.shell("sleep 100", None, None);
1614 let result = task.run(Some(Duration::from_millis(100)));
1615 assert!(result.is_err());
1616 match result.unwrap_err() {
1617 TaskError::Timeout => {}
1618 other => panic!("expected Timeout, got: {:?}", other),
1619 }
1620 }
1621
1622 #[test]
1625 fn test_task_with_custom_handler() {
1626 let started = Arc::new(AtomicBool::new(false));
1627 let closed = Arc::new(AtomicBool::new(false));
1628 let started_clone = started.clone();
1629 let closed_clone = closed.clone();
1630
1631 struct TestHandler {
1632 started: Arc<AtomicBool>,
1633 closed: Arc<AtomicBool>,
1634 }
1635
1636 impl EventHandler for TestHandler {
1637 fn on_start(&mut self, _worker: &dyn Worker) {
1638 self.started.store(true, Ordering::SeqCst);
1639 }
1640 fn on_close(&mut self, _node: &str, _rc: i32) {
1641 self.closed.store(true, Ordering::SeqCst);
1642 }
1643 }
1644
1645 let handler = TestHandler {
1646 started: started_clone,
1647 closed: closed_clone,
1648 };
1649
1650 let mut task = Task::new();
1651 task.shell("echo hello", None, Some(Box::new(handler)));
1652 task.run(None).unwrap();
1653
1654 assert!(started.load(Ordering::SeqCst), "on_start should be called");
1655 assert!(closed.load(Ordering::SeqCst), "on_close should be called");
1656 }
1657
1658 #[test]
1661 fn test_task_reset_between_runs() {
1662 let mut task = Task::new();
1663 task.shell("exit 5", None, None);
1664 task.run(None).unwrap();
1665 assert_eq!(task.max_retcode(), Some(5));
1666
1667 task.shell("exit 3", None, None);
1669 task.state = TaskState::Idle;
1671 task.run(None).unwrap();
1672 assert_eq!(task.max_retcode(), Some(5));
1676 }
1677
1678 #[test]
1681 fn test_task_topology_none_by_default() {
1682 let task = Task::new();
1683 assert!(!task.has_topology());
1684 }
1685
1686 #[test]
1687 fn test_task_default_tree_disabled() {
1688 let mut task = Task::new();
1689 assert!(!task.default_tree_is_enabled());
1690 }
1691
1692 #[test]
1695 fn test_task_node_buffer_empty() {
1696 let task = Task::new();
1697 let buf = task.node_buffer("somenode").unwrap();
1698 assert!(buf.is_empty());
1699 }
1700
1701 #[test]
1702 fn test_task_node_error_empty() {
1703 let task = Task::new();
1704 let buf = task.node_error("somenode").unwrap();
1705 assert!(buf.is_empty());
1706 }
1707
1708 #[test]
1709 fn test_task_node_buffer_disabled() {
1710 let mut td = TaskDefaults::default();
1711 td.stdout_msgtree = false;
1712 let task = Task::with_config(td, TaskInfo::default());
1713 let result = task.node_buffer("somenode");
1714 assert!(result.is_err());
1715 }
1716
1717 #[test]
1718 fn test_task_flush_buffers() {
1719 let mut task = Task::new();
1720 task.flush_buffers();
1721 task.flush_errors();
1722 }
1724
1725 #[test]
1728 fn test_task_worker_accessor() {
1729 let mut task = Task::new();
1730 let wid = task.shell("echo test", None, None);
1731 assert!(task.worker(wid).is_some());
1732 assert!(task.worker(999).is_none());
1733 }
1734
1735 #[test]
1736 fn test_task_worker_retcodes_accessor() {
1737 let mut task = Task::new();
1738 let wid = task.shell("echo test", None, None);
1739 task.run(None).unwrap();
1740 let retcodes = task.worker_retcodes(wid);
1741 assert!(retcodes.is_some());
1742 assert_eq!(*retcodes.unwrap().get("localhost").unwrap(), 0);
1743 }
1744
1745 #[test]
1748 fn test_task_defaults_mut() {
1749 let mut task = Task::new();
1750 assert!(!task.defaults().stderr);
1751 task.defaults_mut().stderr = true;
1752 assert!(task.defaults().stderr);
1753 }
1754
1755 #[test]
1756 fn test_task_info_mut() {
1757 let mut task = Task::new();
1758 assert_eq!(task.info().fanout, 64);
1759 task.info_mut().fanout = 256;
1760 assert_eq!(task.info().fanout, 256);
1761 }
1762
1763 #[test]
1766 fn test_get_short_hostname() {
1767 let hostname = get_short_hostname();
1768 assert!(!hostname.is_empty());
1769 assert!(!hostname.contains('.'));
1770 }
1771
1772 #[test]
1775 fn test_gathering_handler_collects_retcodes() {
1776 let mut handler = TaskGatheringHandler::new(0, None);
1777 handler.on_close("node1", 0);
1778 handler.on_close("node2", 1);
1779 assert_eq!(handler.retcodes.len(), 2);
1780 assert_eq!(handler.retcodes["node1"], 0);
1781 assert_eq!(handler.retcodes["node2"], 1);
1782 }
1783
1784 #[test]
1785 fn test_gathering_handler_collects_stdout() {
1786 let mut handler = TaskGatheringHandler::new(0, None);
1787 handler.on_read("node1", 1, b"hello");
1788 handler.on_read("node1", 1, b"world");
1789 handler.on_read("node2", 1, b"hi");
1790 assert_eq!(handler.stdout["node1"].len(), 2);
1791 assert_eq!(handler.stdout["node2"].len(), 1);
1792 }
1793
1794 #[test]
1795 fn test_gathering_handler_collects_stderr() {
1796 let mut handler = TaskGatheringHandler::new(0, None);
1797 handler.on_read("node1", 2, b"error msg");
1798 assert_eq!(handler.stderr["node1"].len(), 1);
1799 assert!(handler.stdout.is_empty());
1800 }
1801
1802 #[test]
1803 fn test_gathering_handler_collects_timeouts() {
1804 let mut handler = TaskGatheringHandler::new(0, None);
1805 handler.on_timeout("node1");
1806 handler.on_timeout("node2");
1807 assert_eq!(handler.timeouts.len(), 2);
1808 assert!(handler.timeouts.contains("node1"));
1809 }
1810
1811 #[test]
1812 fn test_gathering_handler_chains_user_handler() {
1813 let called = Arc::new(AtomicUsize::new(0));
1814 let called_clone = called.clone();
1815
1816 struct CountHandler(Arc<AtomicUsize>);
1817 impl EventHandler for CountHandler {
1818 fn on_close(&mut self, _node: &str, _rc: i32) {
1819 self.0.fetch_add(1, Ordering::SeqCst);
1820 }
1821 }
1822
1823 let user_handler = Box::new(CountHandler(called_clone));
1824 let mut handler = TaskGatheringHandler::new(0, Some(user_handler));
1825 handler.on_close("node1", 0);
1826 handler.on_close("node2", 0);
1827
1828 assert_eq!(called.load(Ordering::SeqCst), 2);
1829 assert_eq!(handler.retcodes.len(), 2);
1830 }
1831}