diff --git a/src/hupper/reloader.py b/src/hupper/reloader.py index b61cfdf..74bce12 100644 --- a/src/hupper/reloader.py +++ b/src/hupper/reloader.py @@ -5,6 +5,7 @@ import re import signal import sys +import threading import time from .compat import glob @@ -37,6 +38,8 @@ def __init__(self, callback, logger, ignore_files=None): self.ignore_files = [ re.compile(fnmatch.translate(x)) for x in set(ignore_files or []) ] + self.lock = threading.Lock() + self.is_changed = False def add_path(self, path): # if the glob does not match any files then go ahead and pass @@ -54,13 +57,19 @@ def stop(self): self.monitor.join() def file_changed(self, path): - if path not in self.changed_paths: - self.changed_paths.add(path) - self.logger.info('%s changed; reloading ...' % (path,)) - self.callback(self.changed_paths) + with self.lock: + if path not in self.changed_paths: + self.logger.info('{} changed; reloading ...'.format(path)) + self.changed_paths.add(path) + + if not self.is_changed: + self.callback(self.changed_paths) + self.is_changed = True def clear_changes(self): - self.changed_paths = set() + with self.lock: + self.changed_paths = set() + self.is_changed = False class ControlSignal: @@ -228,18 +237,20 @@ def handle_packet(packet): packets.append(packet) os.write(self.control_w, ControlSignal.WORKER_COMMAND) + self.monitor.clear_changes() + worker.start(handle_packet) result = WorkerResult.WAIT soft_kill = True + logger.info('Starting monitor for PID %s.' % worker.pid) try: # register the worker with the process group self.process_group.add_child(worker.pid) - logger.info('Starting monitor for PID %s.' % worker.pid) - self.monitor.clear_changes() - while True: + # process all packets before moving on to signals to avoid + # missing any files that need to be watched if packets: cmd = packets.popleft() @@ -267,7 +278,7 @@ def handle_packet(packet): result = WorkerResult.RELOAD break - elif cmd[0] == 'watch': + elif cmd[0] == 'watch_files': for path in cmd[1]: self.monitor.add_path(path) @@ -315,8 +326,9 @@ def handle_packet(packet): break elif signal == ControlSignal.FILE_CHANGED: - result = WorkerResult.RELOAD - break + if self.monitor.is_changed: + result = WorkerResult.RELOAD + break if worker.is_alive() and self.shutdown_interval: if soft_kill: diff --git a/src/hupper/worker.py b/src/hupper/worker.py index bda86cb..384d6ea 100644 --- a/src/hupper/worker.py +++ b/src/hupper/worker.py @@ -15,7 +15,7 @@ class WatchSysModules(threading.Thread): """ Poll ``sys.modules`` for imported modules.""" - poll_interval = 1 + poll_interval = 3 ignore_system_paths = True def __init__(self, callback): @@ -205,7 +205,7 @@ def __init__(self, pipe): def watch_files(self, files): files = [os.path.abspath(f) for f in files] - self.pipe.send(('watch', files)) + self.pipe.send(('watch_files', files)) def trigger_reload(self): self.pipe.send(('reload',))