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

PollWatcher may deadlock when user try change watch path in event_handler #410

Open
visig9 opened this issue May 22, 2022 · 4 comments
Open
Labels
B-unconfirmed Needs verification (by maintainer or testing party)

Comments

@visig9
Copy link
Contributor

visig9 commented May 22, 2022

System details

I believe this one affect all environment.

What you did

//! # Dependencies
//!
//! ```toml
//! [dependencies]
//! notify = "5.0.0-pre.15"
//! ```
//!
//! # Test
//!
//! ```sh
//! # in first terminal
//! touch mew.txt
//! cargo run
//!
//! # in second terminal
//! touch mew.txt  # try repeat multiple time
//! ```

use notify::{PollWatcher, RecursiveMode, Watcher};
use std::{
    sync::{Arc, Mutex},
    thread,
    time::Duration,
};

fn main() {
    // pass watcher into event handler.
    let arc_watcher_1: Arc<Mutex<Option<PollWatcher>>> = Default::default();
    let arc_watcher_2 = Arc::clone(&arc_watcher_1);

    // start watcher
    let mut watcher = PollWatcher::with_config(
        move |event| {
            println!("{event:?}");

            // try dead lock PollWatcher's internal loop.
            //
            // Scenario: User may try to add another watch path in event handler.
            if let Some(watcher) = arc_watcher_2.lock().unwrap().as_mut() {
                watcher
                    .watch("bow.txt".as_ref(), RecursiveMode::NonRecursive)
                    .unwrap();
            }

            // if this line printed, no deadlock.
            println!("add new watch path in event handler, successful")
        },
        notify::poll::PollWatcherConfig {
            poll_interval: Duration::from_secs(1),
            compare_contents: true,
        },
    )
    .unwrap();

    // watch a location.
    watcher
        .watch("mew.txt".as_ref(), RecursiveMode::NonRecursive)
        .unwrap();

    arc_watcher_1.lock().unwrap().replace(watcher);

    thread::park();
}

What you expected

No deadlock.

What happened

Deadlocked.

Investigate

When user try to change current watching paths, the watcher will take the lock from internal data, See:

But when event_handler be called, this lock are already hold by internal loop, so we call lock() twice at single thread, deadlock.


I may fix it (wait until #409 merged) by add a dedicated thread as event consumer, but after this fix, we will have two thread for each PollWatcher, just little heavy :(

@0xpr03
Copy link
Member

0xpr03 commented Aug 10, 2022

Is this fixed by #409 ?

@0xpr03 0xpr03 added the B-unconfirmed Needs verification (by maintainer or testing party) label Aug 10, 2022
@visig9
Copy link
Contributor Author

visig9 commented Aug 10, 2022

This bug not fixed yet. #409 only include refactoring, almost all behavior are the same.

Recently I have no time to deal this one. If you want to fix it just take it :)

@0xpr03
Copy link
Member

0xpr03 commented Aug 10, 2022

On which system did you get this to deadlock ?

@visig9
Copy link
Contributor Author

visig9 commented Aug 10, 2022

I'm tested within debian bullseye. But pretty sure this bug affect all platform.

The deadlock will happen when user try to change (watch / unwatch) watcher in its event_handler. The API design allow user to do that but it cannot work properly.

Aetf added a commit to shell-pool/shpool that referenced this issue Jun 19, 2024
- Simple merging logic for `Config`
- Load config from system-wide locations (fixes #30)
- Dynamically pick up changes from newly created config files (fixes #29)
- Use `directories-rs` to find user level config directory path

A new `ConfigWatcher` is introduced, which opportunistically watches the
closest existing parent of target paths and rewatches dynamically accordingly.
This way we can get notifications for new file creations in addition to
modifications.

`ConfigWatcher` also does debounce (100ms by default) to only trigger
one config reload for multiple events in a short time period.

The core idea of the watcher is as follow:

Given a watching target path `/base/sub/config.toml`,
a) readd watches if any segment in the path is created/removed;
b) reload config only if the event is about the full target path.

The actual implementation extends this idea to handle multiple targets.
Please refer to `config_watcher.rs` for details and extensive test cases.

A few design points:

