chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:10:34 +08:00
commit a789495a98
1551 changed files with 718128 additions and 0 deletions
+282
View File
@@ -0,0 +1,282 @@
use std::sync::Arc;
/// A soft interrupt message queued for injection at the next safe point.
#[derive(Debug, Clone)]
pub struct SoftInterruptMessage {
pub content: String,
/// If true, can skip remaining tools when injected at point C.
pub urgent: bool,
pub source: SoftInterruptSource,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SoftInterruptSource {
User,
System,
BackgroundTask,
}
/// Thread-safe soft interrupt queue that can be accessed without holding the agent lock.
pub type SoftInterruptQueue = Arc<std::sync::Mutex<Vec<SoftInterruptMessage>>>;
/// Signal to move the currently executing tool to background.
/// Uses std::sync so it can be set without async from outside the agent lock.
pub type BackgroundToolSignal = Arc<std::sync::atomic::AtomicBool>;
/// Signal to gracefully stop generation.
pub type GracefulShutdownSignal = Arc<std::sync::atomic::AtomicBool>;
/// Async-aware interrupt signal that combines AtomicBool (sync read) with
/// tokio::Notify (async wake). Eliminates spin-loops during tool execution.
#[derive(Clone)]
pub struct InterruptSignal {
flag: Arc<std::sync::atomic::AtomicBool>,
/// Monotonic fire counter. Lets owners of a timed/deferred reset detect
/// that a *newer* fire landed in the meantime and skip the reset instead
/// of erasing a cancel the target has not observed yet (issue #428).
epoch: Arc<std::sync::atomic::AtomicU64>,
notify: Arc<tokio::sync::Notify>,
}
impl InterruptSignal {
pub fn new() -> Self {
Self {
flag: Arc::new(std::sync::atomic::AtomicBool::new(false)),
epoch: Arc::new(std::sync::atomic::AtomicU64::new(0)),
notify: Arc::new(tokio::sync::Notify::new()),
}
}
pub fn fire(&self) {
self.epoch.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
self.flag.store(true, std::sync::atomic::Ordering::SeqCst);
self.notify.notify_waiters();
}
pub fn is_set(&self) -> bool {
self.flag.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn reset(&self) {
self.flag.store(false, std::sync::atomic::Ordering::SeqCst);
}
/// Current fire epoch. Capture this right after a [`fire`](Self::fire) to
/// later reset only that specific fire via
/// [`reset_if_epoch`](Self::reset_if_epoch).
pub fn epoch(&self) -> u64 {
self.epoch.load(std::sync::atomic::Ordering::SeqCst)
}
/// Reset the signal only if no newer [`fire`](Self::fire) happened since
/// `epoch` was captured. Returns `true` when the reset was applied.
///
/// If a racing fire lands between the epoch check and the reset, the
/// fire is restored (flag re-set and waiters re-notified) so no cancel
/// is ever silently erased.
pub fn reset_if_epoch(&self, epoch: u64) -> bool {
if self.epoch.load(std::sync::atomic::Ordering::SeqCst) != epoch {
return false;
}
self.flag.store(false, std::sync::atomic::Ordering::SeqCst);
if self.epoch.load(std::sync::atomic::Ordering::SeqCst) != epoch {
// A newer fire raced with the reset; restore it.
self.flag.store(true, std::sync::atomic::Ordering::SeqCst);
self.notify.notify_waiters();
return false;
}
true
}
pub async fn notified(&self) {
let mut notified = std::pin::pin!(self.notify.notified());
// Explicitly register this waiter with the Notify before checking the
// flag. `notify_waiters()` (used by `fire()`) wakes only registered
// waiters; current tokio registers a `notified()` future at creation,
// but `enable()` makes the registration explicit rather than relying
// on that version-specific guarantee, since a lost wakeup here parks
// the cancel path (agent stream loop, tool-wait select) until an
// unrelated event arrives (issue #428).
notified.as_mut().enable();
if self.is_set() {
return;
}
notified.await;
}
pub fn as_atomic(&self) -> Arc<std::sync::atomic::AtomicBool> {
Arc::clone(&self.flag)
}
/// True when `other` is a clone of this signal (shares the same state).
/// Used by cancel fan-out to avoid double-firing the same signal and by
/// diagnostics that need to detect stale signal instances (issue #428).
pub fn same_instance(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.flag, &other.flag)
}
}
impl Default for InterruptSignal {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, thiserror::Error)]
#[error("{message}")]
pub struct StreamError {
pub message: String,
pub retry_after_secs: Option<u64>,
}
impl StreamError {
pub fn new(message: String, retry_after_secs: Option<u64>) -> Self {
Self {
message,
retry_after_secs,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
/// Documents the tokio semantics `InterruptSignal::notified()` relies on:
/// current tokio guarantees a `notified()` future receives wakeups from
/// `notify_waiters()` from the moment it is *created*, even before its
/// first poll. The explicit `enable()` in `notified()` makes that
/// registration explicit instead of relying on the version-specific
/// creation-time guarantee (hardening for issue #428).
#[tokio::test]
async fn notified_future_receives_notify_waiters_from_creation() {
let notify = tokio::sync::Notify::new();
// Created before the notification, not yet polled: must be woken.
let created_before = notify.notified();
notify.notify_waiters();
tokio::time::timeout(Duration::from_millis(100), created_before)
.await
.expect("a notified() future created before notify_waiters() must be woken");
// Created after the notification: must NOT be woken (notify_waiters
// stores no permit). This is why fire() also sets the atomic flag.
let created_after = notify.notified();
assert!(
tokio::time::timeout(Duration::from_millis(50), created_after)
.await
.is_err(),
"notify_waiters() must not store a permit for future waiters"
);
}
/// Probabilistic race hammer for issue #428: `fire()` must never be lost
/// regardless of where the waiter is between creating the `notified()`
/// future and its first poll. The agent stream loop recreates this future
/// per stream event, so under fast token streams the pre-fix race made
/// Esc/Ctrl+C cancels appear to be ignored.
#[test]
fn fire_never_loses_wakeup_while_notified_races() {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_time()
.build()
.expect("runtime");
rt.block_on(async {
for i in 0..2000 {
let signal = InterruptSignal::new();
let waiter = {
let signal = signal.clone();
tokio::spawn(async move { signal.notified().await })
};
// Fire concurrently: the waiter may be anywhere between
// future creation and first poll.
signal.fire();
tokio::time::timeout(Duration::from_secs(2), waiter)
.await
.unwrap_or_else(|_| {
panic!(
"lost wakeup on iteration {i}: notified() missed fire() (issue #428)"
)
})
.expect("waiter task must not panic");
}
});
}
/// A fire() that happened before notified() is observed immediately.
#[tokio::test]
async fn notified_returns_immediately_when_already_fired() {
let signal = InterruptSignal::new();
signal.fire();
tokio::time::timeout(Duration::from_millis(100), signal.notified())
.await
.expect("pre-fired signal must resolve notified() immediately");
}
/// reset() clears the flag so subsequent notified() calls wait again.
#[tokio::test]
async fn reset_clears_fired_state() {
let signal = InterruptSignal::new();
signal.fire();
assert!(signal.is_set());
signal.reset();
assert!(!signal.is_set());
let waited = tokio::time::timeout(Duration::from_millis(50), signal.notified()).await;
assert!(waited.is_err(), "reset signal must park notified() again");
}
/// reset_if_epoch() clears the flag only for the fire that captured the
/// epoch. A deferred reset (e.g. the server's 500ms timer for detached
/// turns) must not erase a newer cancel fired in the meantime, otherwise
/// rapid repeated Esc presses cancel each other out (issue #428).
#[test]
fn reset_if_epoch_skips_when_newer_fire_landed() {
let signal = InterruptSignal::new();
signal.fire();
let first_epoch = signal.epoch();
// A second cancel (repeated Esc) fires before the deferred reset runs.
signal.fire();
assert!(
!signal.reset_if_epoch(first_epoch),
"stale deferred reset must be skipped"
);
assert!(
signal.is_set(),
"newer cancel must survive the stale deferred reset"
);
// The reset scheduled for the latest fire still works.
let second_epoch = signal.epoch();
assert!(signal.reset_if_epoch(second_epoch));
assert!(!signal.is_set());
// And a reset for an already-consumed epoch stays a no-op.
assert!(!signal.reset_if_epoch(first_epoch));
}
/// A fire() racing the flag-clear inside reset_if_epoch() is restored
/// rather than silently erased.
#[test]
fn reset_if_epoch_never_erases_concurrent_fire() {
for _ in 0..2000 {
let signal = InterruptSignal::new();
signal.fire();
let epoch = signal.epoch();
let firer = {
let signal = signal.clone();
std::thread::spawn(move || signal.fire())
};
let _ = signal.reset_if_epoch(epoch);
firer.join().expect("firer thread");
assert!(
signal.is_set(),
"a concurrent fire() must never be erased by reset_if_epoch()"
);
}
}
}