Browse Source

Root cause: Frontend was firing multiple concurrent PUT requests when toggling MQTT, causing SQLite "database is locked" errors after 6-second timeout.

Fixes:
1. frontend/src/pages/SettingsPage.tsx - Added isSavingRef to prevent concurrent saves while a mutation is in progress
2. backend/app/services/mqtt_relay.py - Added 3-second timeout for MQTT connect to prevent blocking on unreachable brokers
maziggy 4 months ago
parent
commit
5d6ba0f875

+ 9 - 3
backend/app/services/mqtt_relay.py

@@ -92,11 +92,17 @@ class MQTTRelayService:
                 self.client.tls_set(cert_reqs=ssl.CERT_NONE)
                 self.client.tls_insecure_set(True)  # Allow self-signed certs
 
-            # Connect (non-blocking with loop_start)
-            self.client.connect_async(broker, port, keepalive=60)
+            # Run connect_async in thread pool with timeout to avoid blocking
+            # on unreachable brokers (connect_async does synchronous socket creation)
+            try:
+                await asyncio.wait_for(asyncio.to_thread(self.client.connect_async, broker, port, 60), timeout=3.0)
+            except TimeoutError:
+                logger.warning(f"MQTT relay connection to {broker}:{port} timed out")
+                return False
+
             self.client.loop_start()
 
-            # Wait briefly for connection
+            # Wait briefly for connection callback
             await asyncio.sleep(1.0)
 
             if self.connected:

+ 15 - 0
frontend/src/pages/SettingsPage.tsx

@@ -300,6 +300,7 @@ export function SettingsPage() {
 
   // Ref for debounce timeout
   const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const isSavingRef = useRef(false);
   const isInitialLoadRef = useRef(true);
 
   // Sync local state when settings load
@@ -324,6 +325,10 @@ export function SettingsPage() {
     onError: (error: Error) => {
       showToast(`Failed to save: ${error.message}`, 'error');
     },
+    onSettled: () => {
+      // Reset saving flag when mutation completes (success or error)
+      isSavingRef.current = false;
+    },
   });
 
   // Debounced auto-save when localSettings change
@@ -369,6 +374,11 @@ export function SettingsPage() {
       return;
     }
 
+    // Don't queue more saves while one is in progress
+    if (isSavingRef.current) {
+      return;
+    }
+
     // Clear existing timeout
     if (saveTimeoutRef.current) {
       clearTimeout(saveTimeoutRef.current);
@@ -376,6 +386,11 @@ export function SettingsPage() {
 
     // Set new debounced save (500ms delay)
     saveTimeoutRef.current = setTimeout(() => {
+      // Skip if a save is already in progress
+      if (isSavingRef.current) {
+        return;
+      }
+      isSavingRef.current = true;
       // Only send the fields we manage on this page (exclude virtual_printer_* which are managed separately)
       const settingsToSave: AppSettingsUpdate = {
         auto_archive: localSettings.auto_archive,

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-BEswNysN.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-Yr6VeTyb.js"></script>
+    <script type="module" crossorigin src="/assets/index-BEswNysN.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-Dzh7xD3q.css">
   </head>
   <body>

+ 5 - 0
update_website_wiki.sh

@@ -15,4 +15,9 @@ git add .
 git commit -m "Updated Wiki"
 git push
 
+cd ../spoolbuddy-website
+git add .
+git commit -m "Updated website"
+git push
+
 cd ../bambuddy

Some files were not shown because too many files changed in this diff