Skip to content

Commit

Permalink
[watchmedo] Add option to not auto-restart the command after it exits. (
Browse files Browse the repository at this point in the history
  • Loading branch information
taleinat committed Feb 22, 2023
1 parent d2837e9 commit a9fc7f5
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 10 deletions.
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

0 comments on commit a9fc7f5

Please sign in to comment.