Skip to content

F: Ungil on nightly insufficient for soundness of allow_threads, even without scoped-tls or send_wrapper #3658

Open
@steffahn

Description

@steffahn
use std::cell::Cell;
use std::thread;

use pyo3::prelude::*;

#[pyclass]
struct Class(Cell<u8>);

fn main() -> PyResult<()> {
    Python::with_gil(|py| {
        let class_1 = Py::new(py, Class(Cell::new(0)))?;
        let class_2 = class_1.clone_ref(py);
        let cell: &Cell<u8> = &class_1.borrow(py).0;
        py.allow_threads(|| {
            let t = thread::spawn(move || {
                Python::with_gil(|py| {
                    let cell: &Cell<u8> = &class_2.borrow(py).0;
                    let init = cell.get();
                    loop {
                        let gotten = cell.get();
                        if init != gotten {
                            println!("concurrently modified! - {init} vs {gotten}");
                            break;
                        }
                    }
                });
            });
            loop {
                cell.set(cell.get().wrapping_add(1));
                if t.is_finished() {
                    break;
                }
            }
            t.join().unwrap();
        });
        PyResult::Ok(())
    })?;
    PyResult::Ok(())
}
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/…`
concurrently modified! - 63 vs 66

(concurrent modification of a Cell is UB; I skipped the additional step where you could abuse this data race to cause more obvious UB such as reading memory out of bounds)

This is issue is possibly irrelevant in light of existing issues like #3640 (and fixed by #3646 which abandons Ungil). Also, it might in principle also be fixable by making .borrow do a dynamic check that we’re in the same thread as previous .borrow; or by demanding Sync for pyclass; or letting the user choose between either of the two options (dynamic check or Sync).

I’m raising this issue as another point of argument that nightly Ungil isn’t as good as it claims to be, and this would need to be addressed in case the argument would win that we should keep Ungil (with documented unsoundness warnings) for its benefits of not conservatively requiring Send.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions