Find out where a Future gets cancelled / dropped

I have a tokio::sync::oneshot channel, which sometimes fails to send. So I guess my Receiver end got dropped, but I am wondering where the drop originates from.

My program is pretty big and I have no clue where it could have happened. My initial naive idea was to wrap the Receiver in some struct that implements Drop and is awaitable and then print out a backtrace in the drop.

But I am failing to do it. As soon as I implement Drop I am no longer able to await the inner Receiver, because this would require moving it out of Self.

This idea is probably not good anyway. How would you approach my problem of finding the location where my future is cancelled?

What if you moved the Drop implementation out of the wrapper and into one of its other fields?

I'm using this to print a backtrace on drop:

struct DropToken;

impl Drop for DropToken {
    fn drop(&mut self) {
        let bt = Backtrace::force_capture();
        println!("Dropped:\n{bt}");
    }
}

And this is my wrapper

struct NoisyReceiver<T> {
    receiver: Receiver<T>,
    _token: DropToken,
}

impl<T> NoisyReceiver<T> {
    fn new(receiver: Receiver<T>) -> Self {
        NoisyReceiver {
            receiver,
            _token: DropToken,
        }
    }
}

impl<T> Future for NoisyReceiver<T> {
    type Output = <Receiver<T> as Future>::Output;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        println!("Poll");
        Pin::new(&mut self.receiver).poll(cx)
    }
}

Here is an example program to test the implementation:

#[tokio::main]
async fn main() {
    let (sender, receiver) = oneshot::channel();
    let receiver = NoisyReceiver::new(receiver);
    
    println!("Sending");
    sender.send(42).unwrap();
    println!("Waiting");
    let result = receiver.await.unwrap();
    println!("Result: {result}");
}

And its output when compiled in release mode to get a cleaner backtrace:

Sending
Waiting
Poll
Dropped:
   0: core::ptr::drop_in_place<playground::NoisyReceiver<i32>>
   1: playground::main::{{closure}}
   2: playground::main
   3: std::sys_common::backtrace::__rust_begin_short_backtrace
   4: std::rt::lang_start::{{closure}}
   5: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/core/src/ops/function.rs:284:13
   6: std::panicking::try::do_call
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/std/src/panicking.rs:500:40
   7: std::panicking::try
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/std/src/panicking.rs:464:19
   8: std::panic::catch_unwind
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/std/src/panic.rs:142:14
   9: std::rt::lang_start_internal::{{closure}}
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/std/src/rt.rs:148:48
  10: std::panicking::try::do_call
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/std/src/panicking.rs:500:40
  11: std::panicking::try
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/std/src/panicking.rs:464:19
  12: std::panic::catch_unwind
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/std/src/panic.rs:142:14
  13: std::rt::lang_start_internal
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/std/src/rt.rs:148:20
  14: main
  15: __libc_start_main
  16: _start

Result: 42

You can try it yourself - everything is on the playground.

6 Likes

Ohh thanks a lot, that works beautifully :heart:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.