فهرست منبع

Fix K-profile MQTT handler race condition crash (#462)

The MQTT callback thread checked self._pending_kprofile_response before
calling .set(), but the asyncio thread could clear it to None between
the check and the use (TOCTOU race after get_kprofiles() timeout).
Capture the event reference in a local variable to eliminate the race.
maziggy 3 ماه پیش
والد
کامیت
8a935a2bce
2فایلهای تغییر یافته به همراه7 افزوده شده و 3 حذف شده
  1. 1 0
      CHANGELOG.md
  2. 6 3
      backend/app/services/bambu_mqtt.py

+ 1 - 0
CHANGELOG.md

@@ -5,6 +5,7 @@ All notable changes to Bambuddy will be documented in this file.
 ## [0.2.1b2] - Unreleased
 
 ### Fixed
+- **K-Profile Response Race Condition Crash** ([#462](https://github.com/maziggy/bambuddy/issues/462)) — An unsolicited or late K-profile MQTT response could crash the MQTT handler with `AttributeError: 'NoneType' object has no attribute 'set'`. The MQTT callback thread checked `self._pending_kprofile_response` (not None) at line 2698, but between that check and the `.set()` call, the asyncio thread's `finally` block in `get_kprofiles()` could clear the attribute to `None` after a timeout — a classic TOCTOU race. Fixed by capturing the event reference in a local variable before the check.
 - **Queue Stuck on "Busy" for "Any Model" Jobs** ([#435](https://github.com/maziggy/bambuddy/issues/435)) — When a print was queued with "Any [Model]" (e.g., "Any P1S"), it was created with `printer_id=NULL` and `target_model="P1S"`. After the assigned printer finished, the queue widget queried only for items matching `printer_id=X`, missing the next pending model-based item (`printer_id IS NULL`). With no next item found, the "Clear Plate & Start Next" button never appeared, leaving the scheduler stuck reporting "Busy". The queue API now accepts an optional `target_model` parameter; when combined with `printer_id`, it uses OR logic to also return unassigned items whose `target_model` matches the printer's model. The frontend passes the printer's model through to this query. Additionally, the backend now resolves the printer's model server-side from the database when the frontend doesn't provide `target_model` (e.g., when the printer was added without selecting a model), ensuring the OR logic works regardless of whether the client knows the printer's model.
 
 ### New Features

+ 6 - 3
backend/app/services/bambu_mqtt.py

@@ -2695,13 +2695,16 @@ class BambuMQTTClient:
 
         # Signal that we received the response (only if we were waiting for one)
         # Use thread-safe method since MQTT callbacks run in a different thread
-        if self._pending_kprofile_response:
+        # Capture in local var to avoid TOCTOU race: asyncio thread can clear
+        # self._pending_kprofile_response between the check and the .set() call
+        event = self._pending_kprofile_response
+        if event:
             logger.info("[%s] Got %s K-profiles for nozzle=%s", self.serial_number, len(profiles), response_nozzle)
             if self._loop and self._loop.is_running():
-                self._loop.call_soon_threadsafe(self._pending_kprofile_response.set)
+                self._loop.call_soon_threadsafe(event.set)
             else:
                 # Fallback for when loop is not available
-                self._pending_kprofile_response.set()
+                event.set()
 
     async def get_kprofiles(
         self, nozzle_diameter: str = "0.4", timeout: float = 5.0, max_retries: int = 3