-
-
Notifications
You must be signed in to change notification settings - Fork 80
Generified Flow Search Algorithms #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
5f996d5
ddb608d
2b51497
bb0864e
f4de174
d414723
4c8faa4
eaf4769
0ed42f8
7f4d488
deb585b
72e49df
eb48552
e640fd3
7c1e6df
6dfbb2d
ae9581b
1331ddd
a307266
211f297
c714502
94857ac
94845a1
f4ebc6e
8e8ddfb
b90e878
b44f402
366e080
233107f
2bf2829
733ab4a
7c31794
a1ceb70
11be993
b1f0976
e598a91
7173c9c
2707059
21c6623
13ec682
4ee5cec
6e28d68
0cddee1
09a5d21
5a77748
8bb6e6a
10e2eb4
389543b
bd426b3
2efcba4
8ebbdc9
f97a08f
13ef687
094cb36
2116580
698d0d3
b8ac909
64ec619
9efad20
1338b64
6c4b8e3
0237e96
abdf296
a955451
6eb3705
ac2609d
e8e6203
b5c2b9d
866e21d
cd6e3dd
84656e5
55dc020
4af21f5
1627155
e1988cd
606de64
c9f2444
a53b13e
aa18ee5
f22aba6
9880dc8
700d009
586979c
7bb581c
7b9faca
ae47cc2
d21d67a
1678b7d
2030360
6b6eba7
33e079e
56e42eb
94e9169
6cd4215
9d05aa6
d332d8c
9067f00
8ed03ee
80b7ad4
cf4d5ac
4f1575c
0bc9ffe
4cc9206
2bcfd8b
34483c3
78e8d2c
8abdf34
3d3bf57
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ | |
|
|
||
| import javax.annotation.CheckForNull; | ||
| import javax.annotation.Nonnull; | ||
| import javax.annotation.concurrent.NotThreadSafe; | ||
| import java.util.ArrayList; | ||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
|
|
@@ -38,7 +39,6 @@ | |
| import java.util.LinkedHashSet; | ||
| import java.util.List; | ||
| import java.util.NoSuchElementException; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * Core APIs and base logic for FlowScanners that extract information from a pipeline execution. | ||
|
|
@@ -78,7 +78,7 @@ | |
| * <ul> | ||
| * <li>Implement a {@link FlowNodeVisitor} that collects metrics from each FlowNode visited, and call visitAll to extract the data.</li> | ||
| * <li>Find all flownodes of a given type (ex: stages), using {@link #filteredNodes(Collection, Collection, Predicate)}</li> | ||
| * <li>Find the first node with an Error before a specific node</li> | ||
| * <li>Find the first node with an {@link org.jenkinsci.plugins.workflow.actions.ErrorAction} before a specific node</li> | ||
| * <li>Scan through all nodes *just* within a block | ||
| * <ul> | ||
| * <li>Use the {@link org.jenkinsci.plugins.workflow.graph.BlockEndNode} as the head</li> | ||
|
|
@@ -88,6 +88,7 @@ | |
| * | ||
| * @author <samvanoort@gmail.com>Sam Van Oort</samvanoort@gmail.com> | ||
| */ | ||
| @NotThreadSafe | ||
| public abstract class AbstractFlowScanner implements Iterable <FlowNode>, Filterator<FlowNode> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, this is simultaneously an
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, it's weird: these map most closely to (reusable via reinitialization) Iterators, but implementing Iterable allows you to use them in for-each looks, which is a nice piece of syntactic sugar to assist users.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I would just make it only
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I disagree: the result of doing that would be even more confusing and would render the FlowScanners non-reusable.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not if the I still think conflating
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🐜 cf. now-hidden discussion about the inadvisability of making an |
||
|
|
||
| protected FlowNode myCurrent; | ||
|
|
@@ -235,15 +236,16 @@ public Filterator<FlowNode> filter(@Nonnull Predicate<FlowNode> filterCondition) | |
| * Find the first FlowNode within the iteration order matching a given condition | ||
| * Includes null-checking on arguments to allow directly calling with unchecked inputs (simplifies use). | ||
| * @param heads Head nodes to start walking from | ||
| * @param endNodes | ||
| * @param blackListNodes Nodes that are never visited, search stops here (bound is exclusive). | ||
| * If you want to create an inclusive bound, just use a node's parents. | ||
| * @param matchCondition Predicate to match when we've successfully found a given node type | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🐜 document that the |
||
| * @return First matching node, or null if no matches found | ||
| */ | ||
| @CheckForNull | ||
| public FlowNode findFirstMatch(@CheckForNull Collection<FlowNode> heads, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| @CheckForNull Collection<FlowNode> endNodes, | ||
| @CheckForNull Collection<FlowNode> blackListNodes, | ||
| Predicate<FlowNode> matchCondition) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if (!setup(heads, endNodes)) { | ||
| if (!setup(heads, blackListNodes)) { | ||
| return null; | ||
| } | ||
|
|
||
|
|
@@ -283,7 +285,7 @@ public FlowNode findFirstMatch(@CheckForNull FlowExecution exec, @Nonnull Predic | |
| * Includes null-checking on arguments to allow directly calling with unchecked inputs (simplifies use). | ||
| * @param heads Nodes to start iterating backward from by visiting their parents. | ||
| * @param blackList Nodes we may not visit or walk beyond. | ||
| * @param matchCondition Predicate that must be met for nodes to be included in output. | ||
| * @param matchCondition Predicate that must be met for nodes to be included in output. Input is always non-null. | ||
| * @return List of flownodes matching the predicate. | ||
| */ | ||
| @Nonnull | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ | |
| import org.jenkinsci.plugins.workflow.graph.FlowNode; | ||
|
|
||
| import javax.annotation.Nonnull; | ||
| import javax.annotation.concurrent.NotThreadSafe; | ||
| import java.util.ArrayDeque; | ||
| import java.util.Collection; | ||
| import java.util.HashSet; | ||
|
|
@@ -42,6 +43,7 @@ | |
| * <p/> The behavior is analogous to {@link org.jenkinsci.plugins.workflow.graph.FlowGraphWalker} but faster. | ||
| * @author <samvanoort@gmail.com>Sam Van Oort</samvanoort@gmail.com> | ||
| */ | ||
| @NotThreadSafe | ||
| public class DepthFirstScanner extends AbstractFlowScanner { | ||
|
|
||
| protected ArrayDeque<FlowNode> queue; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🐜 generally classes like this should be
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Actually it better still to make the whole class package-private, and just offer a factory method.)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree now, I think. In practice the benefits of being able to reuse the scanners across multiple runs probably will not be realized.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jglick To be clear: factory methods could have been a smart refactor, and easy enough to do if this review and feedback had been provided on a reasonable timeframe. Unfortunately that timeframe has long passed, and it is too late to go down that path. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |
|
|
||
| import javax.annotation.CheckForNull; | ||
| import javax.annotation.Nonnull; | ||
| import javax.annotation.concurrent.NotThreadSafe; | ||
| import java.util.ArrayDeque; | ||
| import java.util.ArrayList; | ||
| import java.util.Collection; | ||
|
|
@@ -61,6 +62,7 @@ | |
| * | ||
| * @author <samvanoort@gmail.com>Sam Van Oort</samvanoort@gmail.com> | ||
| */ | ||
| @NotThreadSafe | ||
| public class ForkScanner extends AbstractFlowScanner { | ||
|
|
||
| // Last element in stack is end of myCurrent parallel start, first is myCurrent start | ||
|
|
@@ -82,7 +84,8 @@ protected void reset() { | |
| myNext = null; | ||
| } | ||
|
|
||
| /** If true, we are walking from the flow end node and have a complete view of the flow */ | ||
| /** If true, we are walking from the flow end node and have a complete view of the flow | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🐜 add period after summary sentence |
||
| * Needed because there are implications when not walking from a finished flow (blocks without a {@link BlockEndNode})*/ | ||
| public boolean isWalkingFromFinish() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isWalkingFromEnd since we have EndNodes instead of FinishNodes?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are implications when you're dealing with an unfinished flow (incomplete blocks, for example, potentially unclosed parallels). |
||
| return walkingFromFinish; | ||
| } | ||
|
|
@@ -264,7 +267,7 @@ ArrayDeque<ParallelBlockStart> leastCommonAncestor(@Nonnull Set<FlowNode> heads) | |
|
|
||
| while (itIterator.hasNext()) { | ||
| Filterator<FlowNode> blockStartIterator = itIterator.next(); | ||
| FlowPiece myPiece = pieceIterator.next(); | ||
| FlowPiece myPiece = pieceIterator.next(); //Safe because we always remove/add with both iterators at once | ||
|
|
||
| // Welp we hit the end of a branch | ||
| if (!blockStartIterator.hasNext()) { | ||
|
|
@@ -316,7 +319,6 @@ ArrayDeque<ParallelBlockStart> leastCommonAncestor(@Nonnull Set<FlowNode> heads) | |
| @Override | ||
| protected void setHeads(@Nonnull Collection<FlowNode> heads) { | ||
| if (heads.size() > 1) { | ||
| //throw new IllegalArgumentException("ForkedFlowScanner can't handle multiple head nodes yet"); | ||
| parallelBlockStartStack = leastCommonAncestor(new LinkedHashSet<FlowNode>(heads)); | ||
| currentParallelStart = parallelBlockStartStack.pop(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And what if we have an empty parallel block?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think should be okay, but adding a testcase.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I see leastCommonAncestor() may return empty deque if @svanoort Could you please verify since the code is not so easy for reading?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oleg-nenashev Verified this happens via test. leastCommonAncestor loses its mind here completely -- could have sworn we had a test for it, but now we do and a fix in a few min. |
||
| currentParallelStartNode = currentParallelStart.forkStart; | ||
|
|
@@ -415,8 +417,8 @@ protected FlowNode next(@Nonnull FlowNode current, @Nonnull Collection<FlowNode> | |
|
|
||
| // First we look at the parents of the current node if present | ||
| List<FlowNode> parents = current.getParents(); | ||
| if (parents == null || parents.size() == 0) { | ||
| // welp done with this node, guess we consult the queue? | ||
| if (parents.isEmpty()) { | ||
| // welp, we're done with this node, guess we consult the queue? | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } else if (parents.size() == 1) { | ||
| FlowNode p = parents.get(0); | ||
| if (p == currentParallelStartNode) { | ||
|
|
@@ -436,7 +438,7 @@ protected FlowNode next(@Nonnull FlowNode current, @Nonnull Collection<FlowNode> | |
| return possibleOutput; | ||
| } | ||
| } else { | ||
| throw new IllegalStateException("Found a FlowNode with multiple parents that isn't the end of a block! "+ this.myCurrent.toString()); | ||
| throw new IllegalStateException("Found a FlowNode with multiple parents that isn't the end of a block! "+ this.myCurrent); | ||
| } | ||
|
|
||
| if (currentParallelStart != null && currentParallelStart.unvisited.size() > 0) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong HTML tags?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oleg-nenashev ? Likely fixed in #10 anyway.