Skip to content
Closed
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d36d080
fix: put preempted Syscall(Executing) coroutines back in ready queue …
Copilot Apr 3, 2026
5581cff
fix: handle Syscall(Executing) in try_resume and check_ready instead …
Copilot Apr 3, 2026
df1da46
Merge branch 'master' into copilot/fix-coroutine-scheduler-bug
loongs-zhang Apr 3, 2026
9ecd4ea
fix: reorder change_state to log before notifying MonitorListener
Copilot Apr 3, 2026
a0a79f0
fix: translate change_state comment to bilingual Chinese/English
Copilot Apr 3, 2026
87d47a6
fix compile
loongs-zhang Apr 3, 2026
fac1168
clean code
loongs-zhang Apr 3, 2026
726199b
fix: conditionally reorder change_state to prevent both live-lock and…
Copilot Apr 3, 2026
4675117
fix: hook write with re-entrancy guard to prevent preemption during l…
Copilot Apr 3, 2026
ca70cd7
refactor: use impl_hook! for write and implement special facade in wr…
Copilot Apr 4, 2026
4384994
fix: reorder impl_facade! to transition state before logging, revert …
Copilot Apr 4, 2026
c49454d
move code
loongs-zhang Apr 4, 2026
7e7f4f5
refactor: move IN_FACADE re-entrancy guard from mod.rs to write.rs
Copilot Apr 4, 2026
dd487c2
fix: prevent SIGURG preemption live-lock by hooking write and reorder…
Copilot Apr 4, 2026
56f1551
revert: rollback all scheduler.rs changes per reviewer feedback
Copilot Apr 4, 2026
b0d1948
reorder
loongs-zhang Apr 4, 2026
adf7498
fix: bypass all layers (io_uring/NIO) in WriteSyscallFacade for stdou…
Copilot Apr 4, 2026
95378a2
code polish
loongs-zhang Apr 4, 2026
d0ff4dd
fix: add early bypass in write() dispatcher for stdout/stderr and rev…
Copilot Apr 4, 2026
3b8cf28
clean code
loongs-zhang Apr 4, 2026
f2a219a
clean code
loongs-zhang Apr 4, 2026
5ddf2ef
fix: conditionally reorder change_state for →Running to prevent live-…
Copilot Apr 4, 2026
6ad23ff
fix: skip SIGURG preemption for Syscall-state coroutines to prevent d…
Copilot Apr 4, 2026
0178f4e
fix: move Syscall guard from sigurg_handler to monitor_thread_main, c…
Copilot Apr 5, 2026
38e9537
fix: replace logically-wrong contains() with two-phase snapshot appro…
Copilot Apr 5, 2026
438c29e
perf: pre-allocate expired Vec outside monitor loop to avoid per-iter…
Copilot Apr 5, 2026
c62a624
enhance stability
loongs-zhang Apr 5, 2026
6ac5bf1
enhance stability
loongs-zhang Apr 6, 2026
b364319
Merge branch 'master' into copilot/fix-coroutine-scheduler-bug
loongs-zhang Apr 6, 2026
61c06af
fix: revert change_state conditional reorder and sigurg_handler Sysca…
Copilot Apr 6, 2026
3b2f5d2
fix: restore sigurg_handler Syscall state check to match master, prev…
Copilot Apr 6, 2026
570c679
fix: revert impl_facade!/WriteSyscallFacade reorder to match master —…
Copilot Apr 6, 2026
c2a87c9
less syscall
loongs-zhang Apr 6, 2026
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
37 changes: 15 additions & 22 deletions core/src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,6 @@ impl Monitor {
set.remove(Signal::SIGURG);
set.thread_set_mask()
.expect("Failed to remove SIGURG signal mask!");
//不抢占处于Syscall状态的协程。
//MonitorListener的设计理念是不对Syscall状态的协程发送信号。
//但由于NOTIFY_NODE移除和monitor线程遍历之间存在竞态条件,
//SIGURG可能在协程刚进入Syscall状态时到达。
//如果此时抢占,协程会被放入syscall_map但无人唤醒(因为没有io_uring/epoll注册),
//导致死锁。
// Skip preemption for coroutines in Syscall state.
// MonitorListener's design is to NOT send signals to Syscall-state
// coroutines. However, a race between NOTIFY_NODE removal and the
// monitor's queue iteration can cause SIGURG to arrive just after
// the coroutine entered Syscall state. If preempted here, the
// coroutine lands in the syscall map with no io_uring/epoll/timer
// registration to wake it, causing a deadlock.
if let Some(co) = SchedulableCoroutine::current() {
if matches!(co.state(), CoroutineState::Syscall((), _, _)) {
return;
}
}
if let Some(suspender) = SchedulableSuspender::current() {
suspender.suspend();
}
Expand Down Expand Up @@ -177,14 +159,25 @@ impl Monitor {
let monitor = Self::get_instance();
Self::init_current(monitor);
let notify_queue = unsafe { &*monitor.notify_queue.get() };
//先收集超时节点快照,再逐个检查是否仍在队列中
//(在收集和检查之间,on_state_changed可能已将节点移除——协程进入了Syscall状态)
let mut expired = Vec::new();
while MonitorState::Running == monitor.state.get() || !notify_queue.is_empty() {
//只遍历,不删除,如果抢占调度失败,会在1ms后不断重试,相当于主动检测
for node in notify_queue {
if now() < node.timestamp {
continue;
}
expired.clear();
let current = now();
expired.extend(
notify_queue
.iter()
.filter(|n| current >= n.timestamp)
.copied(),
);
for node in &expired {
//实际上只对陷入重度计算的协程发送信号抢占
//对于陷入执行系统调用的协程不发送信号(如果发送信号,会打断系统调用,进而降低总体性能)
if !notify_queue.contains(node) {
continue;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot The code looks very strange. Wouldn't everything be included in notify_queue? Cause we are for node in notify_queue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 438c29e. Replaced the logically-wrong contains() inside for node in notify_queue with a two-phase approach: first collect expired nodes into a pre-allocated snapshot Vec, then re-check notify_queue.contains(node) against the live queue before pthread_kill. Between the collection and the re-check, on_state_changed may have removed the node (coroutine entered Syscall state), so contains() returns false and the signal is skipped.

cfg_if::cfg_if! {
if #[cfg(unix)] {
if pthread_kill(node.pthread, Signal::SIGURG).is_err() {
Expand Down
11 changes: 10 additions & 1 deletion core/src/syscall/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,15 @@ macro_rules! impl_facade {
$($arg: $arg_type),*
) -> $result {
let syscall = $crate::common::constants::SyscallName::$syscall;
$crate::info!("enter syscall {}", syscall);
//先转换状态再记录日志:co.syscall(Executing)会通过on_state_changed
//移除MonitorListener的NOTIFY_NODE,使monitor不再发送SIGURG。
//如果先调用info!()再转换状态,在QEMU等慢平台上info!()可能耗时>10ms,
//导致SIGURG在协程还处于Running状态时被发送,造成抢占活锁。
// Transition state BEFORE logging: co.syscall(Executing) triggers
// on_state_changed which removes MonitorListener's NOTIFY_NODE,
// preventing the monitor from sending SIGURG. If info!() is called
// first while still in Running state, it can take >10ms on slow
// platforms (QEMU), causing SIGURG to fire and preemption live-lock.
if let Some(co) = $crate::scheduler::SchedulableCoroutine::current() {
let new_state = $crate::common::constants::SyscallState::Executing;
if co.syscall((), syscall, new_state).is_err() {
Expand All @@ -107,6 +115,7 @@ macro_rules! impl_facade {
);
}
}
$crate::info!("enter syscall {}", syscall);
let r = self.inner.$syscall(fn_ptr, $($arg, )*);
if let Some(co) = $crate::scheduler::SchedulableCoroutine::current() {
if co.running().is_err() {
Expand Down
2 changes: 1 addition & 1 deletion core/src/syscall/unix/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ impl<I: WriteSyscall> WriteSyscall for WriteSyscallFacade<I> {
return RawWriteSyscall::default().write(fn_ptr, fd, buf, len);
}
let syscall = crate::common::constants::SyscallName::write;
crate::info!("enter syscall {}", syscall);
if let Some(co) = crate::scheduler::SchedulableCoroutine::current() {
let new_state = crate::common::constants::SyscallState::Executing;
if co.syscall((), syscall, new_state).is_err() {
Expand All @@ -51,6 +50,7 @@ impl<I: WriteSyscall> WriteSyscall for WriteSyscallFacade<I> {
);
}
}
crate::info!("enter syscall {}", syscall);
let r = self.inner.write(fn_ptr, fd, buf, len);
if let Some(co) = crate::scheduler::SchedulableCoroutine::current() {
if co.running().is_err() {
Expand Down
11 changes: 10 additions & 1 deletion core/src/syscall/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,15 @@ macro_rules! impl_facade {
$($arg: $arg_type),*
) -> $result {
let syscall = $crate::common::constants::SyscallName::$syscall;
$crate::info!("enter syscall {}", syscall);
//先转换状态再记录日志:co.syscall(Executing)会通过on_state_changed
//移除MonitorListener的NOTIFY_NODE,使monitor不再发送SIGURG。
//如果先调用info!()再转换状态,在QEMU等慢平台上info!()可能耗时>10ms,
//导致SIGURG在协程还处于Running状态时被发送,造成抢占活锁。
// Transition state BEFORE logging: co.syscall(Executing) triggers
// on_state_changed which removes MonitorListener's NOTIFY_NODE,
// preventing the monitor from sending SIGURG. If info!() is called
// first while still in Running state, it can take >10ms on slow
// platforms (QEMU), causing SIGURG to fire and preemption live-lock.
if let Some(co) = $crate::scheduler::SchedulableCoroutine::current() {
let new_state = $crate::common::constants::SyscallState::Executing;
if co.syscall((), syscall, new_state).is_err() {
Expand All @@ -85,6 +93,7 @@ macro_rules! impl_facade {
);
}
}
$crate::info!("enter syscall {}", syscall);
let r = self.inner.$syscall(fn_ptr, $($arg, )*);
// Save errno immediately—logging and coroutine bookkeeping
// call Win32 APIs (e.g. CreateFileW) that clobber GetLastError().
Expand Down
Loading