detect loops while building the workflow, not after: faster, better

master
Y 2018-02-24 13:05:57 +01:00
parent 71a9ba321f
commit fb6a69c40e
1 changed files with 13 additions and 31 deletions

View File

@ -11,7 +11,7 @@ class Workflow:
firstStep = None firstStep = None
for label in actions: for label in actions:
if not label in seen: if not label in seen:
(entryPoint, seen, newDangling) = self._initChain(actions, label, seen) (entryPoint, seen, newDangling) = self._initChain(actions, label, seen, (label,))
if firstStep is None: if firstStep is None:
firstStep = entryPoint firstStep = entryPoint
elif len(dangling) > 0: elif len(dangling) > 0:
@ -19,11 +19,8 @@ class Workflow:
setter(entryPoint) setter(entryPoint)
dangling = newDangling dangling = newDangling
self.firstStep = firstStep self.firstStep = firstStep
loop = self._checkForLoops()
if loop:
raise RecursionError("Loop found in actions: %s\n" % loop)
def _initChain(self, actions, label, seen): def _initChain(self, actions, label, seen, wholeChain):
dangling = [] dangling = []
previousSetter = None previousSetter = None
firstStep = None firstStep = None
@ -38,12 +35,16 @@ class Workflow:
obj.setStepName(label + '[' + str(stepNum) + ']') obj.setStepName(label + '[' + str(stepNum) + ']')
if mod.thenRun: if mod.thenRun:
(seen, dangling) = \ (seen, dangling) = \
self._branchToChain(obj.setNextStep, mod.thenRun, actions, seen, dangling) self._branchToChain(
obj.setNextStep, mod.thenRun, wholeChain,
actions, seen, dangling)
isThenCalled = True isThenCalled = True
if mod.isFilter: if mod.isFilter:
if mod.elseRun: if mod.elseRun:
(seen, dangling) = \ (seen, dangling) = \
self._branchToChain(obj.setAltStep, mod.elseRun, actions, seen, dangling) self._branchToChain(
obj.setAltStep, mod.elseRun, wholeChain,
actions, seen, dangling)
else: else:
dangling.append(obj.setAltStep) dangling.append(obj.setAltStep)
isPreviousDangling = mod.isFilter and not isThenCalled isPreviousDangling = mod.isFilter and not isThenCalled
@ -57,35 +58,16 @@ class Workflow:
seen[label] = firstStep if len(dangling) == 0 else None seen[label] = firstStep if len(dangling) == 0 else None
return (firstStep, seen, dangling) return (firstStep, seen, dangling)
def _branchToChain(self, parentSetter, branchName, actions, seen, dangling): def _branchToChain(self, parentSetter, branchName, wholeChain, actions, seen, dangling):
if branchName in seen and seen[branchName]: if branchName in wholeChain:
raise RecursionError("Loop found in actions: %s\n" % str(wholeChain + (branchName,)))
elif branchName in seen and seen[branchName] is not None:
parentSetter(seen[branchName]) parentSetter(seen[branchName])
elif branchName in actions: elif branchName in actions:
(entryPoint, seen, newDangling) = \ (entryPoint, seen, newDangling) = \
self._initChain(actions, branchName, seen) self._initChain(actions, branchName, seen, wholeChain + (branchName,))
parentSetter(entryPoint) parentSetter(entryPoint)
dangling.extend(newDangling) dangling.extend(newDangling)
else: else:
raise ValueError("Action chain not found: %s\n" % branchName) raise ValueError("Action chain not found: %s\n" % branchName)
return (seen, dangling) return (seen, dangling)
def _checkForLoops(self):
if self.firstStep is None:
return None
branches = [(self.firstStep, [], [])]
while True:
node, branchIds, branch = branches.pop()
idNode = id(node)
if idNode in branchIds:
return branch if self._withDebug else True
branchIds.append(idNode)
if self._withDebug:
branch.append(node.stepName)
if isinstance(node, base.Filter) and node.altStep:
altBranch = list(branch)
altBranchIds = list(branchIds)
branches.append((node.altStep, altBranchIds, altBranch))
if node.nextStep:
branches.append((node.nextStep, branchIds, branch))
if len(branches) == 0:
return None