fix: auto-unload model after idle timeout to reduce memory (#1051)
- Default model_unload_timeout from Never to Min5 - Fix Drop impl: use take() on watcher handle so clones from initiate_model_load() don't kill the watcher thread - Reset last_activity on model load to prevent immediate unload - Upgrade watcher logging from debug to info level - Remove duplicate "unloaded" event (unload_model already emits it) Co-authored-by: CJ Pais <cj@cjpais.com>
This commit is contained in:
parent
789a4393e8
commit
d1da935479
2 changed files with 40 additions and 21 deletions
|
|
@ -97,6 +97,7 @@ impl TranscriptionManager {
|
|||
let manager_cloned = manager.clone();
|
||||
let shutdown_signal = manager.shutdown_signal.clone();
|
||||
let handle = thread::spawn(move || {
|
||||
debug!("Idle watcher thread started");
|
||||
while !shutdown_signal.load(Ordering::Relaxed) {
|
||||
thread::sleep(Duration::from_secs(10)); // Check every 10 seconds
|
||||
|
||||
|
|
@ -114,28 +115,29 @@ impl TranscriptionManager {
|
|||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as u64;
|
||||
let idle_ms = now_ms.saturating_sub(last);
|
||||
let limit_ms = limit_seconds * 1000;
|
||||
|
||||
if now_ms.saturating_sub(last) > limit_seconds * 1000 {
|
||||
if idle_ms > limit_ms {
|
||||
// idle -> unload
|
||||
if manager_cloned.is_model_loaded() {
|
||||
let unload_start = std::time::Instant::now();
|
||||
debug!("Starting to unload model due to inactivity");
|
||||
|
||||
if let Ok(()) = manager_cloned.unload_model() {
|
||||
let _ = app_handle_cloned.emit(
|
||||
"model-state-changed",
|
||||
ModelStateEvent {
|
||||
event_type: "unloaded".to_string(),
|
||||
model_id: None,
|
||||
model_name: None,
|
||||
error: None,
|
||||
},
|
||||
);
|
||||
let unload_duration = unload_start.elapsed();
|
||||
debug!(
|
||||
"Model unloaded due to inactivity (took {}ms)",
|
||||
unload_duration.as_millis()
|
||||
);
|
||||
info!(
|
||||
"Model idle for {}s (limit: {}s), unloading",
|
||||
idle_ms / 1000,
|
||||
limit_seconds
|
||||
);
|
||||
match manager_cloned.unload_model() {
|
||||
Ok(()) => {
|
||||
let unload_duration = unload_start.elapsed();
|
||||
info!(
|
||||
"Model unloaded due to inactivity (took {}ms)",
|
||||
unload_duration.as_millis()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to unload idle model: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -399,6 +401,15 @@ impl TranscriptionManager {
|
|||
*current_model = Some(model_id.to_string());
|
||||
}
|
||||
|
||||
// Reset idle timer so the watcher doesn't immediately unload a just-loaded model
|
||||
self.last_activity.store(
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as u64,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
|
||||
// Emit loading completed event
|
||||
let _ = self.app_handle.emit(
|
||||
"model-state-changed",
|
||||
|
|
@ -771,7 +782,15 @@ pub fn get_available_accelerators() -> AvailableAccelerators {
|
|||
|
||||
impl Drop for TranscriptionManager {
|
||||
fn drop(&mut self) {
|
||||
debug!("Shutting down TranscriptionManager");
|
||||
// Skip shutdown unless this is the very last clone. TranscriptionManager
|
||||
// is cloned by initiate_model_load() and the watcher thread — those
|
||||
// clones dropping must not kill the watcher. The watcher thread holds
|
||||
// its own clone, so engine's strong_count is always >= 2 while the
|
||||
// watcher is alive. When it reaches 1, only this instance remains
|
||||
// and we can safely shut down.
|
||||
if Arc::strong_count(&self.engine) > 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Signal the watcher thread to shutdown
|
||||
self.shutdown_signal.store(true, Ordering::Relaxed);
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ impl Default for KeyboardImplementation {
|
|||
|
||||
impl Default for ModelUnloadTimeout {
|
||||
fn default() -> Self {
|
||||
ModelUnloadTimeout::Never
|
||||
ModelUnloadTimeout::Min5
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -735,7 +735,7 @@ pub fn get_default_settings() -> AppSettings {
|
|||
debug_mode: false,
|
||||
log_level: default_log_level(),
|
||||
custom_words: Vec::new(),
|
||||
model_unload_timeout: ModelUnloadTimeout::Never,
|
||||
model_unload_timeout: ModelUnloadTimeout::default(),
|
||||
word_correction_threshold: default_word_correction_threshold(),
|
||||
history_limit: default_history_limit(),
|
||||
recording_retention_period: default_recording_retention_period(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue