Skip to content

Commit

Permalink
Fix AsyncFileDialog blocking on Windows
Browse files Browse the repository at this point in the history
ThreadFuture would send a mutex to the thread it spawned, which would
then immediately lock that mutex to pass it into whichever blocking
callback wanted to write to the data inside it. Meanwhile, calling
`poll` on that ThreadFuture would *also* lock that mutex, blocking
until the spawned thread finished running and hence defeating the entire
purpose of using a future, and possibly even causing the *spawned*
thread to block instead if `poll` was called fast enough, causing a
deadlock. This is fixed by separating the `data` and `waker` into two
separate mutexes; calling `poll` always sets the `waker` but doesn't
lock the mutex for `data`.
  • Loading branch information
valadaptive committed Apr 29, 2024
1 parent 7db7985 commit c472f94
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 21 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Change Log

## Unreleased
- Fix `AsyncFileDialog` blocking the executor on Windows (#191)

## 0.14.0
- i18n for GTK and XDG Portal
- Use XDG Portal as default
Expand Down
51 changes: 30 additions & 21 deletions src/backend/win_cid/thread_future.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,32 @@
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Mutex, TryLockError};

use std::task::{Context, Poll, Waker};

struct FutureState<R> {
waker: Option<Waker>,
data: Option<R>,
waker: Mutex<Option<Waker>>,
data: Mutex<Option<R>>,
}

unsafe impl<R> Send for FutureState<R> {}

pub struct ThreadFuture<R> {
state: Arc<Mutex<FutureState<R>>>,
state: Arc<FutureState<R>>,
}

unsafe impl<R> Send for ThreadFuture<R> {}

impl<R: 'static> ThreadFuture<R> {
impl<R: Send + 'static> ThreadFuture<R> {
pub fn new<F: FnOnce(&mut Option<R>) + Send + 'static>(f: F) -> Self {
let state = Arc::new(Mutex::new(FutureState {
waker: None,
data: None,
}));
let state = Arc::new(FutureState {
waker: Mutex::new(None),
data: Mutex::new(None),
});

{
let state = state.clone();
std::thread::spawn(move || {
let mut state = state.lock().unwrap();

f(&mut state.data);
f(&mut state.data.lock().unwrap());

if let Some(waker) = state.waker.take() {
if let Some(waker) = state.waker.lock().unwrap().take() {
waker.wake();
}
});
Expand All @@ -44,13 +40,26 @@ impl<R> std::future::Future for ThreadFuture<R> {
type Output = R;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut state = self.state.lock().unwrap();
let state = &self.state;
let data = state.data.try_lock();

if state.data.is_some() {
Poll::Ready(state.data.take().unwrap())
} else {
state.waker = Some(cx.waker().clone());
Poll::Pending
match data {
Ok(mut data) => {
match data.take() {
Some(data) => Poll::Ready(data),
None => {
*state.waker.lock().unwrap() = Some(cx.waker().clone());
Poll::Pending
}
}
}
Err(TryLockError::Poisoned(err)) => {
panic!("{}", err);
}
Err(TryLockError::WouldBlock) => {
*state.waker.lock().unwrap() = Some(cx.waker().clone());
Poll::Pending
}
}
}
}

0 comments on commit c472f94

Please sign in to comment.