Fix model switching crash and improve error handling

**Model Reload Fixes:**
- Properly disconnect signals before reconnecting to prevent duplicate connections
- Wait for previous model loader thread to finish before starting new one
- Add garbage collection after unloading model to free memory
- Improve error handling in model reload callback

**Settings Dialog:**
- Remove duplicate success message (callback handles it)
- Only show message if no callback is defined

**Transcription Engine:**
- Explicitly delete model reference before setting to None
- Force garbage collection to ensure memory is freed

This prevents crashes when switching models, especially when done
multiple times in succession or while the app is under load.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-27 06:28:40 -08:00
parent 146a8c8beb
commit bd0e84c5e7
3 changed files with 45 additions and 22 deletions

View File

@@ -221,9 +221,16 @@ class TranscriptionEngine:
def unload_model(self): def unload_model(self):
"""Unload the model from memory.""" """Unload the model from memory."""
with self.model_lock: with self.model_lock:
if self.model is not None:
# Delete the model reference
del self.model
self.model = None self.model = None
self.is_loaded = False self.is_loaded = False
# Force garbage collection to free memory
import gc
gc.collect()
def __repr__(self) -> str: def __repr__(self) -> str:
return f"TranscriptionEngine(model={self.model_size}, device={self.device}, loaded={self.is_loaded})" return f"TranscriptionEngine(model={self.model_size}, device={self.device}, loaded={self.is_loaded})"

View File

@@ -571,6 +571,18 @@ class MainWindow(QMainWindow):
self.status_label.setText("⚙ Reloading model...") self.status_label.setText("⚙ Reloading model...")
self.start_button.setEnabled(False) self.start_button.setEnabled(False)
# Wait for any existing model loader thread to finish and disconnect
if self.model_loader_thread and self.model_loader_thread.isRunning():
print("Waiting for previous model loader to finish...")
self.model_loader_thread.wait()
# Disconnect any existing signals to prevent duplicate connections
if self.model_loader_thread:
try:
self.model_loader_thread.finished.disconnect()
except:
pass # Already disconnected or never connected
# Unload current model # Unload current model
if self.transcription_engine: if self.transcription_engine:
try: try:
@@ -600,10 +612,7 @@ class MainWindow(QMainWindow):
min_confidence=self.config.get('processing.min_confidence', 0.5) min_confidence=self.config.get('processing.min_confidence', 0.5)
) )
# Load model in background thread # Create new model loader thread
if self.model_loader_thread and self.model_loader_thread.isRunning():
self.model_loader_thread.wait()
self.model_loader_thread = ModelLoaderThread(self.transcription_engine) self.model_loader_thread = ModelLoaderThread(self.transcription_engine)
self.model_loader_thread.finished.connect(self._on_model_reloaded) self.model_loader_thread.finished.connect(self._on_model_reloaded)
self.model_loader_thread.start() self.model_loader_thread.start()
@@ -619,23 +628,28 @@ class MainWindow(QMainWindow):
def _on_model_reloaded(self, success: bool, message: str): def _on_model_reloaded(self, success: bool, message: str):
"""Handle model reloading completion.""" """Handle model reloading completion."""
if success: try:
# Update device label with actual device used if success:
if self.transcription_engine: # Update device label with actual device used
actual_device = self.transcription_engine.device if self.transcription_engine:
compute_type = self.transcription_engine.compute_type actual_device = self.transcription_engine.device
device_display = f"{actual_device.upper()} ({compute_type})" compute_type = self.transcription_engine.compute_type
self.device_label.setText(f"Device: {device_display}") device_display = f"{actual_device.upper()} ({compute_type})"
self.device_label.setText(f"Device: {device_display}")
host = self.config.get('web_server.host', '127.0.0.1') host = self.config.get('web_server.host', '127.0.0.1')
port = self.config.get('web_server.port', 8080) port = self.config.get('web_server.port', 8080)
self.status_label.setText(f"✓ Ready | Web: http://{host}:{port}") self.status_label.setText(f"✓ Ready | Web: http://{host}:{port}")
self.start_button.setEnabled(True) self.start_button.setEnabled(True)
QMessageBox.information(self, "Settings Saved", "Model reloaded successfully with new settings!") QMessageBox.information(self, "Settings Saved", "Model reloaded successfully with new settings!")
else: else:
self.status_label.setText("❌ Model loading failed") self.status_label.setText("❌ Model loading failed")
QMessageBox.critical(self, "Error", f"Failed to reload model:\n{message}") QMessageBox.critical(self, "Error", f"Failed to reload model:\n{message}")
self.start_button.setEnabled(False) self.start_button.setEnabled(False)
except Exception as e:
print(f"Error in _on_model_reloaded: {e}")
import traceback
traceback.print_exc()
def _start_server_sync(self): def _start_server_sync(self):
"""Start server sync client.""" """Start server sync client."""

View File

@@ -283,11 +283,13 @@ class SettingsDialog(QDialog):
self.config.set('server_sync.room', self.server_room_input.text()) self.config.set('server_sync.room', self.server_room_input.text())
self.config.set('server_sync.passphrase', self.server_passphrase_input.text()) self.config.set('server_sync.passphrase', self.server_passphrase_input.text())
# Call save callback # Call save callback (which will show the success message)
if self.on_save: if self.on_save:
self.on_save() self.on_save()
else:
# Only show message if no callback
QMessageBox.information(self, "Settings Saved", "Settings have been saved successfully!")
QMessageBox.information(self, "Settings Saved", "Settings have been saved successfully!")
self.accept() self.accept()
except ValueError as e: except ValueError as e: