Skip to content

Store with SyncStorage deadlocks #4935

@alextechcc

Description

@alextechcc

Problem

When I create a sync store and write to it from another thread while writing/reading it from dioxus renders, I get a deadlock between the two threads.

Steps to reproduce the behavior:

fn app() -> Element {
    let mut store: Store<HashMap<usize, String>, WriteSignal<HashMap<usize, String>, SyncStorage>>
        = use_hook(move || Store::new_maybe_sync(HashMap::<usize, String>::default())).into();
    use_hook({
        let mut s = store.clone();
        move || {
            thread::spawn(move || {
                let (mut n, mut rng) = (0usize, rand::thread_rng());
                loop {
                    n = n.wrapping_add(1);
                    for i in 1..120 {
                        s.write().insert(i, n.to_string());
                    }
                    thread::sleep(Duration::from_millis(rng.gen_range(1..120)));
                }
            });
        }
    });

    let items: Vec<_> = store.read().clone().into_iter().collect();

    rsx! {
        for (k, v) in items {
            input {
                key: "{k}",
                value: "{v}",
                oninput: move |e| { store.write().insert(k, e.value().into()); }
            }
        }
    }
}

Results in this log when I use the deadlock_detection feature from parking_lot:

deadlock from detect_deadlocks feature

Sometimes I have to resize the window or restart the app to make it occur.

Expected behavior
No deadlocking when writing/reading to SyncStorage stores from another thread and the Dioxus thread.

Environment:

  • Dioxus version: 0.17.1
  • Rust version: 1.92.0-nightly
  • OS info: Arch Linux x86_64
  • App platform: Desktop/Webview

Questionnaire

I am trying to figure out how to make SelectorScope on write return WriteMetadata that on drop will do the mark_dirty, but I'm a bit over my head with all the abstractions here diving in from scratch.

I believe that the issue is an AB BA locking problem where SelectorScope immediately does self.mark_dirty() on writes: https://github.com/DioxusLabs/dioxus/blob/main/packages/stores/src/scope.rs#L212 which I believe tries to lock subscribers to notify them, then tries to grab the write lock on the value, but when rendering the scope with a read on the signal - I believe that the dioxus thread is trying to grab a lock on the value, then trying to grab a lock on the subscribers.

This is in contrast to signals which return a guard which notifies subscribers when dropped: https://github.com/DioxusLabs/dioxus/blob/main/packages/signals/src/signal.rs#L528

Also, a side note, I'm not sure if the way I made the hook is correct - but it would be nice to have this in prelude:

pub fn use_sync_store<T, F>(make_default: F) -> Store<T, WriteSignal<T, SyncStorage>>
where
    T: 'static + Send + Sync,
    F: FnOnce() -> T + 'static,
{
    use_hook(move || Store::new_maybe_sync(make_default())).into()
}

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions