Xudong-Huang / generator-rs

rust stackful generator library
Apache License 2.0
286 stars 35 forks source link

Safe sendable `Generator` is unsound #58

Open zetanumbers opened 1 month ago

zetanumbers commented 1 month ago

Sadly sendable Generator is currently unsound because thread local variables may leak non-sendable values from within the our coroutine. Here's example:

#![forbid(unsafe_code)]

use std::{cell::Cell, rc::Rc, thread};

use generator::Gn;

const COUNT_ITERATIONS: u64 = 1000000000;

fn main() {
    thread_local! {
        static COUNTER: Cell<Option<Rc<Cell<u64>>>> = Cell::new(None);
    }

    let counter = Rc::new(Cell::new(0));
    COUNTER.with(|tl| tl.set(Some(Rc::clone(&counter))));

    let mut generator = Gn::new_scoped(move |mut s| {
        let counter = COUNTER.with(|tl| tl.take()).unwrap();
        s.yield_with(());
        println!("Rc {:p} in thread {:?}", counter, thread::current());
        for _ in 0..COUNT_ITERATIONS {
            counter.set(counter.get() + 1)
        }
    });
    generator.next().unwrap();

    let child = thread::spawn(move || {
        generator.next().unwrap();
    });

    println!("Rc {:p} in thread {:?}", counter, thread::current());
    for _ in 0..COUNT_ITERATIONS {
        counter.set(counter.get() + 1)
    }

    child.join().unwrap();
    assert_eq!(counter.get(), 2 * COUNT_ITERATIONS);
}

Output:

Rc 0x60000058d210 in thread Thread { id: ThreadId(1), name: Some("main"), .. }
Rc 0x60000058d210 in thread Thread { id: ThreadId(2), name: None, .. }
thread 'main' panicked at src/main.rs:37:5:
assertion `left == right` failed
  left: 1028478159
 right: 2000000000

See https://blaz.is/blog/post/lets-pretend-that-task-equals-thread/ and https://users.rust-lang.org/t/controversial-opinion-keeping-rc-across-await-should-not-make-future-send-by-itself/100554/12 for details.

zetanumbers commented 1 month ago

Added the example

Xudong-Huang commented 1 month ago

This is a long known issue. This is why may spawn is unsafe. You should use coroutine_local! here. And I have no better idea to design the API, do you have any suggestions?