fix(voice): address PR review findings — 3 critical, 5 warning, 4 suggestion
Critical fixes: - Pause mechanism: workers now hold requests during pause instead of dropping them. Queue and worker pool share the same AtomicBool flag via VoiceQueue::paused_flag(). Submit() rejects while paused. - Seed type: sr-voice accepts u64 seeds over IPC (explicit u32 truncation for llama.cpp sampler, documented). Warning fixes: - HashMap → BTreeMap in cache.rs and worker.rs (D-010 determinism mandate). Added Ord derives to CacheKey, ContentType, TellCategory. - VoicePipe::generate() watchdog kills child after 120s timeout to prevent indefinite blocking on read_line. - VoiceCacheStore Drop impl calls save_all() on shutdown. - trait-modifiers.ron: fixed 3 wrong trait names (Impulsive→Compassionate, Methodical→Incurious, Stubborn→Ruthless) to match PersonalityTrait enum. Suggestion fixes: - Worker spawn: log error + reduce pool instead of panic on thread failure. - on_battery(): added macOS detection via pmset. - Epistemic markers: lowercased constants, removed redundant to_lowercase(). - cache.rs: documented non-atomic write tradeoff. - queue.rs: reprioritize() bypasses pause check (it runs during pause). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+40
-14
@@ -5,7 +5,8 @@
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crossbeam_channel::{Receiver, Sender, TrySendError};
|
||||
|
||||
@@ -83,7 +84,7 @@ impl Ord for VoiceRequest {
|
||||
pub struct VoiceQueue {
|
||||
sender: Sender<VoiceRequest>,
|
||||
receiver: Receiver<VoiceRequest>,
|
||||
paused: Arc<Mutex<bool>>,
|
||||
paused: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl VoiceQueue {
|
||||
@@ -92,12 +93,16 @@ impl VoiceQueue {
|
||||
Self {
|
||||
sender,
|
||||
receiver,
|
||||
paused: Arc::new(Mutex::new(false)),
|
||||
paused: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Submit a voice request. Returns `false` if the queue is full (backpressure).
|
||||
/// Submit a voice request. Returns `false` if the queue is full or paused.
|
||||
pub fn submit(&self, request: VoiceRequest) -> bool {
|
||||
if self.is_paused() {
|
||||
tracing::trace!("voice queue paused — dropping submission");
|
||||
return false;
|
||||
}
|
||||
match self.sender.try_send(request) {
|
||||
Ok(()) => true,
|
||||
Err(TrySendError::Full(_)) => {
|
||||
@@ -116,6 +121,15 @@ impl VoiceQueue {
|
||||
self.receiver.clone()
|
||||
}
|
||||
|
||||
/// Get the shared pause flag for worker threads.
|
||||
///
|
||||
/// Workers check this flag to avoid processing requests during zone
|
||||
/// transitions. The same `Arc<AtomicBool>` is shared between the queue
|
||||
/// and the worker pool.
|
||||
pub fn paused_flag(&self) -> Arc<AtomicBool> {
|
||||
Arc::clone(&self.paused)
|
||||
}
|
||||
|
||||
/// Current number of pending requests in the channel.
|
||||
pub fn pending_count(&self) -> usize {
|
||||
self.sender.len()
|
||||
@@ -123,21 +137,17 @@ impl VoiceQueue {
|
||||
|
||||
/// Pause the queue (zone transition start).
|
||||
pub fn pause(&self) {
|
||||
if let Ok(mut p) = self.paused.lock() {
|
||||
*p = true;
|
||||
}
|
||||
self.paused.store(true, AtomicOrdering::SeqCst);
|
||||
}
|
||||
|
||||
/// Resume the queue (zone transition complete).
|
||||
pub fn resume(&self) {
|
||||
if let Ok(mut p) = self.paused.lock() {
|
||||
*p = false;
|
||||
}
|
||||
self.paused.store(false, AtomicOrdering::SeqCst);
|
||||
}
|
||||
|
||||
/// Check if the queue is paused.
|
||||
pub fn is_paused(&self) -> bool {
|
||||
self.paused.lock().map(|p| *p).unwrap_or(false)
|
||||
self.paused.load(AtomicOrdering::SeqCst)
|
||||
}
|
||||
|
||||
/// Reprioritize all pending requests after a zone change.
|
||||
@@ -162,11 +172,16 @@ impl VoiceQueue {
|
||||
let count = pending.len();
|
||||
let mut resubmitted = 0;
|
||||
|
||||
// Re-tag and re-submit
|
||||
// Re-tag and re-submit (bypass pause check — reprioritize is called
|
||||
// while paused and needs to refill the channel).
|
||||
for mut req in pending {
|
||||
req.priority = classify(&req);
|
||||
if self.submit(req) {
|
||||
resubmitted += 1;
|
||||
match self.sender.try_send(req) {
|
||||
Ok(()) => resubmitted += 1,
|
||||
Err(TrySendError::Full(_)) => {
|
||||
tracing::trace!("voice queue full during reprioritize — dropping request");
|
||||
}
|
||||
Err(TrySendError::Disconnected(_)) => break,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +302,17 @@ mod tests {
|
||||
assert!(!queue.is_paused());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_rejected_when_paused() {
|
||||
let queue = VoiceQueue::new();
|
||||
queue.pause();
|
||||
assert!(!queue.submit(make_request(Priority::High, "should be rejected")));
|
||||
assert_eq!(queue.pending_count(), 0);
|
||||
queue.resume();
|
||||
assert!(queue.submit(make_request(Priority::High, "should be accepted")));
|
||||
assert_eq!(queue.pending_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reprioritize_reshuffles_on_zone_change() {
|
||||
let queue = VoiceQueue::new();
|
||||
|
||||
Reference in New Issue
Block a user