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

Missed sub-second updates when using polling #319

Open
llucax opened this issue Dec 19, 2024 · 0 comments
Open

Missed sub-second updates when using polling #319

llucax opened this issue Dec 19, 2024 · 0 comments
Labels

Comments

@llucax
Copy link

llucax commented Dec 19, 2024

Description

When using force_polling=True and a watched file/directory is changed more than once in the same second, the change is not reported. I was expecting to receive an event for the change. It seems like the sub-second part of the mtime is being ignored.

Example Code

import asyncio
import pathlib
import tempfile
from datetime import datetime

from watchfiles import awatch


async def change_config_file(config_file: pathlib.Path) -> None:
    await asyncio.sleep(0.2)
    config_file.write_text("World")
    print("Modified file: mtime =", config_file.stat().st_ctime)


async def test(tmpdir: pathlib.Path) -> None:
    """Test the complete flow of configuration management."""
    config_file = tmpdir / "file.txt"

    config_file.write_text("Hello")
    print("Created file: mtime =", config_file.stat().st_ctime)

    task = asyncio.create_task(change_config_file(config_file))

    try:
        async with asyncio.timeout(1):
            # Watch the parent directory for changes
            print(f"Started watching {config_file} at {datetime.now().timestamp()}")
            async for changes in awatch(
                config_file,  # Monitoring the parent directory has the issue too
                watch_filter=None,
                force_polling=True,  # Not using polling makes this work fine
                poll_delay_ms=100,
            ):
                for change_type, changed_path in changes:
                    if pathlib.Path(changed_path) == config_file:
                        print(
                            f"<<< EVENT DETECTED >> at {datetime.now().timestamp()}: {change_type} - {changed_path}"
                        )
                        await task
                        return  # Exit after first relevant change is detected
    finally:
        task.cancel()
        print(
            f"Finished at {datetime.now().timestamp()}: mtime = {config_file.stat().st_ctime}"
        )


if __name__ == "__main__":
    with tempfile.TemporaryDirectory() as tmpdir:
        try:
            asyncio.run(test(pathlib.Path(tmpdir)))
        except asyncio.TimeoutError:
            print("<<< TIMEOUT REACHED >> (changes not detected)")

I tried reproducing this using notify-rs directly, and it seems to work fine, but I'm not a rust programmer, so maybe my test is wrong.

use notify::{Config, PollWatcher, RecursiveMode, Watcher};
use std::fs;
use std::time::Duration;
use std::time::SystemTime;
use tempfile::TempDir;
use tokio::time::sleep;

#[tokio::main]
async fn main() {
    // Create temporary test file
    let dir = TempDir::new().unwrap();
    let file = dir.path().join("test.txt");

    fs::write(&file, "Hello").unwrap();
    println!("Created file: mtime = {:?}", file.metadata().unwrap().modified().unwrap());

    println!("Started watching {:?} at {:?}", file, SystemTime::now());
    let (tx, mut rx) = tokio::sync::mpsc::channel(1);
    let mut watcher = PollWatcher::new(
        move |res| {
            let tx = tx.clone();
            if let Ok(event) = res {
                let _ = tx.blocking_send(event);
            }
        },
        Config::default()
            .with_poll_interval(Duration::from_millis(100))
            .with_compare_contents(true),
    )
    .unwrap();

    watcher.watch(dir.path(), RecursiveMode::NonRecursive).unwrap();

    sleep(Duration::from_millis(200)).await;
    fs::write(&file, "World").unwrap();
    println!("Modified file: mtime = {:?}", file.metadata().unwrap().modified().unwrap());

    let timeout = Duration::from_secs(1);
    let start = std::time::Instant::now();
    while start.elapsed() < timeout {
        if let Ok(event) = rx.try_recv() {
            println!("<<< EVENT DETECTED >> at {:?}: {:?}", SystemTime::now(), event);
            return;
        }
        sleep(Duration::from_millis(100)).await;
    }
    println!("Finished at {:?}: mtime = {:?}", SystemTime::now(), file.metadata().unwrap().modified().unwrap());
    println!("<<< TIMEOUT REACHED >> (changes not detected)");
}

This always work, even when the update is done on the same second.

Watchfiles Output

$ python test_file_watcher.py 
Created file: mtime = 1734607409.951942
Started watching /tmp/tmpxee5za0m/file.txt at 1734607409.957442
Modified file: mtime = 1734607410.159944
<<< EVENT DETECTED >> at 1734607410.312549: 2 - /tmp/tmpxee5za0m/file.txt
Finished at 1734607410.312607: mtime = 1734607410.159944
$ python test_file_watcher.py 
Created file: mtime = 1734607411.6479588
Started watching /tmp/tmp5u5ql5yc/file.txt at 1734607411.652159
Modified file: mtime = 1734607411.851961
Finished at 1734607412.653835: mtime = 1734607411.851961
<<< TIMEOUT REACHED >> (changes not detected)
  • In the first run, that succeed, the updated mtime crosses the second-boundary: 1734607409 to 1734607410.
  • In the second run, that fails, the updated mtime and the initial mtime are in the same second (1734607411) and only the sub-second part changes

Operating System & Architecture

Linux-6.11.10-amd64-x86_64-with-glibc2.40
#1 SMP PREEMPT_DYNAMIC Debian 6.11.10-1 (2024-11-23)

Environment

Tried on ext4 and tmpfs, both fail.

Python & Watchfiles Version

python: 3.11.10 (main, Sep 9 2024, 22:11:19) [Clang 18.1.8 ], watchfiles: 1.0.3

Rust & Cargo Version

cargo 1.83.0 (5ffbef321 2024-10-29) rustc 1.83.0 (90b35a623 2024-11-26)

@llucax llucax added the bug label Dec 19, 2024
@llucax llucax changed the title Changes not detected if the file was changed more than once in the same second when using polling Missed sub-second updates when using polling Dec 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant