Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da50395dc7 |
@@ -88,10 +88,22 @@ class ConfigFileHandler(FileSystemEventHandler):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def on_any_event(self, event):
|
def _dispatch(self, event):
|
||||||
if not event.is_directory and self._hits_config(event):
|
if not event.is_directory and self._hits_config(event):
|
||||||
self._on_change()
|
self._on_change()
|
||||||
|
|
||||||
|
# Only write/rename events — NOT on_opened/on_closed, whose read-opens
|
||||||
|
# (our own load_config re-reads the file) would otherwise feed back into
|
||||||
|
# a reload loop (CFG-05).
|
||||||
|
def on_modified(self, event):
|
||||||
|
self._dispatch(event)
|
||||||
|
|
||||||
|
def on_created(self, event):
|
||||||
|
self._dispatch(event)
|
||||||
|
|
||||||
|
def on_moved(self, event):
|
||||||
|
self._dispatch(event)
|
||||||
|
|
||||||
|
|
||||||
class FjerkroaBot(commands.Bot):
|
class FjerkroaBot(commands.Bot):
|
||||||
def __init__(self, config_file: str):
|
def __init__(self, config_file: str):
|
||||||
|
|||||||
@@ -35,9 +35,12 @@ applies directly — there are no concurrent readers yet.
|
|||||||
### CFG-05 — Hot-reload is rename-safe (coverage: test)
|
### CFG-05 — Hot-reload is rename-safe (coverage: test)
|
||||||
|
|
||||||
The watcher observes the config file's **directory**, not the file, and
|
The watcher observes the config file's **directory**, not the file, and
|
||||||
reacts to any event (modified, created, moved) whose source or
|
reacts to a **modified, created, or moved** event whose source or
|
||||||
destination path is the config file. This catches atomic saves — write
|
destination path is the config file. This catches atomic saves — write
|
||||||
a temp file, then rename it over the target — which replace the inode
|
a temp file, then rename it over the target — which replace the inode
|
||||||
and fire a move/create rather than a modify; watching the file directly
|
and fire a move/create rather than a modify; watching the file directly
|
||||||
would go deaf after the first such save. Events for other files in the
|
would go deaf after the first such save. Open/close events are
|
||||||
directory, and directory events themselves, are ignored.
|
deliberately not handled: reloading re-opens the file to read it, so
|
||||||
|
reacting to opens would feed back into an endless reload loop. Events
|
||||||
|
for other files in the directory, and directory events themselves, are
|
||||||
|
ignored.
|
||||||
|
|||||||
+11
-6
@@ -157,10 +157,15 @@ class TestConfigReloadRenameSafe(unittest.TestCase):
|
|||||||
event.dest_path = dest if dest is not None else ""
|
event.dest_path = dest if dest is not None else ""
|
||||||
return event
|
return event
|
||||||
|
|
||||||
handler.on_any_event(evt(src=str(config))) # in-place modify
|
handler.on_modified(evt(src=str(config))) # in-place modify
|
||||||
handler.on_any_event(evt(src=str(Path(tmp) / "kroa.toml.tmp"), dest=str(config))) # atomic rename over
|
handler.on_moved(evt(src=str(Path(tmp) / "kroa.toml.tmp"), dest=str(config))) # atomic rename over
|
||||||
self.assertEqual(len(hits), 2)
|
handler.on_created(evt(src=str(config))) # write-new
|
||||||
|
self.assertEqual(len(hits), 3)
|
||||||
|
|
||||||
handler.on_any_event(evt(src=str(Path(tmp) / "other.txt"))) # unrelated file
|
handler.on_modified(evt(src=str(Path(tmp) / "other.txt"))) # unrelated file
|
||||||
handler.on_any_event(evt(is_dir=True, src=str(config))) # directory event
|
handler.on_modified(evt(is_dir=True, src=str(config))) # directory event
|
||||||
self.assertEqual(len(hits), 2) # neither fired
|
self.assertEqual(len(hits), 3) # neither fired
|
||||||
|
|
||||||
|
# open/close of the config (our own load_config re-reads) must NOT be handled — else a reload loop.
|
||||||
|
self.assertNotIn("on_opened", vars(ConfigFileHandler))
|
||||||
|
self.assertNotIn("on_closed", vars(ConfigFileHandler))
|
||||||
|
|||||||
Reference in New Issue
Block a user