- Notify event handling is done in a separate thread, rather than in
  notify's internal event handler thread, because the code is more
  readable and calling unwatch/watch in that thread may lead to deadlock
  in some cases. (notify-rs/notify#410, notify-rs/notify#463)
- For simplicity, `ConfigWatcher` doesn't  provide infomation about
  which exact config file was changed, assuming a reload would need to
  read all config files anyway.
Aetf added a commit to shell-pool/shpool that referenced this issue Jun 24, 2024
- Simple merging logic for `Config`
- Load config from system-wide locations (fixes #30)
- Dynamically pick up changes from newly created config files (fixes #29)
- Use `directories-rs` to find user level config directory path

A new `ConfigWatcher` is introduced, which opportunistically watches the
closest existing parent of target paths and rewatches dynamically accordingly.
This way we can get notifications for new file creations in addition to
modifications.

`ConfigWatcher` also does debounce (100ms by default) to only trigger
one config reload for multiple events in a short time period.

The core idea of the watcher is as follow:

Given a watching target path `/base/sub/config.toml`,
a) readd watches if any segment in the path is created/removed;
b) reload config only if the event is about the full target path.

The actual implementation extends this idea to handle multiple targets.
Please refer to `config_watcher.rs` for details and extensive test cases.

A few design points:

- Notify event handling is done in a separate thread, rather than in
  notify's internal event handler thread, because the code is more
  readable and calling unwatch/watch in that thread may lead to deadlock
  in some cases. (notify-rs/notify#410, notify-rs/notify#463)
- For simplicity, `ConfigWatcher` doesn't  provide infomation about
  which exact config file was changed, assuming a reload would need to
  read all config files anyway.
Aetf added a commit to shell-pool/shpool that referenced this issue Jun 24, 2024
- Simple merging logic for `Config`
- Load config from system-wide locations (fixes #30)
- Dynamically pick up changes from newly created config files (fixes #29)
- Use `directories-rs` to find user level config directory path

A new `ConfigWatcher` is introduced, which opportunistically watches the
closest existing parent of target paths and rewatches dynamically accordingly.
This way we can get notifications for new file creations in addition to
modifications.

`ConfigWatcher` also does debounce (100ms by default) to only trigger
one config reload for multiple events in a short time period.

The core idea of the watcher is as follow:

Given a watching target path `/base/sub/config.toml`,
a) readd watches if any segment in the path is created/removed;
b) reload config only if the event is about the full target path.

The actual implementation extends this idea to handle multiple targets.
Please refer to `config_watcher.rs` for details and extensive test cases.

A few design points:

- Notify event handling is done in a separate thread, rather than in
  notify's internal event handler thread, because the code is more
  readable and calling unwatch/watch in that thread may lead to deadlock
  in some cases. (notify-rs/notify#410, notify-rs/notify#463)
- For simplicity, `ConfigWatcher` doesn't  provide infomation about
  which exact config file was changed, assuming a reload would need to
  read all config files anyway.
Aetf added a commit to shell-pool/shpool that referenced this issue Jun 24, 2024
- Simple merging logic for `Config`
- Load config from system-wide locations (fixes #30)
- Dynamically pick up changes from newly created config files (fixes #29)
- Use `directories-rs` to find user level config directory path

A new `ConfigWatcher` is introduced, which opportunistically watches the
closest existing parent of target paths and rewatches dynamically accordingly.
This way we can get notifications for new file creations in addition to
modifications.

`ConfigWatcher` also does debounce (100ms by default) to only trigger
one config reload for multiple events in a short time period.

The core idea of the watcher is as follow:

Given a watching target path `/base/sub/config.toml`,
a) readd watches if any segment in the path is created/removed;
b) reload config only if the event is about the full target path.

The actual implementation extends this idea to handle multiple targets.
Please refer to `config_watcher.rs` for details and extensive test cases.

A few design points:

- Notify event handling is done in a separate thread, rather than in
  notify's internal event handler thread, because the code is more
  readable and calling unwatch/watch in that thread may lead to deadlock
  in some cases. (notify-rs/notify#410, notify-rs/notify#463)
- For simplicity, `ConfigWatcher` doesn't  provide infomation about
  which exact config file was changed, assuming a reload would need to
  read all config files anyway.
Aetf added a commit to shell-pool/shpool that referenced this issue Jun 25, 2024
- Simple merging logic for `Config`
- Load config from system-wide locations (fixes #30)
- Dynamically pick up changes from newly created config files (fixes #29)
- Use `directories-rs` to find user level config directory path

A new `ConfigWatcher` is introduced, which opportunistically watches the
closest existing parent of target paths and rewatches dynamically accordingly.
This way we can get notifications for new file creations in addition to
modifications.

`ConfigWatcher` also does debounce (100ms by default) to only trigger
one config reload for multiple events in a short time period.

The core idea of the watcher is as follow:

Given a watching target path `/base/sub/config.toml`,
a) readd watches if any segment in the path is created/removed;
b) reload config only if the event is about the full target path.

The actual implementation extends this idea to handle multiple targets.
Please refer to `config_watcher.rs` for details and extensive test cases.

A few design points:

- Notify event handling is done in a separate thread, rather than in
  notify's internal event handler thread, because the code is more
  readable and calling unwatch/watch in that thread may lead to deadlock
  in some cases. (notify-rs/notify#410, notify-rs/notify#463)
- For simplicity, `ConfigWatcher` doesn't  provide infomation about
  which exact config file was changed, assuming a reload would need to
  read all config files anyway.
Aetf added a commit to shell-pool/shpool that referenced this issue Jun 25, 2024
- Simple merging logic for `Config`
- Load config from system-wide locations (fixes #30)
- Dynamically pick up changes from newly created config files (fixes #29)
- Use `directories-rs` to find user level config directory path

A new `ConfigWatcher` is introduced, which opportunistically watches the
closest existing parent of target paths and rewatches dynamically accordingly.
This way we can get notifications for new file creations in addition to
modifications.

`ConfigWatcher` also does debounce (100ms by default) to only trigger
one config reload for multiple events in a short time period.

The core idea of the watcher is as follow:

Given a watching target path `/base/sub/config.toml`,
a) readd watches if any segment in the path is created/removed;
b) reload config only if the event is about the full target path.

The actual implementation extends this idea to handle multiple targets.
Please refer to `config_watcher.rs` for details and extensive test cases.

A few design points:

- Notify event handling is done in a separate thread, rather than in
  notify's internal event handler thread, because the code is more
  readable and calling unwatch/watch in that thread may lead to deadlock
  in some cases. (notify-rs/notify#410, notify-rs/notify#463)
- For simplicity, `ConfigWatcher` doesn't  provide infomation about
  which exact config file was changed, assuming a reload would need to
  read all config files anyway.
Aetf added a commit to shell-pool/shpool that referenced this issue Jun 25, 2024
* Gracefully handle missing XDG_RUNTIME_DIR env var in tests

* Configuration loading overhaul

- Simple merging logic for `Config`
- Load config from system-wide locations (fixes #30)
- Dynamically pick up changes from newly created config files (fixes #29)
- Use `directories-rs` to find user level config directory path

A new `ConfigWatcher` is introduced, which opportunistically watches the
closest existing parent of target paths and rewatches dynamically accordingly.
This way we can get notifications for new file creations in addition to
modifications.

`ConfigWatcher` also does debounce (100ms by default) to only trigger
one config reload for multiple events in a short time period.

The core idea of the watcher is as follow:

Given a watching target path `/base/sub/config.toml`,
a) readd watches if any segment in the path is created/removed;
b) reload config only if the event is about the full target path.

The actual implementation extends this idea to handle multiple targets.
Please refer to `config_watcher.rs` for details and extensive test cases.

A few design points:

- Notify event handling is done in a separate thread, rather than in
  notify's internal event handler thread, because the code is more
  readable and calling unwatch/watch in that thread may lead to deadlock
  in some cases. (notify-rs/notify#410, notify-rs/notify#463)
- For simplicity, `ConfigWatcher` doesn't  provide infomation about
  which exact config file was changed, assuming a reload would need to
  read all config files anyway.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B-unconfirmed Needs verification (by maintainer or testing party)
Projects
None yet
Development

No branches or pull requests

2 participants