dep_graph/
lib.rs

1//! # Library to perform operations over dependency graphs.
2//!
3//! This library allow running iterative operations over a dependency graph in
4//! the correct evaluation order, or will fail if there are a circular
5//! dependencies.
6//!
7//! To use this library, you create a list of [Nodes](trait.Node.html)
8//! containing dependency information (which node depends on which). You then
9//! create a [DepGraph](struct.DepGraph.html) which will allow you to traverse
10//! the graph so that you will always get an item for which all dependencies
11//! have been processed.
12//!
13//! ## Processing order
14//!
15//! DepGraphs have two methods: one for sequential operations and one for
16//! parallel (multi-threaded) operations. In the first case, it's easy to know
17//! in which order nodes can be processed, as only one node will be processed
18//! at a time. However, in parallel operations, we need to know if a given node
19//! is done processing.
20//!
21//! This leads to a situation where a given worker thread might not be able to
22//! pull a node temporarily, as it needs to wait for another worker to finish
23//! processing another node.
24//!
25//! Let's look at the following case:
26//!
27//! ```text,no_run
28//! (A) <-|
29//!       |-- [E] <-|-- [G]
30//! (B) <-|         |
31//!       |-- [F] <-|-- [H]
32//! [C] <-|
33//! ```
34//!
35//! In this case, the nodes __E__ and __F__ are dependent on __A__, __B__ and
36//! __C__ and __G__ and __H__ are dependent on both __E__ and __F__. If we
37//! process the nodes with two workers, they might pick up nodes A and B first.
38//! Since these nodes don't have any dependencies, there is no problem right
39//! now.
40//!
41//! ```text,no_run
42//! [ ] <-|
43//!       |-- [E] <-|-- [G]
44//! [ ] <-|         |
45//!       |-- [F] <-|-- [H]
46//! (C) <-|
47//! ```
48//!
49//! When one of the worker is done, it can immediately start working on node
50//! __C__, as it does not have any dependencies. However, when the second
51//! worker is done, there are no available nodes for processing: we need to
52//! wait until __C__ is processed before we can start working on __E__ or
53//! __F__. One of the worker will then stay idle until the other one is done.
54//!
55//! ```text,no_run
56//! [ ] <-|
57//!       |-- (E) <-|-- [G]
58//! [ ] <-|         |
59//!       |-- (F) <-|-- [H]
60//! [ ] <-|
61//! ```
62//!
63//! Once that is done, both workers can work on __E__ and __F__. However, if
64//! __E__ takes only a fraction of the time compared to __F__, we will end up
65//! in the same situation, as there are no nodes without un-processed
66//! dependencies.
67//!
68//! ## Parallel iterators
69//!
70//! This library supports using `rayon` as an optional dependency. When using
71//! `rayon`, [DepGraph](struct.DepGraph.html) supports a new method
72//! `into_par_iter()` that will process the dependency graph across multiple
73//! threads.
74//!
75//! Under the hood, it works by creating a dispatcher thread and a series of
76//! crossbeam channels to dispatch nodes and notify the dispatcher when nodes
77//! are done processing.
78//!
79//! Because of that, iterator functions receive a
80//! [Wrapper](struct.Wrapper.html) instead of the item itself. The underlying
81//! item is available by using the dereference operator (`*wrapper`).
82//!
83//! ## Basic usage
84//!
85//! ```rust
86//! use dep_graph::{Node, DepGraph};
87//! #[cfg(feature = "parallel")]
88//! use rayon::prelude::*;
89//!
90//! // Create a list of nodes
91//! let mut root = Node::new("root");
92//! let mut dep1 = Node::new("dep1");
93//! let mut dep2 = Node::new("dep2");
94//! let leaf = Node::new("leaf");
95//!
96//! // Map their connections
97//! root.add_dep(dep1.id());
98//! root.add_dep(dep2.id());
99//! dep1.add_dep(leaf.id());
100//! dep2.add_dep(leaf.id());
101//!
102//! // Create a graph
103//! let nodes = vec![root, dep1, dep2, leaf];
104//!
105//! // Print the name of all nodes in the dependency graph.
106//! // This will parse the dependency graph sequentially
107//! {
108//!     let graph = DepGraph::new(&nodes);
109//!     graph
110//!         .into_iter()
111//!         .for_each(|node| {
112//!             println!("{:?}", node)
113//!         });
114//! }
115//!
116//! // This is the same as the previous command, excepts it leverages rayon
117//! // to process them in parallel as much as possible.
118//! #[cfg(feature = "parallel")]
119//! {
120//!     let graph = DepGraph::new(&nodes);
121//!     graph
122//!         .into_par_iter()
123//!         .for_each(|node| {
124//!             // The node is a dep_graph::Wrapper object, not a String.
125//!             // We need to use `*node` to get its value.
126//!             println!("{:?}", *node)
127//!         });
128//! }
129//! ```
130
131pub mod error;
132mod graph;
133#[cfg(feature = "parallel")]
134mod graph_par;
135mod node;
136
137pub use graph::DepGraph;
138#[cfg(feature = "parallel")]
139pub use graph_par::Wrapper;
140pub use node::Node;
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    #[cfg(feature = "parallel")]
146    use rayon::prelude::*;
147    #[cfg(feature = "parallel")]
148    use std::time::Duration;
149
150    /// Run against a diamond graph
151    ///
152    /// ```no_run
153    ///   1
154    ///  / \
155    /// 2   3
156    ///  \ /
157    ///   4
158    /// ```
159    #[cfg(feature = "parallel")]
160    #[test]
161    fn par_diamond_graph() {
162        let mut n1 = Node::new("1");
163        let mut n2 = Node::new("2");
164        let mut n3 = Node::new("3");
165        let n4 = Node::new("4");
166
167        n1.add_dep(n2.id());
168        n1.add_dep(n3.id());
169        n2.add_dep(n4.id());
170        n3.add_dep(n4.id());
171
172        let deps = vec![n1, n2, n3, n4];
173
174        let r = DepGraph::new(&deps);
175        let result = r.into_par_iter().map(|_| true).collect::<Vec<bool>>();
176
177        assert_eq!(result.len(), deps.len());
178    }
179
180    #[cfg(feature = "parallel")]
181    #[test]
182    fn par_diamond_graph_steps() {
183        let mut n1 = Node::new("1");
184        let mut n2 = Node::new("2");
185        let mut n3 = Node::new("3");
186        let n4 = Node::new("4");
187
188        n1.add_dep(n2.id());
189        n1.add_dep(n3.id());
190        n2.add_dep(n4.id());
191        n3.add_dep(n4.id());
192
193        let deps = vec![n1, n2, n3, n4];
194
195        let r = DepGraph::new(&deps);
196        let result = r
197            .into_par_iter()
198            .map(|node_id| (*node_id).parse::<u64>().unwrap())
199            .reduce(|| 0, |acc, x| acc + x);
200
201        assert_eq!(result, 10);
202    }
203
204    #[cfg(feature = "parallel")]
205    #[test]
206    fn par_diamond_graph_with_timeout() {
207        let mut n1 = Node::new("1");
208        let mut n2 = Node::new("2");
209        let mut n3 = Node::new("3");
210        let n4 = Node::new("4");
211
212        n1.add_dep(n2.id());
213        n1.add_dep(n3.id());
214        n2.add_dep(n4.id());
215        n3.add_dep(n4.id());
216
217        let deps = vec![n1, n2, n3, n4];
218
219        let r = DepGraph::new(&deps);
220        let result = r
221            .into_par_iter()
222            .with_timeout(Duration::from_secs(2))
223            .map(|_| true)
224            .collect::<Vec<bool>>();
225
226        assert_eq!(result.len(), deps.len());
227    }
228
229    #[test]
230    fn iter_diamond_graph() {
231        let mut n1 = Node::new("1");
232        let mut n2 = Node::new("2");
233        let mut n3 = Node::new("3");
234        let n4 = Node::new("4");
235
236        n1.add_dep(n2.id());
237        n1.add_dep(n3.id());
238        n2.add_dep(n4.id());
239        n3.add_dep(n4.id());
240
241        let deps = vec![n1, n2, n3, n4];
242
243        let r = DepGraph::new(&deps);
244        let result = r.into_iter().map(|_| true).collect::<Vec<bool>>();
245
246        assert_eq!(result.len(), deps.len());
247    }
248
249    /// 1 000 nodes with 999 depending on one
250    #[cfg(feature = "parallel")]
251    #[test]
252    fn par_thousand_graph() {
253        let mut nodes: Vec<Node<_>> = (0..1000).map(|i| Node::new(format!("{}", i))).collect();
254        for i in 1..1000 {
255            nodes[i].add_dep("0".to_string());
256        }
257
258        let r = DepGraph::new(&nodes);
259        let result = r.into_par_iter().map(|_| true).collect::<Vec<bool>>();
260
261        assert_eq!(result.len(), nodes.len());
262    }
263
264    #[test]
265    fn iter_thousand_graph() {
266        let mut nodes: Vec<Node<_>> = (0..1000).map(|i| Node::new(format!("{}", i))).collect();
267        for i in 1..1000 {
268            nodes[i].add_dep("0".to_string());
269        }
270
271        let r = DepGraph::new(&nodes);
272        let result = r.into_iter().map(|_| true).collect::<Vec<bool>>();
273
274        assert_eq!(result.len(), nodes.len());
275    }
276
277    // #[test]
278    // #[should_panic]
279    // fn par_circular_graph() {
280    //     let mut n1 = Node::new("1");
281    //     let mut n2 = Node::new("2");
282    //     let mut n3 = Node::new("3");
283
284    //     n1.add_dep(n2.id());
285    //     n2.add_dep(n3.id());
286    //     n3.add_dep(n1.id());
287
288    //     let deps = vec![n1, n2, n3];
289
290    //     // This should return an exception
291    //     let r = DepGraph::new(&deps);
292    //     r.into_par_iter().for_each(|_node_id| {});
293    // }
294}