We recently added the following changes in #901.
if ::std::thread::panicking() {
::std::process::abort()
}
It was added in Task::drop_future, the drop implementation for Task and Task::drop. The last one seems to be introducing a subtle condition where we might leak memory which I tried to reproduce in this way.
#[test]
#[should_panic]
fn check_drop() {
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
let first = std::thread::spawn(move || {
compio_runtime::Runtime::new().unwrap().block_on(async {
// This future is never polled for the second time because the timer wheel will notify
// us about the expiration of 1 second first and we will proceed to panic next!
let handle = compio_runtime::spawn(async {
sleep(Duration::from_secs(3)).await;
10
});
tx.send(handle).unwrap();
sleep(Duration::from_secs(1)).await;
panic!("top future panics!");
})
});
let second = std::thread::spawn(move || {
compio_runtime::Runtime::new().unwrap().block_on(async {
let handle = rx.recv().unwrap();
compio_runtime::spawn(async {
// output never arrives because the first runtime panics before the spawned future
// gets to return the output and in that case it clears the executor which leads to
// drop being called on the task inside of it. However, here since we had registered
// our waker and the `Task::drop` does not decrement the refcount as it will
// encounter the panicking check and return early, when this runtime gets dropped,
// this task will leak memory because its refcount has not reached zero.
let _output = handle.await;
})
.detach();
sleep(Duration::from_secs(3)).await;
})
});
second.join().unwrap();
// joining this will panic!
first.join().unwrap();
}
This may leak the underlying memory because when the first runtime is clearing the executor, it is calling Task::drop for the task that we spawned there which immediately returns because the check for whether we are panicking or not returns true. Since we return early, the refcount does not get decremented for the waker that we had registered and that leads to the memory for the task on the second runtime from never being deallocated.
We recently added the following changes in #901.
It was added in
Task::drop_future, the drop implementation forTaskandTask::drop. The last one seems to be introducing a subtle condition where we might leak memory which I tried to reproduce in this way.This may leak the underlying memory because when the first runtime is clearing the executor, it is calling
Task::dropfor the task that we spawned there which immediately returns because the check for whether we are panicking or not returns true. Since we return early, the refcount does not get decremented for the waker that we had registered and that leads to the memory for the task on the second runtime from never being deallocated.