Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to control process respawn. #301

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion circus/config.py
Expand Up @@ -30,7 +30,9 @@ def watcher_defaults():
'singleton': False,
'copy_env': False,
'copy_path': False,
'hooks': dict()}
'hooks': dict(),
'respawn': True,
}


_BOOL_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
Expand Down Expand Up @@ -223,6 +225,9 @@ def get_config(config_file):

watcher['hooks'][hook_name] = val

elif opt == 'respawn':
watcher['respawn'] = dget(section, "respawn", True, bool)

else:
# freeform
watcher[opt] = val
Expand Down
31 changes: 31 additions & 0 deletions circus/tests/test_watcher.py
Expand Up @@ -254,3 +254,34 @@ def test_after_stop_fails(self):
def test_after_stop_false(self):
self._test_hooks(behavior=FAILURE, status='stopped',
hook_name='after_stop', call=self._stop)


def oneshot_process(test_file):
pass


class RespawnTest(TestCircus):
def setUp(self):
super(RespawnTest, self).setUp()
# Create a watcher which doesn't respawn its processes.
oneshot_process = 'circus.tests.test_watcher.oneshot_process'
self._run_circus(oneshot_process, respawn=False)
self.watcher = self.arbiters[-1].watchers[-1]

def test_not_respawning(self):
# Per default, we shouldn't respawn processes, so we should have one
# process, even if in a dead state.
self.assertEquals(len(self.watcher.processes), 1)

# let's reap processes and explicitely ask for process management
self.watcher.reap_and_manage_processes()
# we should have zero processes (the process shouldn't respawn)
self.assertEquals(len(self.watcher.processes), 0)

def test_respawning(self):
# If we explicitely ask the watcher to respawn its processes, ensure
# it's doing so.
self.assertEquals(len(self.watcher.processes), 1)
self.watcher.reap_and_manage_processes()
self.watcher.spawn_processes()
self.assertEquals(len(self.watcher.processes), 1)
19 changes: 10 additions & 9 deletions circus/watcher.py
Expand Up @@ -137,6 +137,9 @@ class Watcher(object):
found in the configuration file for instance, are passed
in this mapping -- this can be used by plugins for watcher-specific
options.

- **respawn** -- If set to False, the processes handled by a watcher will
not be respawned automatically. (default: True)
"""
def __init__(self, name, cmd, args=None, numprocesses=1, warmup_delay=0.,
working_dir=None, shell=False, uid=None, max_retry=5,
Expand All @@ -146,8 +149,7 @@ def __init__(self, name, cmd, args=None, numprocesses=1, warmup_delay=0.,
stderr_stream=None, stream_backend='thread', priority=0,
singleton=False, use_sockets=False, copy_env=False,
copy_path=False, max_age=0, max_age_variance=30,
hooks=None,
**options):
hooks=None, respawn=True, **options):
self.name = name
self.use_sockets = use_sockets
self.res_name = name.lower().replace(" ", "_")
Expand Down Expand Up @@ -176,6 +178,7 @@ def __init__(self, name, cmd, args=None, numprocesses=1, warmup_delay=0.,
self.max_age_variance = int(max_age_variance)
self.ignore_hook_failure = ['before_stop', 'after_stop']
self.hooks = self._resolve_hooks(hooks)
self.respawn = respawn

if singleton and self.numprocesses not in (0, 1):
raise ValueError("Cannot have %d processes with a singleton "
Expand Down Expand Up @@ -344,8 +347,7 @@ def reap_processes(self):

@util.debuglog
def manage_processes(self):
""" manage processes
"""
"""Manage processes."""
if self.stopped:
return

Expand All @@ -359,7 +361,7 @@ def manage_processes(self):
"time": time.time()})
self.kill_process(process)

if len(self.processes) < self.numprocesses:
if self.respawn and len(self.processes) < self.numprocesses:
self.spawn_processes()

processes = self.processes.values()
Expand All @@ -374,8 +376,7 @@ def manage_processes(self):

@util.debuglog
def reap_and_manage_processes(self):
"""Reap & manage processes.
"""
"""Reap & manage processes."""
if self.stopped:
return
self.reap_processes()
Expand Down Expand Up @@ -533,7 +534,7 @@ def process_info(self, pid):

@util.debuglog
def info(self):
return dict([(proc.pid, proc.info())\
return dict([(proc.pid, proc.info())
for proc in self.processes.values()])

@util.debuglog
Expand Down Expand Up @@ -624,7 +625,7 @@ def start(self):

self._create_redirectors()
self.reap_processes()
self.manage_processes()
self.spawn_processes()

if not self.call_hook('after_start'):
logger.debug('Aborting startup')
Expand Down
3 changes: 3 additions & 0 deletions docs/source/configuration.rst
Expand Up @@ -72,6 +72,9 @@ circus - single section
**debug**
If set to True, all Circus stout/stderr daemons are redirected to circusd
stdout/stderr (default: False)
**respawn**
If set to False, the processes handled by a watcher will not be
respawned automatically. (default: True)

.. note::

Expand Down