Skip to content

Commit aab7ff6

Browse files
committed
Merge branch 'master' into dev
2 parents 5890a64 + 9bb5592 commit aab7ff6

34 files changed

+711
-256
lines changed

CHANGES.rst

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1-
4.2.2.dev0 (Next Release)
1+
4.3.0.dev0 (Next Release)
22
-------------------------
33

4+
- The web interface will now return a 404 Not Found response if a log file
5+
is missing. Previously, it would return 410 Gone. It was changed because
6+
410 is intended to mean that the condition is likely to be permanent. A
7+
log file missing is usually temporary, e.g. a process that was never started
8+
will not have a log file but will have one as soon as it is started.
9+
10+
4.2.2 (2021-02-26)
11+
------------------
12+
13+
- Fixed a bug where ``supervisord`` could crash if a subprocess exited
14+
immediately before trying to kill it.
15+
416
- Fixed a bug where the ``stdout_syslog`` and ``stderr_syslog`` options
517
of a ``[program:x]`` section could not be used unless file logging for
618
the same program had also been configured. The file and syslog options
@@ -11,9 +23,16 @@
1123
``syslog`` was supplied, as is supported by all other log filename
1224
options. Patch by Franck Cuny.
1325

26+
- Fixed a bug where environment variables defined in ``environment=``
27+
in the ``[supervisord]`` section or a ``[program:x]`` section could
28+
not be used in ``%(ENV_x)s`` expansions. Patch by MythRen.
29+
1430
- The ``supervisorctl signal`` command now allows a signal to be sent
1531
when a process is in the ``STOPPING`` state. Patch by Mike Gould.
1632

33+
- ``supervisorctl`` and ``supervisord`` now print help when given ``-?``
34+
in addition to the existing ``-h``/``--help``.
35+
1736
4.2.1 (2020-08-20)
1837
------------------
1938

docs/conf.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,6 @@
2222

2323
parent = os.path.dirname(os.path.dirname(__file__))
2424
sys.path.append(os.path.abspath(parent))
25-
wd = os.getcwd()
26-
os.chdir(parent)
27-
os.system('%s setup.py test -q' % sys.executable)
28-
os.chdir(wd)
29-
30-
for item in os.listdir(parent):
31-
if item.endswith('.egg'):
32-
sys.path.append(os.path.join(parent, item))
3325

3426
version_txt = os.path.join(parent, 'supervisor/version.txt')
3527
supervisor_version = open(version_txt).read().strip()

docs/configuration.rst

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,8 @@ follows.
445445
``environment``
446446

447447
A list of key/value pairs in the form ``KEY="val",KEY2="val2"`` that
448-
will be placed in the :program:`supervisord` process' environment
449-
(and as a result in all of its child process' environments). This
448+
will be placed in the environment of all child processes. This does
449+
not change the environment of :program:`supervisord` itself. This
450450
option can include the value ``%(here)s``, which expands to the
451451
directory in which the supervisord configuration file was found.
452452
Values containing non-alphanumeric characters should be quoted
@@ -752,8 +752,15 @@ where specified.
752752

753753
The number of serial failure attempts that :program:`supervisord`
754754
will allow when attempting to start the program before giving up and
755-
putting the process into an ``FATAL`` state. See
756-
:ref:`process_states` for explanation of the ``FATAL`` state.
755+
putting the process into an ``FATAL`` state.
756+
757+
.. note::
758+
759+
After each failed restart, process will be put in ``BACKOFF`` state
760+
and each retry attempt will take increasingly more time.
761+
762+
See :ref:`process_states` for explanation of the ``FATAL`` and
763+
``BACKOFF`` states.
757764

758765
*Default*: 3
759766

@@ -1525,8 +1532,8 @@ Adding ``rpcinterface:x`` settings in the configuration file is only
15251532
useful for people who wish to extend supervisor with additional custom
15261533
behavior.
15271534

1528-
In the sample config file, there is a section which is named
1529-
``[rpcinterface:supervisor]``. By default it looks like the
1535+
In the sample config file (see :ref:`create_config`), there is a section
1536+
which is named ``[rpcinterface:supervisor]``. By default it looks like the
15301537
following.
15311538

15321539
.. code-block:: ini

docs/installing.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ the system boots.
101101
`Ubuntu Bug #1594740 <https://bugs.launchpad.net/ubuntu/+source/supervisor/+bug/1594740>`_
102102
for more information.
103103

104+
.. _create_config:
105+
104106
Creating a Configuration File
105107
-----------------------------
106108

docs/plugins.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,5 @@ with third party applications:
184184
`supervisor-alert <https://github.com/rahiel/supervisor-alert>`_
185185
Send event notifications over `Telegram <https://telegram.org>`_ or to an
186186
arbitrary command.
187+
`supervisor-discord <https://github.com/chaos-a/supervisor-discord>`_
188+
Send event notifications to `Discord <https://discord.com>`_ via webhooks.

docs/subprocess.rst

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ interface elements in clients.
218218
``BACKOFF`` (30)
219219

220220
The process entered the ``STARTING`` state but subsequently exited
221-
too quickly to move to the ``RUNNING`` state.
221+
too quickly (before the time defined in ``startsecs``) to move to
222+
the ``RUNNING`` state.
222223

223224
``STOPPING`` (40)
224225

@@ -254,8 +255,15 @@ automatically restarted by :program:`supervisord`. It will switch
254255
between ``STARTING`` and ``BACKOFF`` states until it becomes evident
255256
that it cannot be started because the number of ``startretries`` has
256257
exceeded the maximum, at which point it will transition to the
257-
``FATAL`` state. Each start retry will take progressively
258-
more time.
258+
``FATAL`` state.
259+
260+
.. note::
261+
Retries will take increasingly more time depending on the number of
262+
subsequent attempts made, adding one second each time.
263+
264+
So if you set ``startretries=3``, :program:`supervisord` will wait one,
265+
two and then three seconds between each restart attempt, for a total of
266+
5 seconds.
259267

260268
When a process is in the ``EXITED`` state, it will
261269
automatically restart:

supervisor/http.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -737,10 +737,11 @@ def handle_request(self, request):
737737
logfile = getattr(process.config, '%s_logfile' % channel, None)
738738

739739
if logfile is None or not os.path.exists(logfile):
740-
# XXX problematic: processes that don't start won't have a log
741-
# file and we probably don't want to go into fatal state if we try
742-
# to read the log of a process that did not start.
743-
request.error(410) # gone
740+
# we return 404 because no logfile is a temporary condition.
741+
# if the process has never been started, no logfile will exist
742+
# on disk. a logfile of None is also a temporay condition,
743+
# since the config file can be reloaded.
744+
request.error(404) # not found
744745
return
745746

746747
mtime = os.stat(logfile)[stat.ST_MTIME]
@@ -774,7 +775,10 @@ def handle_request(self, request):
774775
logfile = self.supervisord.options.logfile
775776

776777
if logfile is None or not os.path.exists(logfile):
777-
request.error(410) # gone
778+
# we return 404 because no logfile is a temporary condition.
779+
# even if a log file of None is configured, the config file
780+
# may be reloaded, and the new config may have a logfile.
781+
request.error(404) # not found
778782
return
779783

780784
mtime = os.stat(logfile)[stat.ST_MTIME]

supervisor/options.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def __init__(self, require_configfile=True):
102102
self.attr_priorities = {}
103103
self.require_configfile = require_configfile
104104
self.add(None, None, "h", "help", self.help)
105+
self.add(None, None, "?", None, self.help)
105106
self.add("configfile", None, "c:", "configuration=")
106107

107108
here = os.path.dirname(os.path.dirname(sys.argv[0]))
@@ -405,7 +406,6 @@ class ServerOptions(Options):
405406
passwdfile = None
406407
nodaemon = None
407408
silent = None
408-
environment = None
409409
httpservers = ()
410410
unlink_pidfile = False
411411
unlink_socketfiles = False
@@ -656,6 +656,11 @@ def get(opt, default, **kwargs):
656656
environ_str = get('environment', '')
657657
environ_str = expand(environ_str, expansions, 'environment')
658658
section.environment = dict_of_key_value_pairs(environ_str)
659+
660+
# extend expansions for global from [supervisord] environment definition
661+
for k, v in section.environment.items():
662+
self.environ_expansions['ENV_%s' % k ] = v
663+
659664
# Process rpcinterface plugins before groups to allow custom events to
660665
# be registered.
661666
section.rpcinterface_factories = self.get_plugins(
@@ -963,6 +968,10 @@ def get(section, opt, *args, **kwargs):
963968
environment = dict_of_key_value_pairs(
964969
expand(environment_str, expansions, 'environment'))
965970

971+
# extend expansions for process from [program:x] environment definition
972+
for k, v in environment.items():
973+
expansions['ENV_%s' % k] = v
974+
966975
directory = get(section, 'directory', None)
967976

968977
logfiles = {}
@@ -1589,9 +1598,6 @@ def readfd(self, fd):
15891598
data = b''
15901599
return data
15911600

1592-
def process_environment(self):
1593-
os.environ.update(self.environment or {})
1594-
15951601
def chdir(self, dir):
15961602
os.chdir(dir)
15971603

supervisor/pidproxy.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
#!/usr/bin/env python
1+
#!/usr/bin/env python -u
22

3-
""" An executable which proxies for a subprocess; upon a signal, it sends that
4-
signal to the process identified by a pidfile. """
3+
"""pidproxy -- run command and proxy signals to it via its pidfile.
4+
5+
This executable runs a command and then monitors a pidfile. When this
6+
executable receives a signal, it sends the same signal to the pid
7+
in the pidfile.
8+
9+
Usage: %s <pidfile name> <command> [<cmdarg1> ...]
10+
"""
511

612
import os
713
import sys
@@ -10,18 +16,19 @@
1016

1117
class PidProxy:
1218
pid = None
19+
1320
def __init__(self, args):
14-
self.setsignals()
1521
try:
1622
self.pidfile, cmdargs = args[1], args[2:]
17-
self.command = os.path.abspath(cmdargs[0])
23+
self.abscmd = os.path.abspath(cmdargs[0])
1824
self.cmdargs = cmdargs
1925
except (ValueError, IndexError):
2026
self.usage()
2127
sys.exit(1)
2228

2329
def go(self):
24-
self.pid = os.spawnv(os.P_NOWAIT, self.command, self.cmdargs)
30+
self.setsignals()
31+
self.pid = os.spawnv(os.P_NOWAIT, self.abscmd, self.cmdargs)
2532
while 1:
2633
time.sleep(5)
2734
try:
@@ -32,7 +39,7 @@ def go(self):
3239
break
3340

3441
def usage(self):
35-
print("pidproxy.py <pidfile name> <command> [<cmdarg1> ...]")
42+
print(__doc__ % sys.argv[0])
3643

3744
def setsignals(self):
3845
signal.signal(signal.SIGTERM, self.passtochild)
@@ -64,6 +71,3 @@ def main():
6471

6572
if __name__ == '__main__':
6673
main()
67-
68-
69-

supervisor/process.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import os
2-
import time
31
import errno
2+
import functools
3+
import os
4+
import signal
45
import shlex
6+
import time
57
import traceback
6-
import signal
7-
from functools import total_ordering
88

99
from supervisor.compat import maxint
1010
from supervisor.compat import as_bytes
@@ -30,7 +30,7 @@
3030

3131
from supervisor.socket_manager import SocketManager
3232

33-
@total_ordering
33+
@functools.total_ordering
3434
class Subprocess(object):
3535

3636
"""A class to manage a subprocess."""
@@ -403,7 +403,8 @@ def give_up(self):
403403
self.change_state(ProcessStates.FATAL)
404404

405405
def kill(self, sig):
406-
"""Send a signal to the subprocess. This may or may not kill it.
406+
"""Send a signal to the subprocess with the intention to kill
407+
it (to make it exit). This may or may not actually kill it.
407408
408409
Return None if the signal was sent, or an error message string
409410
if an error occurred or if the subprocess is not running.
@@ -463,14 +464,23 @@ def kill(self, sig):
463464
pid = -self.pid
464465

465466
try:
466-
options.kill(pid, sig)
467+
try:
468+
options.kill(pid, sig)
469+
except OSError as exc:
470+
if exc.errno == errno.ESRCH:
471+
msg = ("unable to signal %s (pid %s), it probably just exited "
472+
"on its own: %s" % (processname, self.pid, str(exc)))
473+
options.logger.debug(msg)
474+
# we could change the state here but we intentionally do
475+
# not. we will do it during normal SIGCHLD processing.
476+
return None
477+
raise
467478
except:
468479
tb = traceback.format_exc()
469480
msg = 'unknown problem killing %s (%s):%s' % (processname,
470481
self.pid, tb)
471482
options.logger.critical(msg)
472483
self.change_state(ProcessStates.UNKNOWN)
473-
self.pid = 0
474484
self.killing = False
475485
self.delay = 0
476486
return msg
@@ -502,14 +512,24 @@ def signal(self, sig):
502512
ProcessStates.STOPPING)
503513

504514
try:
505-
options.kill(self.pid, sig)
515+
try:
516+
options.kill(self.pid, sig)
517+
except OSError as exc:
518+
if exc.errno == errno.ESRCH:
519+
msg = ("unable to signal %s (pid %s), it probably just now exited "
520+
"on its own: %s" % (processname, self.pid,
521+
str(exc)))
522+
options.logger.debug(msg)
523+
# we could change the state here but we intentionally do
524+
# not. we will do it during normal SIGCHLD processing.
525+
return None
526+
raise
506527
except:
507528
tb = traceback.format_exc()
508529
msg = 'unknown problem sending sig %s (%s):%s' % (
509530
processname, self.pid, tb)
510531
options.logger.critical(msg)
511532
self.change_state(ProcessStates.UNKNOWN)
512-
self.pid = 0
513533
return msg
514534

515535
return None
@@ -753,7 +773,7 @@ def _prepare_child_fds(self):
753773
for i in range(3, options.minfds):
754774
options.close_fd(i)
755775

756-
@total_ordering
776+
@functools.total_ordering
757777
class ProcessGroupBase(object):
758778
def __init__(self, config):
759779
self.config = config

0 commit comments

Comments
 (0)