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:
2026-03-07 18:11:46 +01:00
co-authored by Claude Opus 4.6
parent a3cd65c208
commit e93a9e8b70
9 changed files with 192 additions and 76 deletions
+40 -14
View File
@@ -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();