Skip to content

bugfix: resolve 40+ crashes, threading races, and Klipper protocol issues - #200

Closed
gmmcosta15 wants to merge 4 commits into
devfrom
bugfix/crash-history
Closed

gmmcosta15 wants to merge 4 commits into
devfrom
bugfix/crash-history

Conversation

@gmmcosta15

@gmmcosta15 gmmcosta15 commented Mar 19, 2026 •

Copy link
Copy Markdown
Collaborator

Select the type:

  • Feature
  • Bug fix
  • Code refactor
  • Documentation

This PR introduces the following changes:

Threading & concurrency (moonrakerComm.py):

  • Move class-level state vars (connected/connecting/_reconnect_count/callback_table) into init to prevent shared state across instances
  • Add _state_lock (RLock) and _request_lock guarding all reads/writes to those vars
  • Fix mutable default dict in send_request (params={} -> params=None)
  • Fix unbound super() in OneShotTokenError (init without class arg)
  • Guard _retry_timer with None before stopTimer/startTimer calls; stop existing timer in try_connection before creating a new one (prevents orphaned threads)
  • sendEvent -> postEvent for cross-thread event delivery
  • Wrap json.loads in try/except; use .get() on all external response dicts
  • Fix dest_dir guard (False -> None) in copy/move file; fix machine.update.client missing params dict; fix machine.update.rollback method typo (comma -> dot)
  • Add klippy startup/disconnected retry cap (30 attempts) to prevent infinite loop
  • Add join(timeout=self.timeout+1) on _wst to avoid indefinite block

RepeatedTimer: fix inverted stopEvent semantics (set=stop, clear=running)

