Skip to content

Commit

Permalink
Change FileWatcher to ignore watchfile heartbeat timeout.
Browse files Browse the repository at this point in the history
The rust_timeout parameter in awatch() controls how long the
underlying Rust code waits for file changes before checking
if it should stop. In result in non-windows system missleading
debug log was printed every
5 seconds:
` DEBUG    watchfiles.main:267: rust notify timeout, continuing`

Set `rust_timeout` to sys.maxsize - 1 as we don't need frequent
timeouts for proper interrupt handling on non-Windows systems.
yield_on_timeout was changed to True to yield empty set on this
timeout error instead of printing debug log.

This is safe because:
- Unix systems use native file system events (inotify/FSEvents)
- Process interruption works reliably without shorter timeouts
- The large timeout value doesn't affect file change detection performance
  • Loading branch information
ela-kotulska-frequenz committed Nov 21, 2024
1 parent b3c98e5 commit 5a84c3d
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 5 deletions.
20 changes: 15 additions & 5 deletions src/frequenz/channels/file_watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import asyncio
import pathlib
import sys
from collections import abc
from dataclasses import dataclass
from datetime import timedelta
Expand Down Expand Up @@ -159,6 +160,10 @@ def __init__(
watch_filter=self._filter_events,
force_polling=force_polling,
poll_delay_ms=int(polling_interval.total_seconds() * 1_000),
# Two arguments below are ok for non-Windows systems.
# For more details, refer awatch documentation.
rust_timeout=sys.maxsize - 1,
yield_on_timeout=True,
)
self._awatch_stopped_exc: Exception | None = None
self._changes: set[FileChange] = set()
Expand Down Expand Up @@ -204,11 +209,16 @@ async def ready(self) -> bool:
if self._awatch_stopped_exc is not None:
return False

try:
self._changes = await anext(self._awatch)
except StopAsyncIteration as err:
self._awatch_stopped_exc = err
return False
# awatch will yield an empty set if rust notify timeout is reached.
# This is safety heartbeat mechanism for Windows.
# But on non-Windows systems, we don't need it, so we can ignore it.
# For more details, refer awatch documentation.
while len(self._changes) == 0:
try:
self._changes = await anext(self._awatch)
except StopAsyncIteration as err:
self._awatch_stopped_exc = err
return False

return True

Expand Down
3 changes: 3 additions & 0 deletions tests/test_file_watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@


import pathlib
import sys
from collections.abc import AsyncGenerator, Iterator, Sequence
from typing import Any
from unittest import mock
Expand Down Expand Up @@ -102,6 +103,8 @@ async def test_file_watcher_filter_events(
watch_filter=filter_events,
force_polling=True,
poll_delay_ms=1_000,
rust_timeout=sys.maxsize - 1,
yield_on_timeout=True,
)
]
for event_type in EventType:
Expand Down

0 comments on commit 5a84c3d

Please sign in to comment.