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}