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

[watchmedo] Add option to not auto-restart the command after it exits. #946

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Changelog
- [inotify] Add support for ``IN_OPEN`` events: a ``FileOpenedEvent`` event will be fired. (`#941 <https://github.com/gorakhargosh/watchdog/pull/941>`__)
- [watchmedo] Add optional event debouncing for ``auto-restart``, only restarting once if many events happen in quick succession (`#940 <https://github.com/gorakhargosh/watchdog/pull/940>`__)
- [watchmedo] Exit gracefully on ``KeyboardInterrupt`` exception (Ctrl+C) (`#945 <https://github.com/gorakhargosh/watchdog/pull/945>`__)
- [watchmedo] Add option to not auto-restart the command after it exits. (`#946 <https://github.com/gorakhargosh/watchdog/pull/946>`__)
- Thanks to our beloved contributors: @BoboTiG, @dstaple, @taleinat

2.2.1
Expand Down
12 changes: 8 additions & 4 deletions src/watchdog/tricks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ class AutoRestartTrick(Trick):

def __init__(self, command, patterns=None, ignore_patterns=None,
ignore_directories=False, stop_signal=signal.SIGINT,
kill_after=10, debounce_interval_seconds=0):
kill_after=10, debounce_interval_seconds=0,
restart_on_command_exit=True):
if kill_after < 0:
raise ValueError("kill_after must be non-negative.")
if debounce_interval_seconds < 0:
Expand All @@ -193,6 +194,7 @@ def __init__(self, command, patterns=None, ignore_patterns=None,
self.stop_signal = stop_signal
self.kill_after = kill_after
self.debounce_interval_seconds = debounce_interval_seconds
self.restart_on_command_exit = restart_on_command_exit

self.process = None
self.process_watcher = None
Expand Down Expand Up @@ -227,16 +229,18 @@ def stop(self):
# Don't leak threads: Wait for background threads to stop.
if self.event_debouncer is not None:
self.event_debouncer.join()
process_watcher.join()
if process_watcher is not None:
process_watcher.join()

def _start_process(self):
if self._is_trick_stopping:
return

# windows doesn't have setsid
self.process = subprocess.Popen(self.command, preexec_fn=getattr(os, 'setsid', None))
self.process_watcher = ProcessWatcher(self.process, self._restart_process)
self.process_watcher.start()
if self.restart_on_command_exit:
self.process_watcher = ProcessWatcher(self.process, self._restart_process)
self.process_watcher.start()

def _stop_process(self):
# Ensure the body of the function is not run in parallel in different threads.
Expand Down
10 changes: 8 additions & 2 deletions src/watchdog/watchmedo.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,12 @@ def shell_command(args):
default=0.0,
type=float,
help='After a file change, Wait until the specified interval (in '
'seconds) passes with no file changes, and only then restart.')])
'seconds) passes with no file changes, and only then restart.'),
argument('--no-restart-on-command-exit',
dest='restart_on_command_exit',
default=True,
action='store_false',
help="Don't auto-restart the command after it exits.")])
def auto_restart(args):
"""
Command to start a long-running subprocess and restart it on matched events.
Expand Down Expand Up @@ -640,7 +645,8 @@ def handler_termination_signal(_signum, _frame):
ignore_directories=args.ignore_directories,
stop_signal=stop_signal,
kill_after=args.kill_after,
debounce_interval_seconds=args.debounce_interval)
debounce_interval_seconds=args.debounce_interval,
restart_on_command_exit=args.restart_on_command_exit)
handler.start()
observer = Observer(timeout=args.timeout)
try:
Expand Down
13 changes: 9 additions & 4 deletions tests/test_0_watchmedo.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ def test_auto_restart_on_file_change_debounce(tmpdir, capfd):
assert trick.restart_count == 2


def test_auto_restart_subprocess_termination(tmpdir, capfd):
@pytest.mark.parametrize("restart_on_command_exit", [True, False])
def test_auto_restart_subprocess_termination(tmpdir, capfd, restart_on_command_exit):
"""Run auto-restart with a script that terminates in about 2 seconds.
After 5 seconds, expect it to have been restarted at least once.
Expand All @@ -162,13 +163,17 @@ def test_auto_restart_subprocess_termination(tmpdir, capfd):
import sys
import time
script = make_dummy_script(tmpdir, n=2)
trick = AutoRestartTrick([sys.executable, script])
trick = AutoRestartTrick([sys.executable, script], restart_on_command_exit=restart_on_command_exit)
trick.start()
time.sleep(5)
trick.stop()
cap = capfd.readouterr()
assert cap.out.splitlines(keepends=False).count('+++++ 0') > 1
assert trick.restart_count >= 1
if restart_on_command_exit:
assert cap.out.splitlines(keepends=False).count('+++++ 0') > 1
assert trick.restart_count >= 1
else:
assert cap.out.splitlines(keepends=False).count('+++++ 0') == 1
assert trick.restart_count == 0


def test_auto_restart_arg_parsing_basic():
Expand Down