slint-ui / slint

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.
https://slint.dev
Other
16.94k stars 568 forks source link

already mutably borrowed: BorrowError #4888

Closed lifeRobot closed 6 months ago

lifeRobot commented 6 months ago

version

slint 1.5.0 rust 1.76.0

example code

code

Cargo.toml: ``` [dependencies] slint = "1.5.0" cbsk_base = { version = "0.1.7", features = ["once_cell", "tokio_full"] } cbsk_mut_data = { version = "0.1.4", default-features = false, features = ["obj"] } ``` main.rs : ``` use std::rc::Rc; use std::time::Duration; use cbsk_base::once_cell::sync::Lazy; use cbsk_base::tokio; use cbsk_base::tokio::task::JoinHandle; use cbsk_mut_data::mut_data_obj::MutDataObj; use slint::{Model, VecModel}; static A: Lazy>>> = Lazy::new(MutDataObj::default); #[tokio::main] pub async fn main() { let h = vec![set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data()]; for h in h { h.await.unwrap() } } fn set_row_data() -> JoinHandle<()> { tokio::spawn(async { loop { let count = A.row_count(); println!("count is {count}"); A.set_row_data(3, 0); tokio::time::sleep(Duration::from_millis(100)).await; } }) } ```

error msg

thread 'tokio-runtime-worker' panicked at E:\dev\rust\cargo\registry\src\index.crates.io-6f17d22bba15001f\i-slint-core-1.5.0\model.rs:395:20:
already mutably borrowed: BorrowError
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

image

analysis

  1. This problem arises because use self.array.borrow().len() from slint-core::model.rs in line 395
  2. self.array.borrow().len() used the following code internally:
    code

let b = borrow.get().wrapping_add(1);
if !is_reading(b) {
    // Incrementing borrow can result in a non-reading value (<= 0) in these cases:
    // 1. It was < 0, i.e. there are writing borrows, so we can't allow a read borrow
    //    due to Rust's reference aliasing rules
    // 2. It was isize::MAX (the max amount of reading borrows) and it overflowed
    //    into isize::MIN (the max amount of writing borrows) so we can't allow
    //    an additional read borrow because isize can't represent so many read borrows
    //    (this can only happen if you mem::forget more than a small constant amount of
     //    `Ref`s, which is not good practice)
    None
} else {
    // Incrementing borrow can result in a reading value (> 0) in these cases:
    // 1. It was = 0, i.e. it wasn't borrowed, and we are taking the first read borrow
    // 2. It was > 0 and < isize::MAX, i.e. there were read borrows, and isize
    //    is large enough to represent having one more read borrow
    borrow.set(b);
    Some(BorrowRef { borrow })
}

fn is_reading is:

const UNUSED: BorrowFlag = 0;

#[inline(always)]
fn is_reading(x: BorrowFlag) -> bool {
    x > UNUSED
}

  1. According to the above code, the problem is borrow.get().wrapping_add(1) < 0, this is a logical problem with reference counting, so, this may not be is slint problem, but is rust problem
  2. According to the code, this is caused by multithreading, but I believe that the program should be allowed to run for a whole day without restarting. However, one day this problem will occur, and it's just that multithreading caused this problem to occur earlier

opinion

I suggest changing self.array.borrow() to self.array.try_borrow(), or use third-party internal variability code, such as dark-std or cbsk_mut_data

ogoffart commented 6 months ago

I'm not sure what your code is doing because i'm not familiar with cbsk. But i'm afraid that there might be something wrong with threading and this MutDataObj. VecModel is not Sync, and Rc is not Send, so there shouldn't be accessible from different thread at the same time, but it seems that your code does that.

lifeRobot commented 6 months ago

I'm not sure what your code is doing because i'm not familiar with cbsk. But i'm afraid that there might be something wrong with threading and this MutDataObj. VecModel is not Sync, and Rc is not Send, so there shouldn't be accessible from different thread at the same time, but it seems that your code does that.

  1. The multi-threaded code above simulates the logic of receiving data from the backend and updating UI data
  2. But now, I think I have found the problem, which is caused by multithreading, because when multiple threads run simultaneously, there may be situations where both self.array.borrow_mut() and self.array.borrow() are called, self.array.borrow_mut() will set borrow to -1, adding locks to multiple threads can solve this problem
ogoffart commented 6 months ago

I can trivially reproduce the bug without using Slint by simply using a RefCell:

use std::rc::Rc;
use std::time::Duration;
use cbsk_base::once_cell::sync::Lazy;
use cbsk_base::tokio;
use cbsk_base::tokio::task::JoinHandle;
use cbsk_mut_data::mut_data_obj::MutDataObj;
use std::cell::RefCell;

static A: Lazy<MutDataObj<Rc<RefCell<i32>>>> = Lazy::new(MutDataObj::default);

#[tokio::main]
pub async fn main() {
    let h = vec![set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data(), set_row_data()];
    for h in h {
        h.await.unwrap()
    }
}

fn set_row_data() -> JoinHandle<()> {
    tokio::spawn(async {
        loop {
            let count = *A.borrow();
            println!("count is {count}");
            *A.borrow_mut() += 1;
            tokio::time::sleep(Duration::from_millis(100)).await;
        }
    })
}

I think that MutDataObj is unsound.

(the output is something like)

count is 0
count is 0
count is 0
thread 'tokio-runtime-workercount is 2
' panicked at src/main.rscount is 1
:28:16:
already borrowed: BorrowMutError
count is 1
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
count is 1
count is 1
count is 3
count is 4
count is 9
count is 10
count is 11
count is 12
count is 12
thread 'tokio-runtime-worker' panicked at src/main.rs:28:16:
already borrowed: BorrowMutError
thread 'main' panicked at src/main.rs:19:17:

I'm going to close this since this is not a Slint bug.