Pi runtime crashes:

  • cancelPage: fix AttributeError on file_metadata dict access
  • connectionPage: fix stale slot name
  • inputshaperPage: fix uninitialized currentItem reference
  • mainWindow: fix _handle_notify (dict.popitem -> next(iter(items()))); fix global_back tab-bar gate; fix logger f-string
  • filamentTab: fix slot signature mismatch; fix stale _is_printing state; fix bare self.isVisible reference (missing parentheses)
  • sensorsPanel: removeWidget+deleteLater before clearing tracking dict on reconnect to prevent widget stacking
  • babystepPage: label sync with confirmed Klipper value, button reorder, code deduplication
  • probeHelperPage: only process is_active when key present in update dict (Klipper sends position-only reensaver: guard DPMS calls with hasattr(_dpms_available) check

Python 3.11 typing (20 files):

  • Opional/Union/List/Dict → native syntax; add from future import annotations
  • filament.brand setter: return type None; _spool_type initialized in init
  • files.py: remove duplicate ReceivedFileData import; use events.ReceivedFileData

controlTab/tunePage slider split:

  • controlTab.on_slider_change: handles fan_generics only (SET_FAN_SPEED)
  • tunePage: _on_speed_slider_change for print speed (M220) and on_slider_change for fan_generics eliminates collision when fan is named speed

Motivation

  • Fix 40+ bugs causing crashes, UI freezes, and incorrect state on Pi hardware, including thread-race conditions in the WebSocket layer, bad event dispatch between threads, and incorrect Klipper protocol handling
  • Modernise Python typing across 20 files (Optional/Union/List/Dict -> native 3.11 syntax) and clean up helper_methods.py (spurious self, shadow of bytes builtin, Mainsail-ported layer formulas)
  • Split fan/speed slider callbacks in controlTab and tunePage to eliminate gcode dispatch errors when a fan_generic is named "speed"

…sues

  Threading & concurrency (moonrakerComm.py):
  - Move class-level state vars (connected/connecting/_reconnect_count/callback_table)
    into __init__ to prevent shared state across instances
  - Add _state_lock (RLock) and _request_lock guarding all reads/writes to those vars
  - Fix mutable default dict in send_request (params={} → params=None)
  - Fix unbound super() in OneShotTokenError (__init__ without class arg)
  - Guard _retry_timer with None before stopTimer/startTimer calls; stop existing
    timer in try_connection before creating a new one (prevents orphaned threads)
  - sendEvent → postEvent for cross-thread event delivery
  - Wrap json.loads in try/except; use .get() on all external response dicts
  - Fix dest_dir guard (False → None) in copy/move file; fix machine.update.client
    missing params dict; fix machine.update.rollback method typo (comma → dot)
  - Add klippy startup/disconnected retry cap (30 attempts) to prevent infinite loop
  - Add join(timeout=self.timeout+1) on _wst to avoid indefinite block

  RepeatedTimer: fix inverted stopEvent semantics (set=stop, clear=running)

  Pi runtime crashes:
  - cancelPage: fix AttributeError on file_metadata dict access
  - connectionPage: fix stale slot name
  - inputshaperPage: fix uninitialized currentItem reference
  - mainWindow: fix _handle_notify (dict.popitem → next(iter(items())));
    fix global_back tab-bar gate; fix logger f-string
  - filamentTab: fix slot signature mismatch; fix stale _is_printing state;
    fix bare self.isVisible reference (missing parentheses)
  - jobStatusPage: use separate event_state var (never reassign lstate);
    capture file locals before clearing state; print_finish only on complete;
    layer fallback uses Mainsail ceil formula; layer_fallback flag decoupled
    from total_layer
  - sensorsPanel: removeWidget+deleteLater before clearing tracking dict on
    reconnect to prevent widget stacking
  - babystepPage: label sync with confirmed Klipper value, button reorder,
    code deduplication
  - probeHelperPage: only process is_active when key present in update dict
    (Klipper sends position-only partial updates after TESTZ)
  - screensaver: guard DPMS calls with hasattr(_dpms_available) check

  Python 3.11 typing (20 files):
  - Optional/Union/List/Dict → native syntax; add from __future__ import annotations
  - filament.brand setter: return type None; _spool_type initialized in __init__
  - files.py: remove duplicate ReceivedFileData import; use events.ReceivedFileData

  helper_methods:
  - convert_bytes_to_mb: remove spurious self param; rename bytes → size_bytes
  - calculate_current_layer: Mainsail formula (ceil((z-first)/layer+1)), clamped
  - calculate_max_layers: extracted from calculate_current_layer; max(1,...) guard
  - normalize, check_filepath_permission, check_dir_existence: pathlib + type hints
  - estimate_print_time: modernized variable names and return type annotation
  - Remove unused get_file_loc/digest_hash stubs; remove normalize import from
    controlTab and tunePage

  controlTab/tunePage slider split:
  - controlTab.on_slider_change: handles fan_generics only (SET_FAN_SPEED)
  - tunePage: _on_speed_slider_change for print speed (M220) and on_slider_change
    for fan_generics — eliminates collision when fan is named speed
@gmmcosta15 gmmcosta15 self-assigned this Mar 19, 2026
@gmmcosta15
gmmcosta15 requested a review from HugoCLSC March 19, 2026 16:16
@gmmcosta15
gmmcosta15 marked this pull request as ready for review March 19, 2026 16:17

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate this changes into a new PR

@gmmcosta15

gmmcosta15 commented Apr 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

@gmmcosta15
gmmcosta15 deleted the bugfix/crash-history branch July 9, 2026 09:12
gmmcosta15 added a commit that referenced this pull request Sep 24, 2026
…Timer (#211)

- [ ] Feature
- [x] Bug fix
- [ ] Code refactor
- [ ] Documentation

# Description

- Move `connected`, `connecting`, `_reconnect_count`, `callback_table`
from class-level to `__init__` to prevent shared state across instances.
- Add `_state_lock` (RLock) and `_request_lock` guarding all
reads/writes to those vars.
- Fix mutable default argument `params={}` → `params=None` in
`send_request`.
- Fix unbound `super()` in `OneShotTokenError`.
- Guard `_retry_timer` with `None` check before
`stopTimer`/`startTimer`; stop existing timer in `try_connection` before
creating a new one (prevents orphaned threads).
- Replace `sendEvent` → `postEvent` for safe cross-thread event
delivery.
- Wrap `json.loads` in `try/except`; use `.get()` on all external
response dicts.
- Add klippy startup/disconnected retry cap (30 attempts) to prevent
infinite reconnect loop.
- Add `join(timeout=self.timeout+1)` on `_wst` to avoid indefinite block
on shutdown.
- Fix `RepeatedTimer`: inverted `stopEvent` semantics (`set`=stop,
`clear`=running).

# Motivation

The WebSocket layer had multiple threading races and incorrect
cross-thread event dispatch causing UI freezes and crashes on Pi
hardware. `RepeatedTimer` had inverted stop-event logic causing timers
to never stop correctly. These are the root-cause fixes for the most
severe class of bugs in #200.

---------

Signed-off-by: Guilherme Costa <guilherme.costa@blockstec.com>
gmmcosta15 added a commit that referenced this pull request Sep 24, 2026
- [ ] Feature
- [x] Bug fix
- [ ] Code refactor
- [ ] Documentation

# Description

- `cancelPage`: fix `AttributeError` on `file_metadata` dict access.
- `connectionPage`: fix stale slot name.
- `inputshaperPage`: fix uninitialized `currentItem` reference.
- `mainWindow`: fix `_handle_notify` (`dict.popitem` →
`next(iter(items()))`); fix `global_back` tab-bar gate; fix logger
f-string.
- `filamentTab`: fix slot signature mismatch; fix stale `_is_printing`
state; fix bare `self.isVisible` missing parentheses.
- `sensorsPanel`: call `removeWidget` + `deleteLater` before clearing
tracking dict on reconnect to prevent widget stacking.
- `babystepPage`: label syncs with confirmed Klipper value; button
reorder; code deduplication.
- `probeHelperPage`: only process `is_active` when key is present in
update dict.
- `screensaver`: guard DPMS calls with `hasattr(_dpms_available)` check.
- `confirmPage`: deduplicate repeated layout logic.
- `networkWindow`, `utilitiesTab`, `notificationPage`, `basePopup`,
`troubleshootPage`: minor crash guards and cleanup.
- `printer.py`: fix missing key guards on Klipper state update dicts.

# Motivation

30+ crashes observed on Pi hardware during normal operation. Each fix is
isolated to its widget. Related to the root PR #200.

---------

Signed-off-by: Guilherme Costa <guilherme.costa@blockstec.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant