Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions compio-runtime/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,15 @@ impl Runtime {
}
}

impl Drop for Runtime {
fn drop(&mut self) {
self.enter(|| {
self.timer_runtime.borrow_mut().clear();
self.scheduler.clear();
})
}
}

impl AsRawFd for Runtime {
fn as_raw_fd(&self) -> RawFd {
self.driver.borrow().as_raw_fd()
Expand Down
33 changes: 27 additions & 6 deletions compio-runtime/src/runtime/scheduler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::runtime::scheduler::{local_queue::LocalQueue, send_wrapper::SendWrapper};
use std::{future::Future, marker::PhantomData, sync::Arc};

use async_task::{Runnable, Task};
use compio_driver::NotifyHandle;
use crossbeam_queue::SegQueue;
use std::{future::Future, marker::PhantomData, sync::Arc};

use crate::runtime::scheduler::{local_queue::LocalQueue, send_wrapper::SendWrapper};

mod local_queue;
mod send_wrapper;
Expand All @@ -24,8 +26,8 @@ impl TaskQueue {

/// Pushes a `Runnable` task to the appropriate queue.
///
/// If the current thread is the same as the creator thread, push to the local queue.
/// Otherwise, push to the sync queue.
/// If the current thread is the same as the creator thread, push to the
/// local queue. Otherwise, push to the sync queue.
fn push(&self, runnable: Runnable, notify: &NotifyHandle) {
if let Some(local_queue) = self.local_queue.get() {
local_queue.push(runnable);
Expand All @@ -37,7 +39,8 @@ impl TaskQueue {
}
}

/// Pops at most one task from each queue and returns them as `(local_task, sync_task)`.
/// Pops at most one task from each queue and returns them as `(local_task,
/// sync_task)`.
///
/// # Safety
///
Expand All @@ -48,7 +51,8 @@ impl TaskQueue {

let local_task = local_queue.pop();

// Perform an empty check as a fast path, since `SegQueue::pop()` is more expensive.
// Perform an empty check as a fast path, since `SegQueue::pop()` is more
// expensive.
let sync_task = if self.sync_queue.is_empty() {
None
} else {
Expand Down Expand Up @@ -105,10 +109,14 @@ impl Scheduler {
// Use `Weak` to break reference cycle.
// `TaskQueue` -> `Runnable` -> `TaskQueue`
let task_queue = Arc::downgrade(&self.task_queue);
let thread_guard = SendWrapper::new(());

move |runnable| {
if let Some(task_queue) = task_queue.upgrade() {
task_queue.push(runnable, &notify);
} else if thread_guard.get().is_none() {
// It's not safe to drop the runnable in another thread.
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment should explain why it's not safe to drop the runnable in another thread, and clarify that the condition thread_guard.get().is_none() means we're on a different thread. Consider expanding: 'If we're on a different thread (thread_guard.get().is_none()) and the task queue has been dropped, we cannot safely drop the runnable because [reason], so we forget it to prevent the drop from running.'

Suggested change
// It's not safe to drop the runnable in another thread.
// If we're on a different thread (thread_guard.get().is_none()) and the task queue has been dropped,
// we cannot safely drop the runnable because its destructor may access thread-local or queue-related resources
// that are only valid on the original thread. Dropping it here could lead to undefined behavior.
// Therefore, we use std::mem::forget to prevent the destructor from running.

Copilot uses AI. Check for mistakes.
std::mem::forget(runnable);
}
}
};
Expand Down Expand Up @@ -151,4 +159,17 @@ impl Scheduler {
// on `TaskQueue`'s creator thread.
!unsafe { self.task_queue.is_empty() }
}

pub(crate) fn clear(&self) {
loop {
// SAFETY:
// `Scheduler` is `!Send` and `!Sync`, so this method is only called
// on `TaskQueue`'s creator thread.
let tasks = unsafe { self.task_queue.pop() };

if let (None, None) = tasks {
break;
}
}
}
Comment on lines +163 to +174
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clear() method pops tasks but doesn't explicitly drop them or handle them in any way. While Rust will drop the returned tasks automatically when they go out of scope at the end of each loop iteration, this implicit behavior should be documented to clarify that the purpose is to drop all pending tasks. Consider adding a comment explaining this or explicitly binding and dropping: let _tasks = unsafe { self.task_queue.pop() }; // Drop all pending tasks.

Copilot uses AI. Check for mistakes.
}
4 changes: 4 additions & 0 deletions compio-runtime/src/runtime/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ impl TimerRuntime {
}
});
}

pub fn clear(&mut self) {
self.wheel.clear();
}
}

pub struct TimerFuture(TimerKey);
Expand Down
11 changes: 11 additions & 0 deletions compio-runtime/tests/drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#[test]
fn test_drop() {
compio_runtime::Runtime::new().unwrap().block_on(async {
compio_runtime::spawn(async {
loop {
compio_runtime::time::sleep(std::time::Duration::from_secs(1)).await;
}
})
.detach();
})
}
Loading