OOZIE-3561 Forkjoin validation is slow when there are many actions in chain (dionusos, pbacsko via asalamon74)
diff --git a/core/src/main/java/org/apache/oozie/workflow/lite/LiteWorkflowValidator.java b/core/src/main/java/org/apache/oozie/workflow/lite/LiteWorkflowValidator.java
index eceb019..e6102d8 100644
--- a/core/src/main/java/org/apache/oozie/workflow/lite/LiteWorkflowValidator.java
+++ b/core/src/main/java/org/apache/oozie/workflow/lite/LiteWorkflowValidator.java
@@ -34,12 +34,14 @@
import org.apache.oozie.service.ActionService;
import org.apache.oozie.service.Services;
import org.apache.oozie.util.ParamChecker;
+import org.apache.oozie.util.XLog;
import org.apache.oozie.util.XmlUtils;
import org.apache.oozie.workflow.WorkflowException;
import org.jdom.Element;
import org.jdom.JDOMException;
public class LiteWorkflowValidator {
+ private static XLog LOG = XLog.getLog(LiteWorkflowValidator.class);
public void validateWorkflow(LiteWorkflowApp app, boolean validateForkJoin) throws WorkflowException {
NodeDef startNode = app.getNode(StartNodeDef.START);
@@ -64,7 +66,8 @@
true,
new ArrayDeque<String>(),
new HashMap<String, String>(),
- new HashMap<String, Optional<String>>());
+ new HashMap<String, Optional<String>>(),
+ new HashSet<>());
}
}
@@ -135,11 +138,18 @@
* already been visited at least once before
* @param forkJoins Map that contains a mapping of fork-join node pairs.
* @param nodeAndDecisionParents Map that contains a mapping of nodes and their eldest decision node
+ * @param visitedNodes contains the nodes that have been already visited & validated (except Join/End nodes)
* @throws WorkflowException If there is any of the constraints described above is violated
*/
- private void validateForkJoin(LiteWorkflowApp app, NodeDef node, NodeDef currentFork, String topDecisionParent,
- boolean okPath, Deque<String> path, Map<String, String> forkJoins,
- Map<String, Optional<String>> nodeAndDecisionParents) throws WorkflowException {
+ private void validateForkJoin(LiteWorkflowApp app,
+ NodeDef node,
+ NodeDef currentFork,
+ String topDecisionParent,
+ boolean okPath,
+ Deque<String> path,
+ Map<String, String> forkJoins,
+ Map<String, Optional<String>> nodeAndDecisionParents,
+ Set<NodeDef> visitedNodes) throws WorkflowException {
final String nodeName = node.getName();
path.addLast(nodeName);
@@ -186,6 +196,33 @@
}
}
+ /* Memoization part: don't re-walk paths that have been visited already. This prevents
+ * exponential runtime in specific cases.
+ *
+ * There are three edge-cases that we have to keep in mind:
+ * 1. This part of the code cannot be above the "okTo" verification part. Otherwise we would
+ * accept WFs where multiple "ok" paths lead to the same node.
+ *
+ * 2. We don't store Join nodes. Firstly, we don't recurse from Join nodes anyway.
+ * Also, it's necessary to reach fork-join mapping verification below,
+ * so that we can throw errors "E0742" or "E0758" if needed.
+ *
+ * 3. We don't store End nodes. Similarly to Join, no recursion occurs after End. Plus, we
+ * could miss the erroneous condition "E0737" if we previously arrived at End from a valid path.
+ */
+ if (visitedNodes.contains(node)) {
+ LOG.debug("Skipping node because it's been validated: " + nodeName);
+ path.remove(nodeName);
+ return;
+ } else {
+ if (node instanceof JoinNodeDef || node instanceof EndNodeDef) {
+ LOG.debug("Not storing node because it's a Join or End: " + nodeName);
+ } else {
+ visitedNodes.add(node);
+ LOG.debug("Storing node as visited: " + nodeName);
+ }
+ }
+
/* Fork-Join validation logic:
*
* At each Fork node, we recurse to every possible paths, changing the "currentFork" variable to the Fork node. We stop
@@ -211,7 +248,8 @@
for (String t : transitions) {
NodeDef transition = app.getNode(t);
- validateForkJoin(app, transition, node, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents);
+ validateForkJoin(app, transition, node, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents,
+ visitedNodes);
}
// get the Join node for this ForkNode & validate it (we must have only one)
@@ -222,7 +260,8 @@
List<String> joinTransitions = app.getNode(joins.iterator().next()).getTransitions();
NodeDef next = app.getNode(joinTransitions.get(0));
- validateForkJoin(app, next, currentFork, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents);
+ validateForkJoin(app, next, currentFork, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents,
+ visitedNodes);
} else if (node instanceof JoinNodeDef) {
if (currentFork == null) {
throw new WorkflowException(ErrorCode.E0742, node.getName());
@@ -247,7 +286,7 @@
for (String t : transitions) {
NodeDef transition = app.getNode(t);
validateForkJoin(app, transition, currentFork, parentDecisionNode, okPath, path, forkJoins,
- nodeAndDecisionParents);
+ nodeAndDecisionParents, visitedNodes);
}
} else if (node instanceof KillNodeDef) {
// no op
@@ -262,19 +301,21 @@
} else if (node instanceof ActionNodeDef) {
String transition = node.getTransitions().get(0); // "ok to" transition
NodeDef okNode = app.getNode(transition);
- validateForkJoin(app, okNode, currentFork, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents);
+ validateForkJoin(app, okNode, currentFork, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents,
+ visitedNodes);
transition = node.getTransitions().get(1); // "error to" transition
NodeDef errorNode = app.getNode(transition);
- validateForkJoin(app, errorNode, currentFork, topDecisionParent, false, path, forkJoins, nodeAndDecisionParents);
+ validateForkJoin(app, errorNode, currentFork, topDecisionParent, false, path, forkJoins, nodeAndDecisionParents,
+ visitedNodes);
} else if (node instanceof StartNodeDef) {
String transition = node.getTransitions().get(0); // start always has only 1 transition
NodeDef tranNode = app.getNode(transition);
- validateForkJoin(app, tranNode, currentFork, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents);
+ validateForkJoin(app, tranNode, currentFork, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents,
+ visitedNodes);
} else {
throw new WorkflowException(ErrorCode.E0740, node.getClass());
}
-
path.remove(nodeName);
}
diff --git a/core/src/test/java/org/apache/oozie/workflow/lite/TestLiteWorkflowAppParser.java b/core/src/test/java/org/apache/oozie/workflow/lite/TestLiteWorkflowAppParser.java
index 1389d3e..157d406 100644
--- a/core/src/test/java/org/apache/oozie/workflow/lite/TestLiteWorkflowAppParser.java
+++ b/core/src/test/java/org/apache/oozie/workflow/lite/TestLiteWorkflowAppParser.java
@@ -1292,6 +1292,75 @@
}
}
+ public void testMultiplePathsToEnd() throws Exception {
+ // Makes sure that despite using memoization, the validator
+ // still finds incorrect state transition to "end" nodes
+ LiteWorkflowAppParser parser = newLiteWorkflowAppParser();
+
+ LiteWorkflowApp def = new LiteWorkflowApp("name", "def",
+ new StartNodeDef(LiteWorkflowStoreService.LiteControlNodeHandler.class, "one"))
+ .addNode(new ActionNodeDef("one", dummyConf, TestActionNodeHandler.class, "end", "f"))
+ .addNode(new ForkNodeDef("f", LiteWorkflowStoreService.LiteControlNodeHandler.class,
+ Arrays.asList(new String[]{"two", "three"})))
+ .addNode(new ActionNodeDef("two", dummyConf, TestActionNodeHandler.class, "end", "k")) // invalid
+ .addNode(new ActionNodeDef("three", dummyConf, TestActionNodeHandler.class, "j", "k"))
+ .addNode(new JoinNodeDef("j", LiteWorkflowStoreService.LiteControlNodeHandler.class, "end"))
+ .addNode(new KillNodeDef("k", "kill", LiteWorkflowStoreService.LiteControlNodeHandler.class))
+ .addNode(new EndNodeDef("end", LiteWorkflowStoreService.LiteControlNodeHandler.class));
+
+ try {
+ invokeForkJoin(parser, def);
+ fail("Expected to catch an exception but did not encounter any");
+ } catch (WorkflowException we) {
+ assertEquals(ErrorCode.E0737, we.getErrorCode());
+ assertTrue(we.getMessage().contains("[two]"));
+ }
+ }
+
+ public void testRuntimeWith20Actions() throws Exception {
+ testRuntimeWithActions("wf-actions-20.xml");
+ }
+
+ public void testRuntimeWith40Actions() throws Exception {
+ testRuntimeWithActions("wf-actions-40.xml");
+ }
+
+ public void testRuntimeWith80Actions() throws Exception {
+ testRuntimeWithActions("wf-actions-80.xml");
+ }
+
+ @SuppressWarnings("deprecation")
+ private void testRuntimeWithActions(String workflowXml) throws Exception {
+ LiteWorkflowAppParser parser = newLiteWorkflowAppParser();
+
+ final AtomicBoolean failure = new AtomicBoolean(false);
+ final AtomicBoolean finished = new AtomicBoolean(false);
+
+ Runnable r = () -> {
+ try {
+ parser.validateAndParse(IOUtils.getResourceAsReader(
+ workflowXml, -1), new Configuration());
+ } catch (final Exception e) {
+ e.printStackTrace();
+ failure.set(true);
+ }
+
+ finished.set(true);
+ };
+
+ Thread t = new Thread(r);
+ t.setName("Workflow validator thread for " + workflowXml);
+ t.start();
+ t.join((long) (2000 * XTestCase.WAITFOR_RATIO));
+
+ if (!finished.get()) {
+ t.stop(); // don't let the validation keep running in the background which causes high CPU load
+ fail("Workflow validation did not finish in time");
+ }
+
+ assertFalse("Workflow validation failed", failure.get());
+ }
+
private void invokeForkJoin(LiteWorkflowAppParser parser, LiteWorkflowApp def) throws WorkflowException {
LiteWorkflowValidator validator = new LiteWorkflowValidator();
validator.validateWorkflow(def, true);
diff --git a/core/src/test/resources/wf-actions-20.xml b/core/src/test/resources/wf-actions-20.xml
new file mode 100644
index 0000000..645fdb3
--- /dev/null
+++ b/core/src/test/resources/wf-actions-20.xml
@@ -0,0 +1,43 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<workflow-app xmlns="uri:oozie:workflow:0.5" name="test-wf">
+ <start to="a1"/>
+
+ <action name="a1"><fs><mkdir path='/tmp'/></fs><ok to="a2"/><error to="a2"/></action>
+ <action name="a2"><fs><mkdir path='/tmp'/></fs><ok to="a3"/><error to="a3"/></action>
+ <action name="a3"><fs><mkdir path='/tmp'/></fs><ok to="a4"/><error to="a4"/></action>
+ <action name="a4"><fs><mkdir path='/tmp'/></fs><ok to="a5"/><error to="a5"/></action>
+ <action name="a5"><fs><mkdir path='/tmp'/></fs><ok to="a6"/><error to="a6"/></action>
+ <action name="a6"><fs><mkdir path='/tmp'/></fs><ok to="a7"/><error to="a7"/></action>
+ <action name="a7"><fs><mkdir path='/tmp'/></fs><ok to="a8"/><error to="a8"/></action>
+ <action name="a8"><fs><mkdir path='/tmp'/></fs><ok to="a9"/><error to="a9"/></action>
+ <action name="a9"><fs><mkdir path='/tmp'/></fs><ok to="a10"/><error to="a10"/></action>
+ <action name="a10"><fs><mkdir path='/tmp'/></fs><ok to="a11"/><error to="a11"/></action>
+ <action name="a11"><fs><mkdir path='/tmp'/></fs><ok to="a12"/><error to="a12"/></action>
+ <action name="a12"><fs><mkdir path='/tmp'/></fs><ok to="a13"/><error to="a13"/></action>
+ <action name="a13"><fs><mkdir path='/tmp'/></fs><ok to="a14"/><error to="a14"/></action>
+ <action name="a14"><fs><mkdir path='/tmp'/></fs><ok to="a15"/><error to="a15"/></action>
+ <action name="a15"><fs><mkdir path='/tmp'/></fs><ok to="a16"/><error to="a16"/></action>
+ <action name="a16"><fs><mkdir path='/tmp'/></fs><ok to="a17"/><error to="a17"/></action>
+ <action name="a17"><fs><mkdir path='/tmp'/></fs><ok to="a18"/><error to="a18"/></action>
+ <action name="a18"><fs><mkdir path='/tmp'/></fs><ok to="a19"/><error to="a19"/></action>
+ <action name="a19"><fs><mkdir path='/tmp'/></fs><ok to="a20"/><error to="a20"/></action>
+ <action name="a20"><fs><mkdir path='/tmp'/></fs><ok to="z"/><error to="z"/></action>
+
+ <end name="z"/>
+</workflow-app>
\ No newline at end of file
diff --git a/core/src/test/resources/wf-actions-40.xml b/core/src/test/resources/wf-actions-40.xml
new file mode 100644
index 0000000..256c19c
--- /dev/null
+++ b/core/src/test/resources/wf-actions-40.xml
@@ -0,0 +1,63 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<workflow-app xmlns="uri:oozie:workflow:0.5" name="test-wf">
+ <start to="a1"/>
+
+ <action name="a1"><fs><mkdir path='/tmp'/></fs><ok to="a2"/><error to="a2"/></action>
+ <action name="a2"><fs><mkdir path='/tmp'/></fs><ok to="a3"/><error to="a3"/></action>
+ <action name="a3"><fs><mkdir path='/tmp'/></fs><ok to="a4"/><error to="a4"/></action>
+ <action name="a4"><fs><mkdir path='/tmp'/></fs><ok to="a5"/><error to="a5"/></action>
+ <action name="a5"><fs><mkdir path='/tmp'/></fs><ok to="a6"/><error to="a6"/></action>
+ <action name="a6"><fs><mkdir path='/tmp'/></fs><ok to="a7"/><error to="a7"/></action>
+ <action name="a7"><fs><mkdir path='/tmp'/></fs><ok to="a8"/><error to="a8"/></action>
+ <action name="a8"><fs><mkdir path='/tmp'/></fs><ok to="a9"/><error to="a9"/></action>
+ <action name="a9"><fs><mkdir path='/tmp'/></fs><ok to="a10"/><error to="a10"/></action>
+ <action name="a10"><fs><mkdir path='/tmp'/></fs><ok to="a11"/><error to="a11"/></action>
+ <action name="a11"><fs><mkdir path='/tmp'/></fs><ok to="a12"/><error to="a12"/></action>
+ <action name="a12"><fs><mkdir path='/tmp'/></fs><ok to="a13"/><error to="a13"/></action>
+ <action name="a13"><fs><mkdir path='/tmp'/></fs><ok to="a14"/><error to="a14"/></action>
+ <action name="a14"><fs><mkdir path='/tmp'/></fs><ok to="a15"/><error to="a15"/></action>
+ <action name="a15"><fs><mkdir path='/tmp'/></fs><ok to="a16"/><error to="a16"/></action>
+ <action name="a16"><fs><mkdir path='/tmp'/></fs><ok to="a17"/><error to="a17"/></action>
+ <action name="a17"><fs><mkdir path='/tmp'/></fs><ok to="a18"/><error to="a18"/></action>
+ <action name="a18"><fs><mkdir path='/tmp'/></fs><ok to="a19"/><error to="a19"/></action>
+ <action name="a19"><fs><mkdir path='/tmp'/></fs><ok to="a20"/><error to="a20"/></action>
+ <action name="a20"><fs><mkdir path='/tmp'/></fs><ok to="a21"/><error to="a21"/></action>
+ <action name="a21"><fs><mkdir path='/tmp'/></fs><ok to="a22"/><error to="a22"/></action>
+ <action name="a22"><fs><mkdir path='/tmp'/></fs><ok to="a23"/><error to="a23"/></action>
+ <action name="a23"><fs><mkdir path='/tmp'/></fs><ok to="a24"/><error to="a24"/></action>
+ <action name="a24"><fs><mkdir path='/tmp'/></fs><ok to="a25"/><error to="a25"/></action>
+ <action name="a25"><fs><mkdir path='/tmp'/></fs><ok to="a26"/><error to="a26"/></action>
+ <action name="a26"><fs><mkdir path='/tmp'/></fs><ok to="a27"/><error to="a27"/></action>
+ <action name="a27"><fs><mkdir path='/tmp'/></fs><ok to="a28"/><error to="a28"/></action>
+ <action name="a28"><fs><mkdir path='/tmp'/></fs><ok to="a29"/><error to="a29"/></action>
+ <action name="a29"><fs><mkdir path='/tmp'/></fs><ok to="a30"/><error to="a30"/></action>
+ <action name="a30"><fs><mkdir path='/tmp'/></fs><ok to="a31"/><error to="a31"/></action>
+ <action name="a31"><fs><mkdir path='/tmp'/></fs><ok to="a32"/><error to="a32"/></action>
+ <action name="a32"><fs><mkdir path='/tmp'/></fs><ok to="a33"/><error to="a33"/></action>
+ <action name="a33"><fs><mkdir path='/tmp'/></fs><ok to="a34"/><error to="a34"/></action>
+ <action name="a34"><fs><mkdir path='/tmp'/></fs><ok to="a35"/><error to="a35"/></action>
+ <action name="a35"><fs><mkdir path='/tmp'/></fs><ok to="a36"/><error to="a36"/></action>
+ <action name="a36"><fs><mkdir path='/tmp'/></fs><ok to="a37"/><error to="a37"/></action>
+ <action name="a37"><fs><mkdir path='/tmp'/></fs><ok to="a38"/><error to="a38"/></action>
+ <action name="a38"><fs><mkdir path='/tmp'/></fs><ok to="a39"/><error to="a39"/></action>
+ <action name="a39"><fs><mkdir path='/tmp'/></fs><ok to="a40"/><error to="a40"/></action>
+ <action name="a40"><fs><mkdir path='/tmp'/></fs><ok to="z"/><error to="z"/></action>
+
+ <end name="z"/>
+</workflow-app>
\ No newline at end of file
diff --git a/core/src/test/resources/wf-actions-80.xml b/core/src/test/resources/wf-actions-80.xml
new file mode 100644
index 0000000..95a623b
--- /dev/null
+++ b/core/src/test/resources/wf-actions-80.xml
@@ -0,0 +1,102 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<workflow-app xmlns="uri:oozie:workflow:0.5" name="test-wf">
+ <start to="a1"/>
+
+ <action name="a1"><fs><mkdir path='/tmp'/></fs><ok to="a2"/><error to="a2"/></action>
+ <action name="a2"><fs><mkdir path='/tmp'/></fs><ok to="a3"/><error to="a3"/></action>
+ <action name="a3"><fs><mkdir path='/tmp'/></fs><ok to="a4"/><error to="a4"/></action>
+ <action name="a4"><fs><mkdir path='/tmp'/></fs><ok to="a5"/><error to="a5"/></action>
+ <action name="a5"><fs><mkdir path='/tmp'/></fs><ok to="a6"/><error to="a6"/></action>
+ <action name="a6"><fs><mkdir path='/tmp'/></fs><ok to="a7"/><error to="a7"/></action>
+ <action name="a7"><fs><mkdir path='/tmp'/></fs><ok to="a8"/><error to="a8"/></action>
+ <action name="a8"><fs><mkdir path='/tmp'/></fs><ok to="a9"/><error to="a9"/></action>
+ <action name="a9"><fs><mkdir path='/tmp'/></fs><ok to="a10"/><error to="a10"/></action>
+ <action name="a10"><fs><mkdir path='/tmp'/></fs><ok to="a11"/><error to="a11"/></action>
+ <action name="a11"><fs><mkdir path='/tmp'/></fs><ok to="a12"/><error to="a12"/></action>
+ <action name="a12"><fs><mkdir path='/tmp'/></fs><ok to="a13"/><error to="a13"/></action>
+ <action name="a13"><fs><mkdir path='/tmp'/></fs><ok to="a14"/><error to="a14"/></action>
+ <action name="a14"><fs><mkdir path='/tmp'/></fs><ok to="a15"/><error to="a15"/></action>
+ <action name="a15"><fs><mkdir path='/tmp'/></fs><ok to="a16"/><error to="a16"/></action>
+ <action name="a16"><fs><mkdir path='/tmp'/></fs><ok to="a17"/><error to="a17"/></action>
+ <action name="a17"><fs><mkdir path='/tmp'/></fs><ok to="a18"/><error to="a18"/></action>
+ <action name="a18"><fs><mkdir path='/tmp'/></fs><ok to="a19"/><error to="a19"/></action>
+ <action name="a19"><fs><mkdir path='/tmp'/></fs><ok to="a20"/><error to="a20"/></action>
+ <action name="a20"><fs><mkdir path='/tmp'/></fs><ok to="a21"/><error to="a21"/></action>
+ <action name="a21"><fs><mkdir path='/tmp'/></fs><ok to="a22"/><error to="a22"/></action>
+ <action name="a22"><fs><mkdir path='/tmp'/></fs><ok to="a23"/><error to="a23"/></action>
+ <action name="a23"><fs><mkdir path='/tmp'/></fs><ok to="a24"/><error to="a24"/></action>
+ <action name="a24"><fs><mkdir path='/tmp'/></fs><ok to="a25"/><error to="a25"/></action>
+ <action name="a25"><fs><mkdir path='/tmp'/></fs><ok to="a26"/><error to="a26"/></action>
+ <action name="a26"><fs><mkdir path='/tmp'/></fs><ok to="a27"/><error to="a27"/></action>
+ <action name="a27"><fs><mkdir path='/tmp'/></fs><ok to="a28"/><error to="a28"/></action>
+ <action name="a28"><fs><mkdir path='/tmp'/></fs><ok to="a29"/><error to="a29"/></action>
+ <action name="a29"><fs><mkdir path='/tmp'/></fs><ok to="a30"/><error to="a30"/></action>
+ <action name="a30"><fs><mkdir path='/tmp'/></fs><ok to="a31"/><error to="a31"/></action>
+ <action name="a31"><fs><mkdir path='/tmp'/></fs><ok to="a32"/><error to="a32"/></action>
+ <action name="a32"><fs><mkdir path='/tmp'/></fs><ok to="a33"/><error to="a33"/></action>
+ <action name="a33"><fs><mkdir path='/tmp'/></fs><ok to="a34"/><error to="a34"/></action>
+ <action name="a34"><fs><mkdir path='/tmp'/></fs><ok to="a35"/><error to="a35"/></action>
+ <action name="a35"><fs><mkdir path='/tmp'/></fs><ok to="a36"/><error to="a36"/></action>
+ <action name="a36"><fs><mkdir path='/tmp'/></fs><ok to="a37"/><error to="a37"/></action>
+ <action name="a37"><fs><mkdir path='/tmp'/></fs><ok to="a38"/><error to="a38"/></action>
+ <action name="a38"><fs><mkdir path='/tmp'/></fs><ok to="a39"/><error to="a39"/></action>
+ <action name="a39"><fs><mkdir path='/tmp'/></fs><ok to="a40"/><error to="a40"/></action>
+ <action name="a40"><fs><mkdir path='/tmp'/></fs><ok to="a41"/><error to="a41"/></action>
+ <action name="a41"><fs><mkdir path='/tmp'/></fs><ok to="a42"/><error to="a42"/></action>
+ <action name="a42"><fs><mkdir path='/tmp'/></fs><ok to="a43"/><error to="a43"/></action>
+ <action name="a43"><fs><mkdir path='/tmp'/></fs><ok to="a44"/><error to="a44"/></action>
+ <action name="a44"><fs><mkdir path='/tmp'/></fs><ok to="a45"/><error to="a45"/></action>
+ <action name="a45"><fs><mkdir path='/tmp'/></fs><ok to="a46"/><error to="a46"/></action>
+ <action name="a46"><fs><mkdir path='/tmp'/></fs><ok to="a47"/><error to="a47"/></action>
+ <action name="a47"><fs><mkdir path='/tmp'/></fs><ok to="a48"/><error to="a48"/></action>
+ <action name="a48"><fs><mkdir path='/tmp'/></fs><ok to="a49"/><error to="a49"/></action>
+ <action name="a49"><fs><mkdir path='/tmp'/></fs><ok to="a50"/><error to="a50"/></action>
+ <action name="a50"><fs><mkdir path='/tmp'/></fs><ok to="a51"/><error to="a51"/></action>
+ <action name="a51"><fs><mkdir path='/tmp'/></fs><ok to="a52"/><error to="a52"/></action>
+ <action name="a52"><fs><mkdir path='/tmp'/></fs><ok to="a53"/><error to="a53"/></action>
+ <action name="a53"><fs><mkdir path='/tmp'/></fs><ok to="a54"/><error to="a54"/></action>
+ <action name="a54"><fs><mkdir path='/tmp'/></fs><ok to="a55"/><error to="a55"/></action>
+ <action name="a55"><fs><mkdir path='/tmp'/></fs><ok to="a56"/><error to="a56"/></action>
+ <action name="a56"><fs><mkdir path='/tmp'/></fs><ok to="a57"/><error to="a57"/></action>
+ <action name="a57"><fs><mkdir path='/tmp'/></fs><ok to="a58"/><error to="a58"/></action>
+ <action name="a58"><fs><mkdir path='/tmp'/></fs><ok to="a59"/><error to="a59"/></action>
+ <action name="a59"><fs><mkdir path='/tmp'/></fs><ok to="a60"/><error to="a60"/></action>
+ <action name="a60"><fs><mkdir path='/tmp'/></fs><ok to="a61"/><error to="a61"/></action>
+ <action name="a61"><fs><mkdir path='/tmp'/></fs><ok to="a62"/><error to="a62"/></action>
+ <action name="a62"><fs><mkdir path='/tmp'/></fs><ok to="a63"/><error to="a63"/></action>
+ <action name="a63"><fs><mkdir path='/tmp'/></fs><ok to="a64"/><error to="a64"/></action>
+ <action name="a64"><fs><mkdir path='/tmp'/></fs><ok to="a65"/><error to="a65"/></action>
+ <action name="a65"><fs><mkdir path='/tmp'/></fs><ok to="a66"/><error to="a66"/></action>
+ <action name="a66"><fs><mkdir path='/tmp'/></fs><ok to="a67"/><error to="a67"/></action>
+ <action name="a67"><fs><mkdir path='/tmp'/></fs><ok to="a68"/><error to="a68"/></action>
+ <action name="a68"><fs><mkdir path='/tmp'/></fs><ok to="a69"/><error to="a69"/></action>
+ <action name="a69"><fs><mkdir path='/tmp'/></fs><ok to="a70"/><error to="a70"/></action>
+ <action name="a70"><fs><mkdir path='/tmp'/></fs><ok to="a71"/><error to="a71"/></action>
+ <action name="a71"><fs><mkdir path='/tmp'/></fs><ok to="a72"/><error to="a72"/></action>
+ <action name="a72"><fs><mkdir path='/tmp'/></fs><ok to="a73"/><error to="a73"/></action>
+ <action name="a73"><fs><mkdir path='/tmp'/></fs><ok to="a74"/><error to="a74"/></action>
+ <action name="a74"><fs><mkdir path='/tmp'/></fs><ok to="a75"/><error to="a75"/></action>
+ <action name="a75"><fs><mkdir path='/tmp'/></fs><ok to="a76"/><error to="a76"/></action>
+ <action name="a76"><fs><mkdir path='/tmp'/></fs><ok to="a77"/><error to="a77"/></action>
+ <action name="a77"><fs><mkdir path='/tmp'/></fs><ok to="a78"/><error to="a78"/></action>
+ <action name="a78"><fs><mkdir path='/tmp'/></fs><ok to="a79"/><error to="a79"/></action>
+ <action name="a79"><fs><mkdir path='/tmp'/></fs><ok to="a80"/><error to="a80"/></action>
+ <action name="a80"><fs><mkdir path='/tmp'/></fs><ok to="z"/><error to="z"/></action>
+ <end name="z"/>
+</workflow-app>
\ No newline at end of file
diff --git a/release-log.txt b/release-log.txt
index caf5a08..e0c6329 100644
--- a/release-log.txt
+++ b/release-log.txt
@@ -1,5 +1,6 @@
-- Oozie 5.3.0 release (trunk - unreleased)
+OOZIE-3561 Forkjoin validation is slow when there are many actions in chain (dionusos, pbacsko via asalamon74)
OOZIE-3491 Confusing System ID error message (matijhs via asalamon74)
OOZIE-3536 Invalid configuration tag <additionalparam> in maven-javadoc-plugin (nobigo via asalamon74)
OOZIE-3559 Code generation encoding error in fluent-job-api (nobigo via asalamon74)