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").is_none_or(|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 .split('.')
179 .next()
180 .unwrap_or("localhost")
181 .to_string()
182 })
183 .unwrap_or_else(|_| "localhost".to_string())
184}
185
186struct ManagedWorker {
188 worker: Box<dyn Worker>,
190 update_task_rc: bool,
192 autoclose: bool,
194}
195
196struct TaskGatheringHandler {
202 #[allow(dead_code)]
204 worker_id: usize,
205 stdout: HashMap<String, Vec<Vec<u8>>>,
207 stderr: HashMap<String, Vec<Vec<u8>>>,
209 retcodes: HashMap<String, i32>,
211 timeouts: HashSet<String>,
213 user_handler: Option<Box<dyn EventHandler>>,
215}
216
217impl TaskGatheringHandler {
218 fn new(worker_id: usize, user_handler: Option<Box<dyn EventHandler>>) -> Self {
219 Self {
220 worker_id,
221 stdout: HashMap::new(),
222 stderr: HashMap::new(),
223 retcodes: HashMap::new(),
224 timeouts: HashSet::new(),
225 user_handler,
226 }
227 }
228}
229
230impl EventHandler for TaskGatheringHandler {
231 fn on_start(&mut self, worker: &dyn Worker) {
232 if let Some(ref mut h) = self.user_handler {
233 h.on_start(worker);
234 }
235 }
236
237 fn on_read(&mut self, node: &str, fd: std::os::unix::io::RawFd, msg: &[u8]) {
238 let map = if fd == 2 {
240 &mut self.stderr
241 } else {
242 &mut self.stdout
243 };
244 map.entry(node.to_string()).or_default().push(msg.to_vec());
245
246 if let Some(ref mut h) = self.user_handler {
247 h.on_read(node, fd, msg);
248 }
249 }
250
251 fn on_close(&mut self, node: &str, rc: i32) {
252 self.retcodes.insert(node.to_string(), rc);
253
254 if let Some(ref mut h) = self.user_handler {
255 h.on_close(node, rc);
256 }
257 }
258
259 fn on_timeout(&mut self, node: &str) {
260 self.timeouts.insert(node.to_string());
261
262 if let Some(ref mut h) = self.user_handler {
263 h.on_timeout(node);
264 }
265 }
266
267 fn on_error(&mut self, node: &str, error: &WorkerError) {
268 if let Some(ref mut h) = self.user_handler {
269 h.on_error(node, error);
270 }
271 }
272
273 fn take_buffers(
274 &mut self,
275 ) -> (
276 HashMap<String, Vec<Vec<u8>>>,
277 HashMap<String, Vec<Vec<u8>>>,
278 HashSet<String>,
279 ) {
280 (
281 std::mem::take(&mut self.stdout),
282 std::mem::take(&mut self.stderr),
283 std::mem::take(&mut self.timeouts),
284 )
285 }
286}
287
288#[derive(Debug, Clone)]
296pub struct TaskAbortHandle {
297 quit: Arc<AtomicBool>,
299 kill: Arc<AtomicBool>,
301}
302
303impl TaskAbortHandle {
304 pub fn abort(&self, kill: bool) {
310 if kill {
311 self.kill.store(true, Ordering::SeqCst);
312 }
313 self.quit.store(true, Ordering::SeqCst);
314 }
315}
316
317pub struct Task {
330 state: TaskState,
332 defaults: TaskDefaults,
334 info: TaskInfo,
336 timeout: Option<Duration>,
338
339 quit: Arc<AtomicBool>,
343 kill_requested: Arc<AtomicBool>,
346 terminated_kill: bool,
351
352 next_worker_id: usize,
355 workers: Vec<(usize, ManagedWorker)>,
357
358 stdout_tree: Option<MsgTree<Source>>,
361 stderr_tree: Option<MsgTree<Source>>,
363 d_source_rc: HashMap<Source, i32>,
365 d_rc_sources: BTreeMap<i32, HashSet<Source>>,
367 max_rc: Option<i32>,
369 timeout_sources: HashSet<Source>,
371
372 topology_parser: Option<TopologyParser>,
375 router: Option<PropagationTreeRouter>,
377 #[allow(dead_code)]
379 gateways: HashMap<String, usize>,
380}
381
382impl Task {
383 pub fn new() -> Self {
385 let defaults = TaskDefaults::default();
386 let info = TaskInfo::default();
387 Self::with_config(defaults, info)
388 }
389
390 pub fn from_defaults(defaults: &Defaults) -> Self {
392 let task_defaults = TaskDefaults::from_defaults(defaults);
393 let task_info = TaskInfo::from_defaults(defaults);
394 Self::with_config(task_defaults, task_info)
395 }
396
397 pub fn with_config(defaults: TaskDefaults, info: TaskInfo) -> Self {
399 let stdout_tree = if defaults.stdout_msgtree {
400 Some(MsgTree::new(MsgTreeMode::Shift))
401 } else {
402 None
403 };
404 let stderr_tree = if defaults.stderr_msgtree {
405 Some(MsgTree::new(MsgTreeMode::Shift))
406 } else {
407 None
408 };
409
410 Self {
411 state: TaskState::Idle,
412 defaults,
413 info,
414 timeout: None,
415 quit: Arc::new(AtomicBool::new(false)),
416 kill_requested: Arc::new(AtomicBool::new(false)),
417 terminated_kill: false,
418 next_worker_id: 0,
419 workers: Vec::new(),
420 stdout_tree,
421 stderr_tree,
422 d_source_rc: HashMap::new(),
423 d_rc_sources: BTreeMap::new(),
424 max_rc: None,
425 timeout_sources: HashSet::new(),
426 topology_parser: None,
427 router: None,
428 gateways: HashMap::new(),
429 }
430 }
431
432 pub fn defaults(&self) -> &TaskDefaults {
438 &self.defaults
439 }
440
441 pub fn defaults_mut(&mut self) -> &mut TaskDefaults {
443 &mut self.defaults
444 }
445
446 pub fn info(&self) -> &TaskInfo {
448 &self.info
449 }
450
451 pub fn info_mut(&mut self) -> &mut TaskInfo {
453 &mut self.info
454 }
455
456 pub fn set_fanout(&mut self, fanout: usize) {
458 self.info.fanout = fanout;
459 }
460
461 pub fn fanout(&self) -> usize {
463 self.info.fanout
464 }
465
466 pub fn load_topology(&mut self, path: &str) -> Result<()> {
474 let hostname = get_short_hostname();
475 let mut parser = TopologyParser::new();
476 parser
477 .load(path)
478 .map_err(|e| TaskError::Topology(format!("failed to load topology: {}", e)))?;
479 parser
481 .tree(&hostname)
482 .map_err(|e| TaskError::Topology(format!("failed to build tree: {}", e)))?;
483 self.topology_parser = Some(parser);
484 Ok(())
485 }
486
487 fn try_load_default_topology(&mut self) -> bool {
489 if self.topology_parser.is_some() {
490 return true;
491 }
492 let configs = topology_configs();
493 for path in configs.iter().rev() {
494 if path.exists() {
495 if let Ok(()) = self.load_topology(&path.to_string_lossy()) {
496 return true;
497 }
498 }
499 }
500 false
501 }
502
503 pub fn default_tree_is_enabled(&mut self) -> bool {
505 if self.topology_parser.is_none() {
506 self.try_load_default_topology();
507 }
508 self.topology_parser.is_some() && self.defaults.auto_tree
509 }
510
511 pub fn default_router(&mut self) -> Result<&PropagationTreeRouter> {
513 if self.router.is_none() {
514 let hostname = get_short_hostname();
515 let parser = self
516 .topology_parser
517 .as_mut()
518 .ok_or_else(|| TaskError::Topology("no topology loaded".to_string()))?;
519 let topology = parser
520 .tree(&hostname)
521 .map_err(|e| TaskError::Topology(format!("failed to build tree: {}", e)))?;
522 let root_ns = topology
523 .root()
524 .ok_or_else(|| TaskError::Topology("topology has no root".to_string()))?;
525 let root_str = root_ns.nodeset.to_string();
526 let fanout = self.info.fanout;
527 let router = PropagationTreeRouter::new(&root_str, topology, fanout)
528 .map_err(|e| TaskError::Topology(format!("router creation failed: {}", e)))?;
529 self.router = Some(router);
530 }
531 Ok(self.router.as_ref().unwrap())
532 }
533
534 pub fn has_topology(&self) -> bool {
536 self.topology_parser.is_some()
537 }
538
539 pub fn shell(
550 &mut self,
551 command: &str,
552 nodes: Option<&str>,
553 handler: Option<Box<dyn EventHandler>>,
554 ) -> usize {
555 self.shell_with_opts(command, nodes, handler, None, false)
556 }
557
558 pub fn shell_with_opts(
562 &mut self,
563 command: &str,
564 nodes: Option<&str>,
565 handler: Option<Box<dyn EventHandler>>,
566 timeout: Option<Duration>,
567 autoclose: bool,
568 ) -> usize {
569 let node_list: Vec<String> = match nodes {
570 Some(ns) => {
571 match NodeSet::parse(ns) {
573 Ok(nodeset) => nodeset.iter().collect::<Vec<String>>(),
574 Err(_) => {
575 vec![ns.to_string()]
577 }
578 }
579 }
580 None => {
581 vec!["localhost".to_string()]
583 }
584 };
585
586 let fanout = self.info.fanout;
587 let worker_timeout = timeout.or_else(|| {
588 if self.info.command_timeout > 0.0 {
589 Some(Duration::from_secs_f64(self.info.command_timeout))
590 } else {
591 None
592 }
593 });
594
595 let mut worker = ExecWorker::new(node_list, command.to_string(), fanout, worker_timeout);
596 worker = worker.with_stderr(self.defaults.stderr);
597
598 self.schedule(Box::new(worker), handler, autoclose)
599 }
600
601 pub fn schedule(
606 &mut self,
607 worker: Box<dyn Worker>,
608 handler: Option<Box<dyn EventHandler>>,
609 autoclose: bool,
610 ) -> usize {
611 let worker_id = self.next_worker_id;
612 self.next_worker_id += 1;
613
614 let gathering = TaskGatheringHandler::new(worker_id, handler);
616 let mut managed_worker = ManagedWorker {
617 worker,
618 update_task_rc: true,
619 autoclose,
620 };
621 managed_worker.worker.set_handler(Box::new(gathering));
622
623 self.workers.push((worker_id, managed_worker));
624 worker_id
625 }
626
627 pub fn run(&mut self, timeout: Option<Duration>) -> Result<()> {
637 self.timeout = timeout;
638 self.resume()
639 }
640
641 pub fn run_command(
643 &mut self,
644 command: &str,
645 nodes: Option<&str>,
646 handler: Option<Box<dyn EventHandler>>,
647 timeout: Option<Duration>,
648 ) -> Result<usize> {
649 let wid = self.shell(command, nodes, handler);
650 self.timeout = timeout;
651 self.resume()?;
652 Ok(wid)
653 }
654
655 pub fn resume(&mut self) -> Result<()> {
657 if self.state == TaskState::Running {
658 return Err(TaskError::AlreadyRunning);
659 }
660
661 self.reset();
662 self.state = TaskState::Running;
663
664 let mut _running_count = 0;
666 let mut errors: Vec<TaskError> = Vec::new();
667
668 for (_wid, mw) in &mut self.workers {
670 if mw.worker.state() == WorkerState::Pending {
671 match mw.worker.start() {
672 Ok(()) => _running_count += 1,
673 Err(e) => errors.push(TaskError::Worker(e)),
674 }
675 }
676 }
677
678 let start = std::time::Instant::now();
680 loop {
681 if self.quit.load(Ordering::SeqCst) {
686 let kill = self.kill_requested.load(Ordering::SeqCst);
687 self.terminate(kill);
688 return Ok(());
689 }
690
691 let mut any_running = false;
692 for (_wid, mw) in &mut self.workers {
693 if !mw.autoclose && !mw.worker.is_done() {
694 let fds = mw.worker.read_fds();
696 for fd in &fds {
697 let _ = mw.worker.handle_read(*fd);
698 }
699 if !mw.worker.is_done() {
700 any_running = true;
701 }
702 }
703 }
704 if !any_running {
705 break;
706 }
707
708 if let Some(timeout) = self.timeout {
710 if start.elapsed() >= timeout {
711 for (_wid, mw) in &mut self.workers {
713 if !mw.worker.is_done() {
714 mw.worker.abort(true);
715 }
716 }
717 self.state = TaskState::Done;
718 return Err(TaskError::Timeout);
719 }
720 }
721
722 std::thread::sleep(Duration::from_millis(10));
724 }
725
726 self.collect_results();
728
729 self.state = TaskState::Done;
730
731 if let Some(err) = errors.into_iter().next() {
732 Err(err)
733 } else {
734 Ok(())
735 }
736 }
737
738 pub fn abort(&mut self, kill: bool) {
747 if kill {
750 self.kill_requested.store(true, Ordering::SeqCst);
751 }
752 self.quit.store(true, Ordering::SeqCst);
753 self.terminate(kill);
754 }
755
756 pub fn abort_handle(&self) -> TaskAbortHandle {
762 TaskAbortHandle {
763 quit: Arc::clone(&self.quit),
764 kill: Arc::clone(&self.kill_requested),
765 }
766 }
767
768 fn terminate(&mut self, kill: bool) {
775 if self.terminated_kill {
777 return;
778 }
779
780 for (_wid, mw) in &mut self.workers {
782 if !mw.worker.is_done() {
783 mw.worker.abort(kill);
784 }
785 }
786
787 if kill {
788 self.terminated_kill = true;
790 }
791
792 self.reset();
794
795 self.state = TaskState::Aborted;
796 }
797
798 pub fn running(&self) -> bool {
800 self.state == TaskState::Running
801 }
802
803 pub fn state(&self) -> TaskState {
805 self.state
806 }
807
808 fn reset(&mut self) {
814 if let Some(ref mut tree) = self.stdout_tree {
815 tree.clear();
816 }
817 if let Some(ref mut tree) = self.stderr_tree {
818 tree.clear();
819 }
820 self.d_source_rc.clear();
821 self.d_rc_sources.clear();
822 self.max_rc = None;
823 self.timeout_sources.clear();
824 }
825
826 fn collect_results(&mut self) {
828 for (wid, mw) in &mut self.workers {
829 let worker_id = *wid;
830
831 if mw.update_task_rc {
833 for (node, &rc) in mw.worker.retcodes() {
834 let source: Source = (worker_id, node.clone());
835 self.d_source_rc.insert(source.clone(), rc);
836 self.d_rc_sources.entry(rc).or_default().insert(source);
837
838 if self.max_rc.is_none_or(|max| rc > max) {
839 self.max_rc = Some(rc);
840 }
841 }
842 }
843
844 if let Some(mut handler) = mw.worker.take_handler() {
848 let (stdout, stderr, timeouts) = handler.take_buffers();
849
850 if let Some(ref mut tree) = self.stdout_tree {
852 for (node, chunks) in stdout {
853 let source: Source = (worker_id, node);
854 for chunk in chunks {
855 tree.add(source.clone(), chunk);
856 }
857 }
858 }
859
860 if let Some(ref mut tree) = self.stderr_tree {
862 for (node, chunks) in stderr {
863 let source: Source = (worker_id, node);
864 for chunk in chunks {
865 tree.add(source.clone(), chunk);
866 }
867 }
868 }
869
870 for node in timeouts {
872 self.timeout_sources.insert((worker_id, node));
873 }
874 }
875 }
876 }
877
878 pub fn max_retcode(&self) -> Option<i32> {
886 self.max_rc
887 }
888
889 pub fn node_retcode(&self, node: &str) -> Option<i32> {
894 let mut max_rc: Option<i32> = None;
895 for ((_, n), &rc) in &self.d_source_rc {
896 if n == node {
897 max_rc = Some(max_rc.map_or(rc, |m: i32| m.max(rc)));
898 }
899 }
900 max_rc
901 }
902
903 pub fn iter_retcodes(&self, match_keys: Option<&HashSet<String>>) -> Vec<(i32, Vec<String>)> {
908 let mut result = Vec::new();
909 for (&rc, sources) in &self.d_rc_sources {
910 let nodes: Vec<String> = sources
911 .iter()
912 .filter(|(_, n)| match_keys.is_none_or(|keys| keys.contains(n)))
913 .map(|(_, n)| n.clone())
914 .collect();
915 if !nodes.is_empty() {
916 result.push((rc, nodes));
917 }
918 }
919 result
920 }
921
922 pub fn iter_retcodes_by_worker(
924 &self,
925 worker_id: usize,
926 match_keys: Option<&HashSet<String>>,
927 ) -> Vec<(i32, Vec<String>)> {
928 let mut result = Vec::new();
929 for (&rc, sources) in &self.d_rc_sources {
930 let nodes: Vec<String> = sources
931 .iter()
932 .filter(|(wid, n)| {
933 *wid == worker_id && match_keys.is_none_or(|keys| keys.contains(n))
934 })
935 .map(|(_, n)| n.clone())
936 .collect();
937 if !nodes.is_empty() {
938 result.push((rc, nodes));
939 }
940 }
941 result
942 }
943
944 pub fn num_timeout(&self) -> usize {
946 self.timeout_sources.len()
947 }
948
949 pub fn iter_keys_timeout(&self) -> impl Iterator<Item = &str> {
951 self.timeout_sources.iter().map(|(_, n)| n.as_str())
952 }
953
954 pub fn num_timeout_by_worker(&self, worker_id: usize) -> usize {
956 self.timeout_sources
957 .iter()
958 .filter(|(wid, _)| *wid == worker_id)
959 .count()
960 }
961
962 pub fn iter_keys_timeout_by_worker(&self, worker_id: usize) -> impl Iterator<Item = &str> {
964 self.timeout_sources
965 .iter()
966 .filter(move |(wid, _)| *wid == worker_id)
967 .map(|(_, n)| n.as_str())
968 }
969
970 pub fn node_buffer(&self, node: &str) -> Result<Vec<u8>> {
978 let tree = self
979 .stdout_tree
980 .as_ref()
981 .ok_or_else(|| TaskError::MsgTreeDisabled("stdout".to_string()))?;
982 let mut buf = Vec::new();
983 for key in tree.keys() {
984 if key.1 == node {
985 if let Some(elem) = tree.get(key) {
986 let msg = elem.message();
987 if !buf.is_empty() {
988 buf.push(b'\n');
989 }
990 buf.extend_from_slice(&msg);
991 }
992 }
993 }
994 Ok(buf)
995 }
996
997 pub fn node_error(&self, node: &str) -> Result<Vec<u8>> {
999 let tree = self
1000 .stderr_tree
1001 .as_ref()
1002 .ok_or_else(|| TaskError::MsgTreeDisabled("stderr".to_string()))?;
1003 let mut buf = Vec::new();
1004 for key in tree.keys() {
1005 if key.1 == node {
1006 if let Some(elem) = tree.get(key) {
1007 let msg = elem.message();
1008 if !buf.is_empty() {
1009 buf.push(b'\n');
1010 }
1011 buf.extend_from_slice(&msg);
1012 }
1013 }
1014 }
1015 Ok(buf)
1016 }
1017
1018 pub fn flush_buffers(&mut self) {
1020 if let Some(ref mut tree) = self.stdout_tree {
1021 tree.clear();
1022 }
1023 }
1024
1025 pub fn flush_errors(&mut self) {
1027 if let Some(ref mut tree) = self.stderr_tree {
1028 tree.clear();
1029 }
1030 }
1031
1032 pub fn num_workers(&self) -> usize {
1038 self.workers.len()
1039 }
1040
1041 pub fn worker(&self, worker_id: usize) -> Option<&dyn Worker> {
1043 self.workers
1044 .iter()
1045 .find(|(wid, _)| *wid == worker_id)
1046 .map(|(_, mw)| mw.worker.as_ref())
1047 }
1048
1049 pub fn worker_retcodes(&self, worker_id: usize) -> Option<&HashMap<String, i32>> {
1051 self.workers
1052 .iter()
1053 .find(|(wid, _)| *wid == worker_id)
1054 .map(|(_, mw)| mw.worker.retcodes())
1055 }
1056}
1057
1058impl Default for Task {
1059 fn default() -> Self {
1060 Self::new()
1061 }
1062}
1063
1064#[cfg(test)]
1069mod tests {
1070 use super::*;
1071 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1072 use std::sync::Arc;
1073
1074 #[test]
1077 fn test_task_state_variants() {
1078 assert_eq!(TaskState::Idle, TaskState::Idle);
1079 assert_eq!(TaskState::Running, TaskState::Running);
1080 assert_eq!(TaskState::Done, TaskState::Done);
1081 assert_eq!(TaskState::Aborted, TaskState::Aborted);
1082 assert_ne!(TaskState::Idle, TaskState::Running);
1083 }
1084
1085 #[test]
1086 fn test_task_state_debug() {
1087 assert_eq!(format!("{:?}", TaskState::Idle), "Idle");
1088 assert_eq!(format!("{:?}", TaskState::Running), "Running");
1089 assert_eq!(format!("{:?}", TaskState::Done), "Done");
1090 assert_eq!(format!("{:?}", TaskState::Aborted), "Aborted");
1091 }
1092
1093 #[test]
1094 fn test_task_state_clone_copy() {
1095 let s = TaskState::Running;
1096 let s2 = s;
1097 assert_eq!(s, s2);
1098 }
1099
1100 #[test]
1103 fn test_task_error_display() {
1104 let e = TaskError::Timeout;
1105 assert_eq!(e.to_string(), "task timeout");
1106
1107 let e = TaskError::AlreadyRunning;
1108 assert_eq!(e.to_string(), "task is already running");
1109
1110 let e = TaskError::MsgTreeDisabled("stdout".to_string());
1111 assert_eq!(e.to_string(), "stdout_msgtree not set");
1112
1113 let e = TaskError::General("test error".to_string());
1114 assert_eq!(e.to_string(), "task error: test error");
1115
1116 let e = TaskError::Topology("no topology".to_string());
1117 assert_eq!(e.to_string(), "topology error: no topology");
1118 }
1119
1120 #[test]
1121 fn test_task_error_from_worker_error() {
1122 let we = WorkerError::Timeout;
1123 let te: TaskError = we.into();
1124 assert!(matches!(te, TaskError::Worker(WorkerError::Timeout)));
1125 }
1126
1127 #[test]
1130 fn test_task_defaults_default() {
1131 let d = TaskDefaults::default();
1132 assert!(!d.stderr);
1133 assert!(d.stdin);
1134 assert!(d.stdout_msgtree);
1135 assert!(d.stderr_msgtree);
1136 assert!(d.auto_tree);
1138 }
1139
1140 #[test]
1141 fn test_task_defaults_from_defaults() {
1142 let defaults = Defaults::new();
1143 let d = TaskDefaults::from_defaults(&defaults);
1144 assert!(!d.stderr);
1145 assert!(d.stdin);
1146 assert!(d.auto_tree);
1147 }
1148
1149 #[test]
1152 fn test_task_info_default() {
1153 let info = TaskInfo::default();
1154 assert!(!info.debug);
1155 assert_eq!(info.fanout, 64);
1156 assert_eq!(info.grooming_delay, 0.25);
1158 assert_eq!(info.connect_timeout, 10.0);
1159 assert_eq!(info.command_timeout, 0.0);
1160 }
1161
1162 #[test]
1163 fn test_task_info_from_defaults() {
1164 let defaults = Defaults::new();
1165 let info = TaskInfo::from_defaults(&defaults);
1166 assert_eq!(info.fanout, defaults.fanout() as usize);
1167 assert_eq!(info.grooming_delay, defaults.grooming_delay());
1168 assert_eq!(info.connect_timeout, defaults.connect_timeout());
1169 assert_eq!(info.command_timeout, defaults.command_timeout());
1170 }
1171
1172 #[test]
1175 fn test_task_new() {
1176 let task = Task::new();
1177 assert_eq!(task.state(), TaskState::Idle);
1178 assert_eq!(task.num_workers(), 0);
1179 assert_eq!(task.max_retcode(), None);
1180 assert_eq!(task.num_timeout(), 0);
1181 assert!(!task.running());
1182 }
1183
1184 #[test]
1185 fn test_task_default() {
1186 let task = Task::default();
1187 assert_eq!(task.state(), TaskState::Idle);
1188 }
1189
1190 #[test]
1191 fn test_task_from_defaults() {
1192 let defaults = Defaults::new();
1193 let task = Task::from_defaults(&defaults);
1194 assert_eq!(task.state(), TaskState::Idle);
1195 assert_eq!(task.fanout(), defaults.fanout() as usize);
1196 }
1197
1198 #[test]
1199 fn test_task_with_config() {
1200 let td = TaskDefaults {
1201 stderr: true,
1202 stdout_msgtree: false,
1203 ..Default::default()
1204 };
1205
1206 let ti = TaskInfo {
1207 fanout: 32,
1208 ..Default::default()
1209 };
1210
1211 let task = Task::with_config(td, ti);
1212 assert!(task.defaults().stderr);
1213 assert!(!task.defaults().stdout_msgtree);
1214 assert_eq!(task.fanout(), 32);
1215 assert!(task.stdout_tree.is_none());
1217 assert!(task.stderr_tree.is_some());
1218 }
1219
1220 #[test]
1223 fn test_task_set_fanout() {
1224 let mut task = Task::new();
1225 assert_eq!(task.fanout(), 64);
1226 task.set_fanout(128);
1227 assert_eq!(task.fanout(), 128);
1228 }
1229
1230 #[test]
1233 fn test_task_shell_local() {
1234 let mut task = Task::new();
1235 let wid = task.shell("echo hello", None, None);
1236 assert_eq!(wid, 0);
1237 assert_eq!(task.num_workers(), 1);
1238 }
1239
1240 #[test]
1241 fn test_task_shell_remote() {
1242 let mut task = Task::new();
1243 let wid = task.shell("hostname", Some("node[1-3]"), None);
1244 assert_eq!(wid, 0);
1245 assert_eq!(task.num_workers(), 1);
1246 }
1247
1248 #[test]
1249 fn test_task_shell_multiple() {
1250 let mut task = Task::new();
1251 let wid1 = task.shell("echo 1", None, None);
1252 let wid2 = task.shell("echo 2", None, None);
1253 assert_eq!(wid1, 0);
1254 assert_eq!(wid2, 1);
1255 assert_eq!(task.num_workers(), 2);
1256 }
1257
1258 #[test]
1259 fn test_task_shell_with_opts() {
1260 let mut task = Task::new();
1261 let wid = task.shell_with_opts(
1262 "echo test",
1263 Some("node[1-5]"),
1264 None,
1265 Some(Duration::from_secs(30)),
1266 true,
1267 );
1268 assert_eq!(wid, 0);
1269 assert_eq!(task.num_workers(), 1);
1270 }
1271
1272 #[test]
1275 fn test_task_run_local_command() {
1276 let mut task = Task::new();
1277 task.shell("echo hello", None, None);
1278 let result = task.run(None);
1279 assert!(result.is_ok());
1280 assert_eq!(task.state(), TaskState::Done);
1281 }
1282
1283 #[test]
1284 fn test_task_run_returns_retcode() {
1285 let mut task = Task::new();
1286 task.shell("true", None, None);
1287 task.run(None).unwrap();
1288 assert_eq!(task.max_retcode(), Some(0));
1289 }
1290
1291 #[test]
1292 fn test_task_run_failing_command() {
1293 let mut task = Task::new();
1294 task.shell("false", None, None);
1295 task.run(None).unwrap();
1296 assert_eq!(task.max_retcode(), Some(1));
1297 }
1298
1299 #[test]
1300 fn test_task_run_mixed_retcodes() {
1301 let mut task = Task::new();
1302 task.shell("true", None, None);
1303 task.shell("exit 42", None, None);
1304 task.run(None).unwrap();
1305 assert_eq!(task.max_retcode(), Some(42));
1306 }
1307
1308 #[test]
1309 fn test_task_run_command_convenience() {
1310 let mut task = Task::new();
1311 let wid = task.run_command("echo test", None, None, None).unwrap();
1312 assert_eq!(wid, 0);
1313 assert_eq!(task.max_retcode(), Some(0));
1314 }
1315
1316 #[test]
1317 fn test_task_run_multiple_nodes() {
1318 let mut task = Task::new();
1319 task.shell("echo hi", Some("node[1-3]"), None);
1320 task.run(None).unwrap();
1321 assert_eq!(task.max_retcode(), Some(0));
1323
1324 let retcodes = task.iter_retcodes(None);
1325 for (rc, _nodes) in &retcodes {
1327 assert_eq!(*rc, 0);
1328 }
1329 }
1330
1331 #[test]
1334 fn test_task_node_retcode() {
1335 let mut task = Task::new();
1336 task.shell("true", None, None);
1337 task.run(None).unwrap();
1338 assert_eq!(task.node_retcode("localhost"), Some(0));
1340 assert_eq!(task.node_retcode("nonexistent"), None);
1341 }
1342
1343 #[test]
1344 fn test_task_iter_retcodes_with_filter() {
1345 let mut task = Task::new();
1346 task.shell("echo hi", Some("node[1-3]"), None);
1347 task.run(None).unwrap();
1348
1349 let mut filter = HashSet::new();
1350 filter.insert("node1".to_string());
1351 let retcodes = task.iter_retcodes(Some(&filter));
1352 for (_rc, nodes) in &retcodes {
1353 for n in nodes {
1354 assert_eq!(n, "node1");
1355 }
1356 }
1357 }
1358
1359 #[test]
1360 fn test_task_iter_retcodes_by_worker() {
1361 let mut task = Task::new();
1362 let wid1 = task.shell("true", None, None);
1363 let wid2 = task.shell("exit 7", None, None);
1364 task.run(None).unwrap();
1365
1366 let rc1 = task.iter_retcodes_by_worker(wid1, None);
1367 assert_eq!(rc1.len(), 1);
1368 assert_eq!(rc1[0].0, 0);
1369
1370 let rc2 = task.iter_retcodes_by_worker(wid2, None);
1371 assert_eq!(rc2.len(), 1);
1372 assert_eq!(rc2[0].0, 7);
1373 }
1374
1375 #[test]
1378 fn test_task_abort() {
1379 let mut task = Task::new();
1380 task.shell("sleep 100", None, None);
1381 task.abort(true);
1383 assert_eq!(task.state(), TaskState::Aborted);
1384 }
1385
1386 struct CountingWorker {
1392 abort_count: Arc<AtomicUsize>,
1393 aborted: bool,
1394 never_done: bool,
1395 retcodes: HashMap<String, i32>,
1396 }
1397
1398 impl CountingWorker {
1399 fn new(abort_count: Arc<AtomicUsize>, never_done: bool) -> Self {
1400 Self {
1401 abort_count,
1402 aborted: false,
1403 never_done,
1404 retcodes: HashMap::new(),
1405 }
1406 }
1407 }
1408
1409 impl Worker for CountingWorker {
1410 fn start(&mut self) -> crate::worker::Result<()> {
1411 Ok(())
1412 }
1413
1414 fn abort(&mut self, _kill: bool) {
1415 self.abort_count.fetch_add(1, Ordering::SeqCst);
1416 self.aborted = true;
1417 }
1418
1419 fn state(&self) -> WorkerState {
1420 if self.aborted {
1421 WorkerState::Aborted
1422 } else {
1423 WorkerState::Running
1424 }
1425 }
1426
1427 fn set_handler(&mut self, _handler: Box<dyn EventHandler>) {}
1428
1429 fn read_fds(&self) -> Vec<std::os::unix::io::RawFd> {
1430 Vec::new()
1431 }
1432
1433 fn write_fds(&self) -> Vec<std::os::unix::io::RawFd> {
1434 Vec::new()
1435 }
1436
1437 fn handle_read(&mut self, _fd: std::os::unix::io::RawFd) -> crate::worker::Result<()> {
1438 Ok(())
1439 }
1440
1441 fn handle_write(&mut self, _fd: std::os::unix::io::RawFd) -> crate::worker::Result<()> {
1442 Ok(())
1443 }
1444
1445 fn is_done(&self) -> bool {
1446 !self.never_done && self.aborted
1447 }
1448
1449 fn retcodes(&self) -> &HashMap<String, i32> {
1450 &self.retcodes
1451 }
1452
1453 fn num_nodes(&self) -> usize {
1454 1
1455 }
1456
1457 fn take_handler(&mut self) -> Option<Box<dyn EventHandler>> {
1458 None
1459 }
1460 }
1461
1462 #[test]
1463 fn test_task_terminate_only_once_on_repeated_abort() {
1464 let count = Arc::new(AtomicUsize::new(0));
1468 let mut task = Task::new();
1469 task.schedule(
1470 Box::new(CountingWorker::new(count.clone(), true)),
1471 None,
1472 false,
1473 );
1474
1475 task.abort(true);
1476 assert_eq!(task.state(), TaskState::Aborted);
1477 assert_eq!(count.load(Ordering::SeqCst), 1);
1478 assert!(task.terminated_kill);
1479
1480 task.abort(true);
1484 task.abort(false);
1485 assert_eq!(count.load(Ordering::SeqCst), 1);
1486 assert_eq!(task.state(), TaskState::Aborted);
1487 }
1488
1489 #[test]
1490 fn test_task_terminate_guard_direct() {
1491 let count = Arc::new(AtomicUsize::new(0));
1495 let mut task = Task::new();
1496 task.schedule(
1497 Box::new(CountingWorker::new(count.clone(), true)),
1498 None,
1499 false,
1500 );
1501
1502 task.terminate(true);
1503 task.terminate(true);
1504 task.terminate(false);
1505 assert_eq!(count.load(Ordering::SeqCst), 1);
1506 }
1507
1508 #[test]
1509 fn test_task_repeated_soft_abort_then_kill() {
1510 let count = Arc::new(AtomicUsize::new(0));
1514 let mut task = Task::new();
1515 task.schedule(
1516 Box::new(CountingWorker::new(count.clone(), false)),
1517 None,
1518 false,
1519 );
1520
1521 task.abort(false);
1522 assert_eq!(task.state(), TaskState::Aborted);
1523 assert!(!task.terminated_kill);
1524 task.abort(false);
1525 assert_eq!(count.load(Ordering::SeqCst), 1);
1526
1527 task.abort(true);
1528 assert!(task.terminated_kill);
1529 task.abort(true);
1530 assert_eq!(count.load(Ordering::SeqCst), 1);
1531 assert_eq!(task.state(), TaskState::Aborted);
1532 }
1533
1534 #[test]
1535 fn test_task_abort_clears_results() {
1536 let mut task = Task::new();
1539 task.shell("echo hello", None, None);
1540 task.run(None).unwrap();
1541 assert_eq!(task.max_retcode(), Some(0));
1542 assert!(!task.node_buffer("localhost").unwrap().is_empty());
1543
1544 task.abort(false);
1545 assert_eq!(task.max_retcode(), None);
1546 assert!(task.node_buffer("localhost").unwrap().is_empty());
1547 assert_eq!(task.num_timeout(), 0);
1548 }
1549
1550 #[test]
1551 fn test_task_terminate_once_on_running_abort() {
1552 let mut abort_handles = Vec::new();
1558 let mut join_handles = Vec::new();
1559
1560 for i in 1..=4u64 {
1561 let mut task = Task::new();
1562 task.shell(&format!("sleep {}", 4 + i), None, None);
1563 abort_handles.push(task.abort_handle());
1564 join_handles.push(std::thread::spawn(move || {
1565 let result = task.run(None);
1566 (result, task.state())
1567 }));
1568 }
1569
1570 std::thread::sleep(Duration::from_millis(300));
1572
1573 for handle in &abort_handles {
1575 handle.abort(true);
1576 }
1577
1578 for join in join_handles {
1582 let (result, state) = join.join().expect("task thread panicked on abort");
1583 assert!(result.is_ok(), "aborted run should unwind normally");
1584 assert_eq!(state, TaskState::Aborted);
1585 }
1586 }
1587
1588 #[test]
1589 fn test_task_abort_handle_repeated_abort_during_run() {
1590 let mut task = Task::new();
1593 task.shell("sleep 8", None, None);
1594 let handle = task.abort_handle();
1595
1596 let join = std::thread::spawn(move || {
1597 let result = task.run(None);
1598 (result, task.state())
1599 });
1600
1601 std::thread::sleep(Duration::from_millis(300));
1602 handle.abort(true);
1603 handle.abort(true);
1604 handle.abort(false);
1605
1606 let (result, state) = join.join().expect("task thread panicked on abort");
1607 assert!(result.is_ok());
1608 assert_eq!(state, TaskState::Aborted);
1609 }
1610
1611 #[test]
1614 fn test_task_run_with_timeout() {
1615 let mut task = Task::new();
1616 task.shell("sleep 100", None, None);
1617 let result = task.run(Some(Duration::from_millis(100)));
1618 assert!(result.is_err());
1619 match result.unwrap_err() {
1620 TaskError::Timeout => {}
1621 other => panic!("expected Timeout, got: {:?}", other),
1622 }
1623 }
1624
1625 #[test]
1628 fn test_task_with_custom_handler() {
1629 let started = Arc::new(AtomicBool::new(false));
1630 let closed = Arc::new(AtomicBool::new(false));
1631 let started_clone = started.clone();
1632 let closed_clone = closed.clone();
1633
1634 struct TestHandler {
1635 started: Arc<AtomicBool>,
1636 closed: Arc<AtomicBool>,
1637 }
1638
1639 impl EventHandler for TestHandler {
1640 fn on_start(&mut self, _worker: &dyn Worker) {
1641 self.started.store(true, Ordering::SeqCst);
1642 }
1643 fn on_close(&mut self, _node: &str, _rc: i32) {
1644 self.closed.store(true, Ordering::SeqCst);
1645 }
1646 }
1647
1648 let handler = TestHandler {
1649 started: started_clone,
1650 closed: closed_clone,
1651 };
1652
1653 let mut task = Task::new();
1654 task.shell("echo hello", None, Some(Box::new(handler)));
1655 task.run(None).unwrap();
1656
1657 assert!(started.load(Ordering::SeqCst), "on_start should be called");
1658 assert!(closed.load(Ordering::SeqCst), "on_close should be called");
1659 }
1660
1661 #[test]
1664 fn test_task_reset_between_runs() {
1665 let mut task = Task::new();
1666 task.shell("exit 5", None, None);
1667 task.run(None).unwrap();
1668 assert_eq!(task.max_retcode(), Some(5));
1669
1670 task.shell("exit 3", None, None);
1672 task.state = TaskState::Idle;
1674 task.run(None).unwrap();
1675 assert_eq!(task.max_retcode(), Some(5));
1679 }
1680
1681 #[test]
1684 fn test_task_topology_none_by_default() {
1685 let task = Task::new();
1686 assert!(!task.has_topology());
1687 }
1688
1689 #[test]
1690 fn test_task_default_tree_disabled() {
1691 let mut task = Task::new();
1692 assert!(!task.default_tree_is_enabled());
1693 }
1694
1695 #[test]
1698 fn test_task_node_buffer_empty() {
1699 let task = Task::new();
1700 let buf = task.node_buffer("somenode").unwrap();
1701 assert!(buf.is_empty());
1702 }
1703
1704 #[test]
1705 fn test_task_node_error_empty() {
1706 let task = Task::new();
1707 let buf = task.node_error("somenode").unwrap();
1708 assert!(buf.is_empty());
1709 }
1710
1711 #[test]
1712 fn test_task_node_buffer_disabled() {
1713 let td = TaskDefaults {
1714 stdout_msgtree: false,
1715 ..Default::default()
1716 };
1717 let task = Task::with_config(td, TaskInfo::default());
1718 let result = task.node_buffer("somenode");
1719 assert!(result.is_err());
1720 }
1721
1722 #[test]
1723 fn test_task_flush_buffers() {
1724 let mut task = Task::new();
1725 task.flush_buffers();
1726 task.flush_errors();
1727 }
1729
1730 #[test]
1733 fn test_task_worker_accessor() {
1734 let mut task = Task::new();
1735 let wid = task.shell("echo test", None, None);
1736 assert!(task.worker(wid).is_some());
1737 assert!(task.worker(999).is_none());
1738 }
1739
1740 #[test]
1741 fn test_task_worker_retcodes_accessor() {
1742 let mut task = Task::new();
1743 let wid = task.shell("echo test", None, None);
1744 task.run(None).unwrap();
1745 let retcodes = task.worker_retcodes(wid);
1746 assert!(retcodes.is_some());
1747 assert_eq!(*retcodes.unwrap().get("localhost").unwrap(), 0);
1748 }
1749
1750 #[test]
1753 fn test_task_defaults_mut() {
1754 let mut task = Task::new();
1755 assert!(!task.defaults().stderr);
1756 task.defaults_mut().stderr = true;
1757 assert!(task.defaults().stderr);
1758 }
1759
1760 #[test]
1761 fn test_task_info_mut() {
1762 let mut task = Task::new();
1763 assert_eq!(task.info().fanout, 64);
1764 task.info_mut().fanout = 256;
1765 assert_eq!(task.info().fanout, 256);
1766 }
1767
1768 #[test]
1771 fn test_get_short_hostname() {
1772 let hostname = get_short_hostname();
1773 assert!(!hostname.is_empty());
1774 assert!(!hostname.contains('.'));
1775 }
1776
1777 #[test]
1780 fn test_gathering_handler_collects_retcodes() {
1781 let mut handler = TaskGatheringHandler::new(0, None);
1782 handler.on_close("node1", 0);
1783 handler.on_close("node2", 1);
1784 assert_eq!(handler.retcodes.len(), 2);
1785 assert_eq!(handler.retcodes["node1"], 0);
1786 assert_eq!(handler.retcodes["node2"], 1);
1787 }
1788
1789 #[test]
1790 fn test_gathering_handler_collects_stdout() {
1791 let mut handler = TaskGatheringHandler::new(0, None);
1792 handler.on_read("node1", 1, b"hello");
1793 handler.on_read("node1", 1, b"world");
1794 handler.on_read("node2", 1, b"hi");
1795 assert_eq!(handler.stdout["node1"].len(), 2);
1796 assert_eq!(handler.stdout["node2"].len(), 1);
1797 }
1798
1799 #[test]
1800 fn test_gathering_handler_collects_stderr() {
1801 let mut handler = TaskGatheringHandler::new(0, None);
1802 handler.on_read("node1", 2, b"error msg");
1803 assert_eq!(handler.stderr["node1"].len(), 1);
1804 assert!(handler.stdout.is_empty());
1805 }
1806
1807 #[test]
1808 fn test_gathering_handler_collects_timeouts() {
1809 let mut handler = TaskGatheringHandler::new(0, None);
1810 handler.on_timeout("node1");
1811 handler.on_timeout("node2");
1812 assert_eq!(handler.timeouts.len(), 2);
1813 assert!(handler.timeouts.contains("node1"));
1814 }
1815
1816 #[test]
1817 fn test_gathering_handler_chains_user_handler() {
1818 let called = Arc::new(AtomicUsize::new(0));
1819 let called_clone = called.clone();
1820
1821 struct CountHandler(Arc<AtomicUsize>);
1822 impl EventHandler for CountHandler {
1823 fn on_close(&mut self, _node: &str, _rc: i32) {
1824 self.0.fetch_add(1, Ordering::SeqCst);
1825 }
1826 }
1827
1828 let user_handler = Box::new(CountHandler(called_clone));
1829 let mut handler = TaskGatheringHandler::new(0, Some(user_handler));
1830 handler.on_close("node1", 0);
1831 handler.on_close("node2", 0);
1832
1833 assert_eq!(called.load(Ordering::SeqCst), 2);
1834 assert_eq!(handler.retcodes.len(), 2);
1835 }
1836}