Skip to content

Commit 18a7a8d

Browse files
author
Michael Hammann
committed
merge from development branch
2 parents 1799a1b + 7f3b3d8 commit 18a7a8d

File tree

5 files changed

+307
-87
lines changed

5 files changed

+307
-87
lines changed

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Changelog of supervisor patch
2+
3+
#### Upcoming Major Changes
4+
5+
#### Upcoming Minor Changes
6+
7+
#### Upcoming Patch Changes
8+
9+
#### 4.2.2-agile5@ar/thirdparty
10+
- fix: depends_on is not blocking anymore, states are properly recognized, errors are handled
11+
- feat: Additional process parameter spawn_timeout: Time until the process must be RUNNING if another process depends on it (deafult=60 seconds)
12+
13+
#### 4.2.2-agile4@ar/thirdparty (10/09/2021)
14+
- depends_on no works correctly when dependency is set to process within a group (group:process)
15+
16+
#### 4.2.2-agile3@ar/thirdparty (08/09/2021)
17+
- Runningregex, only consideres log output after spawning isntead of the full log file. (http://git.ar.int/dev/sys/sopl/ar-supervisor/-/issues/17)
18+
- Depends_on now supports to use autostart, uses dicts for the storage of dependencies and programs only start if dependent processes are in RUNNING state(http://git.ar.int/dev/sys/sopl/ar-supervisor/-/issues/15)
19+
20+
21+
#### 4.2.2-agile2@ar/thirdparty
22+
- fix: depends_on flag excepts multiple dependencies for each process now (dev/sys/sopl/ar-supervisor#12)
23+
- fix: depends_on was only working for first program within a group- now it can be used with all
24+
- fix: depends_on flag needs now to be used with group:process if process is in group or just process if process is not assigned to any group (dev/sys/sopl/ar-supervisor#11)
25+
26+
27+
#### 4.2.2-agile1@ar/thirdparty
28+
- feat: runningregex: set RUNNING state correctly when process is ready. dev/sys/sopl/ar-supervisor#10
29+
- feat: depends_on: add optional attribute to only start process, when required processes are started or in RUNNING state. dev/sys/sopl/ar-supervisor#7

supervisor/options.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,15 @@ def get(section, opt, *args, **kwargs):
932932
serverurl = get(section, 'serverurl', None)
933933
if serverurl and serverurl.strip().upper() == 'AUTO':
934934
serverurl = None
935+
runningregex = get(section, 'runningregex', None)
936+
if runningregex:
937+
try:
938+
runningregex = re.compile(r'.*'+ runningregex)
939+
except Exception as e:
940+
raise ValueError(
941+
f"program section {section} has invalid runningregex value. Error {e}")
935942
depends_on = get(section, 'depends_on', None)
943+
spawn_timeout = int(get(section, 'spawn_timeout', 60))
936944

937945
# find uid from "user" option
938946
user = get(section, 'user', None)
@@ -1059,7 +1067,9 @@ def get(section, opt, *args, **kwargs):
10591067
redirect_stderr=redirect_stderr,
10601068
environment=environment,
10611069
serverurl=serverurl,
1070+
runningregex=runningregex,
10621071
depends_on=depends_on,
1072+
spawn_timeout=spawn_timeout,
10631073
)
10641074

10651075
programs.append(pconfig)
@@ -1878,7 +1888,8 @@ class ProcessConfig(Config):
18781888
'stderr_events_enabled', 'stderr_syslog',
18791889
'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup',
18801890
'exitcodes', 'redirect_stderr' ]
1881-
optional_param_names = [ 'environment', 'serverurl', 'depends_on' ]
1891+
optional_param_names = [ 'environment', 'serverurl',
1892+
'runningregex', 'depends_on', 'spawn_timeout' ]
18821893

18831894
def __init__(self, options, **params):
18841895
self.options = options

supervisor/process.py

Lines changed: 103 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import shlex
66
import time
77
import traceback
8+
import re
89

910
from supervisor.compat import maxint
1011
from supervisor.compat import as_bytes
@@ -22,6 +23,7 @@
2223
from supervisor.options import decode_wait_status
2324
from supervisor.options import signame
2425
from supervisor.options import ProcessException, BadCommand
26+
from supervisor.options import readFile
2527

2628
from supervisor.dispatchers import EventListenerStates
2729

@@ -197,79 +199,97 @@ def record_spawnerr(self, msg):
197199
self.spawnerr = msg
198200
self.config.options.logger.info("spawnerr: %s" % msg)
199201

200-
def spawn(self):
202+
def queue_all_dependee_processes(self, supervisor):
203+
if self.config.name not in supervisor.process_spawn_set:
204+
supervisor.process_spawn_queue.append(self)
205+
supervisor.process_spawn_set.add(self.config.name)
206+
# all dependees that are not in queue and not in STARTING need to be added to queue.
207+
if self.config.depends_on is not None:
208+
for dependee in self.config.depends_on.values():
209+
if dependee.state is not ProcessStates.RUNNING:
210+
ready_to_be_spawned = False
211+
if dependee.state is not ProcessStates.STARTING:
212+
if dependee.config.name not in supervisor.process_spawn_set:
213+
supervisor.process_spawn_queue.append(dependee)
214+
supervisor.process_spawn_set.add(dependee.config.name)
215+
dependee.queue_all_dependee_processes(supervisor)
216+
217+
218+
def spawn(self, supervisor=None):
201219
"""Start the subprocess. It must not be running already.
202220
203221
Return the process id. If the fork() call fails, return None.
204222
"""
223+
# spawn if all dependees are running - else add to queue if not in queue already
224+
ready_to_be_spawned = True
205225

206-
options = self.config.options
207-
processname = as_string(self.config.name)
226+
options = self.config.options
227+
processname = as_string(self.config.name)
208228

209-
if self.pid:
210-
msg = 'process \'%s\' already running' % processname
211-
options.logger.warn(msg)
212-
return
229+
if self.pid:
230+
msg = 'process \'%s\' already running' % processname
231+
options.logger.warn(msg)
232+
return
213233

214-
self.killing = False
215-
self.spawnerr = None
216-
self.exitstatus = None
217-
self.system_stop = False
218-
self.administrative_stop = False
234+
self.killing = False
235+
self.spawnerr = None
236+
self.exitstatus = None
237+
self.system_stop = False
238+
self.administrative_stop = False
219239

220-
self.laststart = time.time()
240+
self.laststart = time.time()
221241

222-
self._assertInState(ProcessStates.EXITED, ProcessStates.FATAL,
223-
ProcessStates.BACKOFF, ProcessStates.STOPPED)
242+
self._assertInState(ProcessStates.EXITED, ProcessStates.FATAL,
243+
ProcessStates.BACKOFF, ProcessStates.STOPPED)
224244

225-
self.change_state(ProcessStates.STARTING)
245+
self.change_state(ProcessStates.STARTING)
226246

227-
try:
228-
filename, argv = self.get_execv_args()
229-
except ProcessException as what:
230-
self.record_spawnerr(what.args[0])
231-
self._assertInState(ProcessStates.STARTING)
232-
self.change_state(ProcessStates.BACKOFF)
233-
return
247+
try:
248+
filename, argv = self.get_execv_args()
249+
except ProcessException as what:
250+
self.record_spawnerr(what.args[0])
251+
self._assertInState(ProcessStates.STARTING)
252+
self.change_state(ProcessStates.BACKOFF)
253+
return
234254

235-
try:
236-
self.dispatchers, self.pipes = self.config.make_dispatchers(self)
237-
except (OSError, IOError) as why:
238-
code = why.args[0]
239-
if code == errno.EMFILE:
240-
# too many file descriptors open
241-
msg = 'too many open files to spawn \'%s\'' % processname
242-
else:
243-
msg = 'unknown error making dispatchers for \'%s\': %s' % (
244-
processname, errno.errorcode.get(code, code))
245-
self.record_spawnerr(msg)
246-
self._assertInState(ProcessStates.STARTING)
247-
self.change_state(ProcessStates.BACKOFF)
248-
return
255+
try:
256+
self.dispatchers, self.pipes = self.config.make_dispatchers(self)
257+
except (OSError, IOError) as why:
258+
code = why.args[0]
259+
if code == errno.EMFILE:
260+
# too many file descriptors open
261+
msg = 'too many open files to spawn \'%s\'' % processname
262+
else:
263+
msg = 'unknown error making dispatchers for \'%s\': %s' % (
264+
processname, errno.errorcode.get(code, code))
265+
self.record_spawnerr(msg)
266+
self._assertInState(ProcessStates.STARTING)
267+
self.change_state(ProcessStates.BACKOFF)
268+
return
249269

250-
try:
251-
pid = options.fork()
252-
except OSError as why:
253-
code = why.args[0]
254-
if code == errno.EAGAIN:
255-
# process table full
256-
msg = ('Too many processes in process table to spawn \'%s\'' %
257-
processname)
258-
else:
259-
msg = 'unknown error during fork for \'%s\': %s' % (
260-
processname, errno.errorcode.get(code, code))
261-
self.record_spawnerr(msg)
262-
self._assertInState(ProcessStates.STARTING)
263-
self.change_state(ProcessStates.BACKOFF)
264-
options.close_parent_pipes(self.pipes)
265-
options.close_child_pipes(self.pipes)
266-
return
270+
try:
271+
pid = options.fork()
272+
except OSError as why:
273+
code = why.args[0]
274+
if code == errno.EAGAIN:
275+
# process table full
276+
msg = ('Too many processes in process table to spawn \'%s\'' %
277+
processname)
278+
else:
279+
msg = 'unknown error during fork for \'%s\': %s' % (
280+
processname, errno.errorcode.get(code, code))
281+
self.record_spawnerr(msg)
282+
self._assertInState(ProcessStates.STARTING)
283+
self.change_state(ProcessStates.BACKOFF)
284+
options.close_parent_pipes(self.pipes)
285+
options.close_child_pipes(self.pipes)
286+
return
267287

268-
if pid != 0:
269-
return self._spawn_as_parent(pid)
288+
if pid != 0:
289+
return self._spawn_as_parent(pid)
270290

271-
else:
272-
return self._spawn_as_child(filename, argv)
291+
else:
292+
return self._spawn_as_child(filename, argv)
273293

274294
def _spawn_as_parent(self, pid):
275295
# Parent
@@ -671,11 +691,11 @@ def transition(self, supervisord_instance=None):
671691
if self.config.autorestart:
672692
if self.config.autorestart is RestartUnconditionally:
673693
# EXITED -> STARTING
674-
self.spawn()
694+
self.spawn(supervisor)
675695
else: # autorestart is RestartWhenExitUnexpected
676696
if self.exitstatus not in self.config.exitcodes:
677697
# EXITED -> STARTING
678-
self.spawn()
698+
self.spawn(supervisor)
679699
elif state == ProcessStates.STOPPED and not self.laststart:
680700
if self.config.autostart:
681701
# STOPPED -> STARTING
@@ -691,11 +711,11 @@ def transition(self, supervisord_instance=None):
691711
if self.backoff <= self.config.startretries:
692712
if now > self.delay:
693713
# BACKOFF -> STARTING
694-
self.spawn()
714+
self.spawn(supervisor)
695715

696716
processname = as_string(self.config.name)
697717
if state == ProcessStates.STARTING:
698-
if now - self.laststart > self.config.startsecs:
718+
if now - self.laststart > self.config.startsecs and not self.config.runningregex:
699719
# STARTING -> RUNNING if the proc has started
700720
# successfully and it has stayed up for at least
701721
# proc.config.startsecs,
@@ -708,6 +728,27 @@ def transition(self, supervisord_instance=None):
708728
'> than %s seconds (startsecs)' % self.config.startsecs)
709729
logger.info('success: %s %s' % (processname, msg))
710730

731+
if self.config.runningregex:
732+
logfile = getattr(self.config, 'stdout_logfile')
733+
logfile_as_str = as_string(readFile(logfile, self.log_offset, 0))
734+
735+
# delete ascii escape sequence and newlines with regular expression
736+
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]|\n')
737+
logfile_as_str = ansi_escape.sub('', logfile_as_str)
738+
739+
# STARTING -> RUNNING if the process has started
740+
# successfully and the runningregex is met
741+
if self.config.runningregex.match(logfile_as_str):
742+
self.delay = 0
743+
self.backoff = 0
744+
self._assertInState(ProcessStates.STARTING)
745+
self.change_state(ProcessStates.RUNNING)
746+
msg = ('entered RUNNING state, found runningregex in stdout')
747+
logger.info('success: %s %s' % (processname, msg))
748+
749+
750+
751+
711752
if state == ProcessStates.BACKOFF:
712753
if self.backoff > self.config.startretries:
713754
# BACKOFF -> FATAL if the proc has exceeded its number

0 commit comments

Comments
 (0)