Skip to content

fix GTK crash when switching to a driver that requires configuration - #417

Open
knipknap wants to merge 3 commits into
mainfrom
fix/415-ruida-driver-switch-crash
Open

knipknap wants to merge 3 commits into
mainfrom
fix/415-ruida-driver-switch-crash

Conversation

@knipknap

@knipknap knipknap commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Fixes #415

Problem

Selecting the Ruida driver in the machine settings crashed the app with

Gtk:ERROR:gtklistfactorywidget.c:114:gtk_list_factory_widget_setup_factory: assertion failed: (priv->object == object)

and because the driver choice is persisted immediately, every subsequent
start crashed too (crash loop). A related report in the comments showed
the driver being rebuilt on every keystroke while editing driver
settings, eventually wedging the connection until restart.

Root causes

  1. BottomPanel._update_wcs_ui() force-refreshed the WCS dropdown by
    re-emitting items_changed(i, 1, 1) on an unchanged Gtk.StringList
    on every machine.changed. Faking item replacements violates the list
    model contract; GTK crashes with the above assertion when this happens
    while it is setting up factory widgets (e.g. during window build at
    startup, which is exactly the persisted-bad-driver crash loop).
  2. LaserControlWidget._rebuild_head_model() replaced the head combo's
    entire model on every machine.changed, poking live widgets mid-flight
    in the same signal wave.
  3. Machine.set_driver()/set_driver_args() scheduled a rebuild directly
    and triggered the controller's changed listener, so every
    configuration change ran two concurrent rebuild coroutines with two
    connection attempts. Per-keystroke edits therefore tore down/re-created
    the driver and transport repeatedly until things wedged.

Fixes

  • bottom_panel: the WCS dropdown model is now spliced only when the
    displayed content (WCS set, labels, offsets) actually changed, via a
    real Gtk.StringList.splice() — legal list model usage, no more
    mid-flight poking.
  • laser_control_widget: the head model is only swapped when the head
    names changed; otherwise just the selected head's fields are re-synced.
  • controller/machine: driver rebuilds are now driven exclusively by
    the machine's changed signal (blinker) and debounced (0.3 s), so
    bursts of edits coalesce into a single rebuild and a single connect.
    Cancellation happens during the sleep and is re-checked afterwards, so
    a superseded rebuild can never touch driver state. A pending rebuild
    is also cancelled by disconnect() so it cannot silently reconnect an
    explicitly disconnected machine.
  • controller (follow-up from testing feedback): while a driver is
    connected, editing its setup arguments no longer tears it down and
    reconnects. The live instance is kept; the edited arguments are applied
    by the rebuild that runs once the connection is gone again (explicit
    disconnect or a drop). This removes the remaining
    disconnect/reconnect churn when typing into numeric driver fields.
  • disconnect(): resets the connection status immediately instead of
    leaving it stale until the scheduled rebuild runs.
  • general_preferences_page: populate the driver settings group from
    the driver class registry instead of the live driver instance, which
    is still the previous driver while a rebuild is pending.

Tests

  • New tests/ui_gtk/doceditor/test_bottom_panel_wcs_model.py: verifies no
    model mutation on unchanged state (the the app crash when switching to the ruida driver #415 crash) and a single real
    splice on offset/WCS-set changes.
  • New TestDriverRebuildDebounce in tests/machine/models/test_machine_controller.py:
    a burst of set_driver_args calls results in exactly one rebuild with
    the final values.
  • New TestKeepLiveDriverOnArgsChange: editing args of a connected
    driver keeps the live instance and connection intact, and the edited
    arguments are applied after a disconnect.

Full tests/machine (2266 passed) and uitest (853 passed) suites green,
pixi run lint/format clean.

Selecting a driver such as RuidaRPAAdapter without connection details
crashed the app with a GTK assertion (priv->object == object in
gtk_list_factory_widget_setup_factory) and left users in a crash loop
at startup, because the driver choice is persisted immediately.

Fixes:

- bottom_panel: stop re-emitting items_changed on the unchanged WCS
  list model. Faking item replacements violates the list model
  contract and crashes GTK while it is setting up factory widgets.
  The model is now spliced only when the displayed content (WCS set,
  labels, offsets) actually changed.

- laser_control_widget: only swap the laser head combo model when the
  head names changed instead of rebuilding it on every machine change.

- controller/machine: driver rebuilds are now exclusively driven by
  the machine's changed signal and debounced, so bursts of edits (e.g.
  per-keystroke changes in the driver settings) coalesce into a single
  rebuild and a single connection attempt. Previously every change
  scheduled two concurrent rebuilds (direct schedule plus the changed
  listener), which could wedge the transport until restart.

- general_preferences_page: populate driver settings from the driver
  class registry instead of the live driver instance, which is still
  the previous driver while a rebuild is pending.

Adds regression tests for the WCS model sync and the rebuild debounce.
A cancellation landing between the task manager's early-cancel check
and the creation of the internal asyncio task does not propagate to
the already-running coroutine, so a superseded debounce could still
reach rebuild_driver. Re-check the execution context's cancellation
flag after the sleep so a cancelled rebuild never touches driver
state.
Editing driver setup arguments (e.g. numeric fields) while the machine
is connected tore the driver down and reconnected on every debounced
change, and a fast editing burst could occasionally leave the driver
disconnected. When the driver class is unchanged and the driver has a
live connection, the running instance is now kept and the edited
arguments are applied by the rebuild that runs once the connection is
gone, e.g. after an explicit disconnect or a drop.

Also reset the connection status directly in disconnect() instead of
leaving it stale until the scheduled rebuild runs.
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.

the app crash when switching to the ruida driver

1 participant