crossbeam-rs / crossbeam

Tools for concurrent programming in Rust
Apache License 2.0
7.33k stars 461 forks source link

Potential deadlock and resource leak when `Receiver` is dropped in `crossbeam_channel`'s bounded channel #1102

Open eval-exec opened 5 months ago

eval-exec commented 5 months ago

I have encountered a situation where the code gets stuck indefinitely and the content in the channel's buffer is not dropped when the receiver is dropped.

I'm not sure if this behavior is intended or if it represents a bug in the crossbeam_channel library. The situation is causing a potential deadlock and resource leaks in my code. I would appreciate further investigation by the upstream maintainers to clarify this behavior.

Here is the minimal reproducing code:

use crossbeam_channel::Sender;
use std::io::Error;

pub struct Request<A, R> {
    pub responder: Sender<R>,
    pub arguments: A,
}

impl<A, R> Request<A, R> {
    /// Call the service with the arguments and wait for the response.
    pub fn call(sender: &Sender<Request<A, R>>, arguments: A) -> Option<R> {
        let (responder, response) = crossbeam_channel::bounded(0);
        sender
            .send(Request {
                responder,
                arguments,
            })
            .unwrap();
        response.recv().ok()
    }
}

#[derive(Debug)]
struct Block {
    b: usize,
}

impl Drop for Block {
    fn drop(&mut self) {
        println!("dropping block {}", self.b);
    }
}

type ProcessBlockRequest = Request<Block, Result<(), Error>>;

struct ChainController {
    process_block_sender: Sender<ProcessBlockRequest>,
}

fn chain_service() -> ChainController {
    let (ct, cx) = crossbeam_channel::bounded::<ProcessBlockRequest>(10);

    std::thread::spawn(move || {
        let _cx_clone = cx.clone();
        return;
    });

    ChainController {
        process_block_sender: ct,
    }
}

impl ChainController {
    fn process_block(&self, b: Block) {
        Request::call(&self.process_block_sender, b);
    }
}

fn main() {
    let chain_controller = chain_service();
    chain_controller.process_block(Block { b: 1 });
}

I expect that fn main won't get stuck and I expect to see "dropping block 1" printed to the terminal. However, in reality, the code gets stuck and "dropping block 1" is not printed.

If I comment out let _cx_clone = cx.clone();, then it does not hang.

taiki-e commented 5 months ago

Same issue as https://github.com/rust-lang/rust/issues/107466? If so we just need to port its fix.