Skip to content

Commit d9b897d

Browse files
author
Michael Hammann
committed
feature: add optional parameter depends_on attribute to start dependent processes consecutively
1 parent 9bb5592 commit d9b897d

File tree

5 files changed

+60
-2
lines changed

5 files changed

+60
-2
lines changed

supervisor/graphutils.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from collections import defaultdict
2+
3+
class Graph():
4+
def __init__(self,vertices):
5+
self.graph = defaultdict(list)
6+
self.V = vertices
7+
8+
def addEdge(self,u,v):
9+
self.graph[u].append(v)
10+
11+
def cyclic(self):
12+
"""Return True if the directed graph has a cycle.
13+
The graph must be represented as a dictionary mapping vertices to
14+
iterables of neighbouring vertices. For example:
15+
16+
>>> cyclic({1: (2,), 2: (3,), 3: (1,)})
17+
True
18+
>>> cyclic({1: (2,), 2: (3,), 3: (4,)})
19+
False
20+
21+
"""
22+
path = set()
23+
visited = set()
24+
25+
def visit(vertex):
26+
if vertex in visited:
27+
return False
28+
visited.add(vertex)
29+
path.add(vertex)
30+
for neighbour in self.graph.get(vertex, ()):
31+
if neighbour in path or visit(neighbour):
32+
return True
33+
path.remove(vertex)
34+
return False
35+
36+
return any(visit(v) for v in self.graph)

supervisor/options.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,7 @@ def get(section, opt, *args, **kwargs):
932932
serverurl = get(section, 'serverurl', None)
933933
if serverurl and serverurl.strip().upper() == 'AUTO':
934934
serverurl = None
935+
depends_on = get(section, 'depends_on', None)
935936

936937
# find uid from "user" option
937938
user = get(section, 'user', None)
@@ -1057,7 +1058,9 @@ def get(section, opt, *args, **kwargs):
10571058
exitcodes=exitcodes,
10581059
redirect_stderr=redirect_stderr,
10591060
environment=environment,
1060-
serverurl=serverurl)
1061+
serverurl=serverurl,
1062+
depends_on=depends_on,
1063+
)
10611064

10621065
programs.append(pconfig)
10631066

@@ -1875,7 +1878,7 @@ class ProcessConfig(Config):
18751878
'stderr_events_enabled', 'stderr_syslog',
18761879
'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup',
18771880
'exitcodes', 'redirect_stderr' ]
1878-
optional_param_names = [ 'environment', 'serverurl' ]
1881+
optional_param_names = [ 'environment', 'serverurl', 'depends_on' ]
18791882

18801883
def __init__(self, options, **params):
18811884
self.options = options

supervisor/process.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from supervisor.states import SupervisorStates
1818
from supervisor.states import getProcessStateDescription
1919
from supervisor.states import STOPPED_STATES
20+
from supervisor.states import RUNNING_STATES
2021

2122
from supervisor.options import decode_wait_status
2223
from supervisor.options import signame
@@ -194,6 +195,10 @@ def spawn(self):
194195
195196
Return the process id. If the fork() call fails, return None.
196197
"""
198+
# check if the process is dependent upon any other process and if so make sure that one is in the RUNNING state
199+
if self.config.depends_on is not None and self.config.depends_on.state not in RUNNING_STATES:
200+
self.config.depends_on.spawn()
201+
197202
options = self.config.options
198203
processname = as_string(self.config.name)
199204

supervisor/rpcinterface.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ def getAllConfigInfo(self):
592592
'stderr_logfile_backups': pconfig.stderr_logfile_backups,
593593
'stderr_logfile_maxbytes': pconfig.stderr_logfile_maxbytes,
594594
'stderr_syslog': pconfig.stderr_syslog,
595+
'depends_on': pconfig.depends_on,
595596
}
596597
# no support for these types in xml-rpc
597598
d.update((k, 'auto') for k, v in d.items() if v is Automatic)

supervisor/supervisord.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from supervisor import events
4545
from supervisor.states import SupervisorStates
4646
from supervisor.states import getProcessStateDescription
47+
from supervisor.graphutils import Graph
4748

4849
class Supervisor:
4950
stopping = False # set after we detect that we are handling a stop request
@@ -84,6 +85,18 @@ def run(self):
8485
try:
8586
for config in self.options.process_group_configs:
8687
self.add_process_group(config)
88+
# add processes to directed graph, to check for dependency cycles
89+
g = Graph(len(self.options.process_group_configs))
90+
# replace depends_on string with actual process object
91+
for config in (self.options.process_group_configs):
92+
depends_on = config.process_configs[0].depends_on
93+
if config.process_configs[0].depends_on is not None:
94+
g.addEdge(config.process_configs[0].name, config.process_configs[0].depends_on)
95+
config.process_configs[0].depends_on = self.process_groups[depends_on].processes[depends_on]
96+
# check for cyclical process dependencies
97+
if g.cyclic() == 1:
98+
raise AttributeError('Process config contains dependeny cycle(s)! Check config files again!')
99+
87100
self.options.openhttpservers(self)
88101
self.options.setsignals()
89102
if (not self.options.nodaemon) and self.options.first:

0 commit comments

Comments
 (0)