Cycle detection and topological sorting

This commit is contained in:
Anders Ingemann 2013-06-23 17:03:55 +02:00
parent 1e4af40b3c
commit 0f29b3d0e2
3 changed files with 87 additions and 18 deletions

View file

@ -1,7 +1,6 @@
from functools import total_ordering
from common.exceptions import TaskListError
@total_ordering
class Task(object):
description = None
@ -42,15 +41,10 @@ class Task(object):
msg = ("The task {self} is specified as running before {other}, "
"but its phase {phase} lies after the phase {other_phase}"
.format(self, other, self.phase, other.phase))
raise TaskOrderError(msg)
raise TaskListError(msg)
for task in self.after:
if self.phase < task.phase:
msg = ("The task {self} is specified as running after {other}, "
"but its phase {phase} lies before the phase {other_phase}"
.format(self=self, other=other, phase=self.phase, other_phase=other.phase))
raise TaskOrderError(msg)
conflict = next(iter(set(self.before) & set(self.after)), None)
if conflict is not None:
msg = ("The task {self} is specified as running both before and after {conflict}"
.format(self=self, conflict=conflict))
raise TaskOrderError(msg)
raise TaskListError(msg)

View file

@ -1,3 +1,4 @@
from common.exceptions import TaskListError
import logging
log = logging.getLogger(__name__)
@ -17,14 +18,89 @@ class TaskList(object):
self.remove(task)
self.add(replacement)
def get(self, task):
def get(self, ref):
return next(task for task in self.tasks if type(task) is ref)
def run(self, bootstrap_info):
log.debug('Tasklist before:\n{list}'.format(list=',\n'.join(str(task) for task in self.tasks) ))
task_list = sorted(self.tasks)
log.debug('Tasklist:\n{list}'.format(list=',\n'.join(str(task) for task in task_list) ))
return
task_list = self.create_list()
for task in tasks:
log.info(task)
task.run(bootstrap_info)
def create_list(self):
graph = {}
for task in self.tasks:
graph[task] = [self.get(succ) for succ in task.before]
graph[task].extend([succ for succ in self.tasks if type(task) in succ.after])
components = self.strongly_connected_components(graph)
cycles_found = 0
for component in components:
if len(component) > 1:
cycles_found += 1
log.debug('Cycle: {list}\n'.format(list=', '.join(str(task) for task in component)))
if cycles_found > 0:
msg = ('{0} cycles were found in the tasklist, '
'consult the logfile for more information.'.format(cycles_found))
raise TaskListError(msg)
sorted_tasks = self.topological_sort(graph)
log.debug('Tasklist:\n\t{list}\n'.format(list='\n\t'.join(str(task) for task in sorted_tasks)))
def strongly_connected_components(self, graph):
# Source: http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py
# Find the strongly connected components in a graph using Tarjan's algorithm.
# graph should be a dictionary mapping node names to lists of successor nodes.
result = [ ]
stack = [ ]
low = { }
def visit(node):
if node in low: return
num = len(low)
low[node] = num
stack_pos = len(stack)
stack.append(node)
for successor in graph[node]:
# print successor
visit(successor)
low[node] = min(low[node], low[successor])
if num == low[node]:
component = tuple(stack[stack_pos:])
del stack[stack_pos:]
result.append(component)
for item in component:
low[item] = len(graph)
for node in graph:
visit(node)
return result
def topological_sort(self, graph):
# Source: http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py
count = { }
for node in graph:
count[node] = 0
for node in graph:
for successor in graph[node]:
count[successor] += 1
ready = [ node for node in graph if count[node] == 0 ]
result = [ ]
while ready:
node = ready.pop(-1)
result.append(node)
for successor in graph[node]:
count[successor] -= 1
if count[successor] == 0:
ready.append(successor)
return result

View file

@ -9,9 +9,8 @@ class ManifestError(Exception):
return "Error in manifest {0}: {1}".format(self.manifest.path, self.message)
class TaskOrderError(Exception):
def __init__(self, message, task):
class TaskListError(Exception):
def __init__(self, message):
self.message = message
self.task = task
def __str__(self):
return "Error in task order: {1}".format(self.message)
return "Error in tasklist: {0}".format(self.message)