Fixed race condition in `ResponseVar::wait_done`.

The recent change of `Var::wait_update` to an async method caused it to not instantiate the future at the moment the method is called, so the code that double checks if the response is done in `wait_done` no longer covered all conditions.
This commit is contained in:
Samuel Guerra 2024-01-14 20:20:35 -03:00
parent 7af83f6990
commit e092792ff6
4 changed files with 21 additions and 45 deletions

View File

@ -1,34 +1,3 @@
* Repeatedly opening the markdown example sometimes shows blank spaces where text should be.
- Blank spaces are the null char (0). Font was `"<empty>"` when it failed, is shaping before font load and then never updating?
- Waiting a single font ResponseVar times-out, but the task itself of that font loading does not.
- Bug in the response future?
- Yes, replacing `wait_into_rsp` loop probing and sleeping *fixes* the bug.
- Need an actual fix.
- `WaitUpdateFut` timeout, hook never called.
- Having trouble tracing modify (bug vanishes if there are prints).
```
ok:
!!: modify Arc(0x23dd0339870)
!!: apply modify Arc(0x23dd0339870)
!!: modify Arc(0x23dd0339ab0)
!!: apply modify Arc(0x23dd0339ab0)
!!: modify Arc(0x23dd0338eb0)
!!: apply modify Arc(0x23dd0338eb0)
!!: modify Arc(0x23dd0339630)
!!: apply modify Arc(0x23dd0339630)
timeout:
!!: modify Arc(0x23dfa6a1da0)
!!: apply modify Arc(0x23dfa6a1da0)
!!: modify Arc(0x23dfa6a2340)
!!: modify Arc(0x23dfa6a2520)
!!: apply modify Arc(0x23dfa6a2340)
!!: apply modify Arc(0x23dfa6a2520)
!!: TIMEOUT Arc(0x23dfa6a2520)
```
* `StyleMix` does not capture `extend_style`/`replace_style` on the same widget, so it ends-up ignored. Need
to promote this pattern.

View File

@ -1258,7 +1258,7 @@ mod response {
fn race_condition() {
let mut app = APP.minimal().run_headless(false);
for _ in 0..1000 {
for _ in 0..100 {
let a = task::respond(async {
task::deadline(1.ms()).await;
'a'

View File

@ -1182,14 +1182,16 @@ pub trait Var<T: VarValue>: IntoVar<T, Var = Self> + AnyVar + Clone {
/// in sync with the UI, but it will elapse in any thread when the variable updates after the future is instantiated.
///
/// Note that outside of the UI tree there is no variable synchronization across multiple var method calls, so
/// a sequence of `get(); wait_is_new().await; get();` can miss a value between `get` and `wait_is_new`.
/// a sequence of `get(); wait_is_new().await; get();` can miss a value between `get` and `wait_update`. The returned
/// future captures the [`last_update`] at the moment this method is called, this can be leveraged by *double-checking* to
/// avoid race conditions, see the [`wait_value`] default implementation for more details.
///
/// [`get`]: Var::get
/// [`wait_value`]: Var::wait_value
/// [`last_update`]: AnyVar::last_update
/// [`is_new`]: AnyVar::is_new
#[allow(async_fn_in_trait)]
async fn wait_update(&self) -> VarUpdateId {
crate::future::WaitUpdateFut::new(self).await
fn wait_update(&self) -> impl std::future::Future<Output = VarUpdateId> {
crate::future::WaitUpdateFut::new(self)
}
/// Awaits for [`is_animating`] to change from `true` to `false`.
@ -1200,9 +1202,20 @@ pub trait Var<T: VarValue>: IntoVar<T, Var = Self> + AnyVar + Clone {
/// If the variable does have the [`VarCapabilities::NEW`] the future is always ready.
///
/// [`is_animating`]: AnyVar::is_animating
fn wait_animation(&self) -> impl std::future::Future<Output = ()> {
crate::future::WaitIsNotAnimatingFut::new(self)
}
///Awaits for a value that passes the `predicate`.
#[allow(async_fn_in_trait)]
async fn wait_animation(&self) {
crate::future::WaitIsNotAnimatingFut::new(self).await
async fn wait_value(&self, mut predicate: impl FnMut(&T) -> bool) {
while !self.with(&mut predicate) {
let future = self.wait_update();
if self.with(&mut predicate) {
break;
}
future.await;
}
}
/// Visit the current value of the variable, if it [`is_new`].

View File

@ -128,13 +128,7 @@ impl<T: VarValue> ResponseVar<T> {
///
/// [`rsp`]: Self::rsp
pub async fn wait_done(&self) {
while !self.is_done() {
let w = self.wait_update();
if self.is_done() {
break;
}
w.await;
}
self.wait_value(Response::is_done).await
}
/// Clone the response, if present and new.