Ensure resilience over disconnect, restarts, hibernation and (re)boot #24

Open
opened 2026-11-13 13:18:05 +01:00 by lucas · 5 comments
Owner

Already observed issue: restarting the application via the systemd user service produces a crash dump.

Generally, the expected behavior is that the app eventually always settles into a working state, regardless of what happens with the system or in which order things happen.

app starts first -> wait for controller to be connected
controller connected, then app starts -> start working as expected
restart at any time -> reconcile connected controllers or wait for it, reconcile state
system boots or hibernates -> reconcile connected controllers or wait for it, reconcile state

Already observed issue: restarting the application via the systemd user service produces a crash dump. Generally, the expected behavior is that the app eventually always settles into a working state, regardless of what happens with the system or in which order things happen. app starts first -> wait for controller to be connected controller connected, then app starts -> start working as expected restart at any time -> reconcile connected controllers or wait for it, reconcile state system boots or hibernates -> reconcile connected controllers or wait for it, reconcile state
Collaborator

Triage

The issue bundles one observed crash and one missing behaviour. They sit in different subsystems, so I'll separate them.

There are four independent lifecycles involved, and the code treats them very differently:

lifecycle handled?
PipeWire/WirePlumber connection yes — on_core_disconnected in src/main.cpp:48 resets AudioService and retries wp_core_connect every 500 ms
MIDI device (ALSA sequencer client) no — opened once, never re-checked
process start/stop partially, and only via systemd's restart loop
LED cache vs. physical device state pushed once at startup, never re-pushed

1. The MIDI side has no lifecycle at all

MidiManager::open() is called exactly once (src/main.cpp:127). It enumerates RtMidiIn/RtMidiOut ports, picks the first name match, opens it, and that is the end of device management. There is no re-enumeration, no subscription to ALSA sequencer client announce/remove events, and MidiManager::isOpen() has no caller anywhere in the tree. Consequences:

  • "app starts first → wait for controller" is not implemented. When no matching port exists, main prints and return 1 (src/main.cpp:128-129). The waiting is done by Restart=on-failure + RestartSec=5 in packaging/nanokontroller2.service — i.e. the process flaps until the device shows up. That works by accident of the exit code rather than by design, and it also means every restart re-parses the config and re-builds all actions.
  • Disconnect while running is a silent dead end. RtMidi's ALSA input thread stops when the port subscription goes away; nothing in the app observes that. m_open stays true, MidiManager::sendControlChange keeps catching the resulting RtMidiError and returning false, and that false is discarded by LedManager::Set's callers (Action_Connection::Refresh_LED_State). The process stays alive, exit code never happens, so systemd never restarts it. The app is up and inert — which is the exact state the issue says must never persist.
  • Hibernate/resume and reboot land in this same hole. PipeWire generally survives a suspend cycle, so the WirePlumber path is not the one that breaks; a USB re-enumeration on resume is indistinguishable from an unplug/replug, and the only recovery path is the one that does not exist.

2. The teardown path is the mirror image of the reconnect path, and it is missing

on_core_disconnected is careful: it calls AudioService::Reset(), which disconnects every per-node signal handler bound to this before dropping the cache (src/AudioService.cpp:78-89). Normal shutdown does none of that. main quits the loop, calls m_MidiManager.close(), and lets ~AppState run. There is no wp_core_disconnect anywhere in the tree and no AudioService::Reset() on the exit path.

Member destruction order in AppState is Actions → AudioService → LedManager → MidiManager → Core → Loop, so AudioService is destroyed while the core it registered object managers with is still alive, and the core's own teardown happens afterwards. WpCore's finalize calls wp_core_disconnect(), and its dispose runs wp_registry_clear() before chaining to GObject's dispose (wireplumber 0.5.8 lib/wp/core.c) — that is, registry teardown and proxy destruction run at a point where handlers are still connected and AudioService is already gone. Every g_signal_connect(..., this) in AudioService::Init and every per-node handler that Reset() would have removed is a callback into freed memory in that window. on_core_disconnected itself is registered with &State, which has the same problem one level up.

That is my primary hypothesis for the crash dump: shutdown runs the destruction in the opposite order from the one the reconnect path established as correct, and nothing severs the callbacks first. [INFERENCE] — I have not reproduced it; see the questions below.

Two further candidates I cannot rule out without the backtrace, both in MidiManager::handleRawMessage (src/MidiManager.cpp:140-178):

  • m_handler(ev) is called unconditionally, but the RtMidi callback is installed inside open() (src/MidiManager.cpp:70) while the handler is only assigned several statements later in main (src/main.cpp:132) — after LedManager::ResetState() has pushed ~30 CC messages at the device. Any inbound byte in that window is an empty std::function call → uncaught std::bad_function_call → abort() → core dump. A restart is exactly when that window is most likely to be hit, because the user may still be touching a control.
  • msg[1] and msg[2] are read without checking msg.size(). ignoreTypes(true, true, true) filters sysex, timing clock and active sensing, but not 1-byte start/continue/stop/reset (0xFA–0xFC, 0xFF) nor 2-byte program change / channel pressure. Those are out-of-bounds std::vector reads.

3. "Reconcile state" has an asymmetry worth naming

After any gap, the two directions are not equally recoverable:

  • LEDs (app → device): LedManager holds a write-through cache (state_) that is only seeded by the single ResetState() at startup. A device that disappears and comes back arrives with all LEDs dark while the cache still claims they are lit, and LedManager::Toggle reads that cache — so the first press after a reconnect does the opposite of what the LED shows. The actions do re-push LED state, but only in response to WirePlumber events (on_node_available, on_default_nodes_changed); a MIDI-only reconnect triggers none of those.
  • Faders/knobs (device → app): the nanoKONTROL2's physical positions cannot be read back. After any restart or resume, Action_Volume::m_LastVolume starts out unset (m_bHasVolume == false) while the hardware sits at some arbitrary position; the divergence is invisible until the user moves the control.

So "reconcile state" means two different mechanisms, and only one of them is a pure software problem.

4. Smaller findings in the same area

  • AudioService::Init guards the links object manager with if (!ObjectManager) (src/AudioService.cpp:22) — the node manager's pointer, not LinksObjectManager. Harmless today only because Reset() nulls both; it breaks the moment Init runs twice without a Reset in between.
  • on_core_disconnected adds a new 500 ms retry timeout (and a g_new allocation) per emission. If "disconnected" fires more than once before a reconnect succeeds, the timers stack and AudioService::Init runs repeatedly.
  • packaging/nanokontroller2.service has Restart=on-failure, so the inert-after-unplug state in §1 is explicitly not covered, and a missing config file produces a process that runs forever with zero actions (AppSettingsManager::GetAppSettings returns empty settings and main carries on).
  • Unrelated to resilience but visible here: g_timeout_add(1, consume_queue_timeout, …) polls the MIDI queue at 1 kHz, i.e. a wakeup every millisecond for the life of the process.

Questions

  1. Can you attach the backtrace (coredumpctl info for that dump, plus the journal lines around it)? It discriminates directly between the three candidates above: WirePlumber teardown is a SIGSEGV deep in glib/wp frames, the empty-handler case is a SIGABRT with std::bad_function_call in an RtMidi thread, the short-message case is a SIGSEGV inside MidiManager::handleRawMessage.
  2. Does the dump come from the process being stopped or from the newly started one? The PIDs in the journal around the restart settle this, and the two point at opposite ends of the lifecycle.
  3. Was the controller plugged in and were you touching a control at the moment of the restart? A crash that only happens with the device connected and active points at the MIDI thread; one that happens on every systemctl --user restart regardless points at teardown.
  • #27 / #1 (MPRIS media buttons) — adds a third external dependency whose availability fluctuates (D-Bus name, players appearing and vanishing). Whatever the connection lifecycle ends up looking like here will govern that subsystem too.
  • #18 / #22 — established that nodes are identified by PipeWire bound id and that actions re-query live state rather than caching ids. That is why the WirePlumber reconnect path survives id churn, and it is the contrast that makes the MIDI side's missing path stand out.
  • #6 (layer/profile system) — introduces state that would have to survive the same restarts this issue is about.
  • #26 — unrelated subject, just the other open infrastructure issue.
## Triage The issue bundles one observed crash and one missing behaviour. They sit in different subsystems, so I'll separate them. There are four independent lifecycles involved, and the code treats them very differently: | lifecycle | handled? | |---|---| | PipeWire/WirePlumber connection | yes — `on_core_disconnected` in `src/main.cpp:48` resets `AudioService` and retries `wp_core_connect` every 500 ms | | MIDI device (ALSA sequencer client) | no — opened once, never re-checked | | process start/stop | partially, and only via systemd's restart loop | | LED cache vs. physical device state | pushed once at startup, never re-pushed | ### 1. The MIDI side has no lifecycle at all `MidiManager::open()` is called exactly once (`src/main.cpp:127`). It enumerates `RtMidiIn`/`RtMidiOut` ports, picks the first name match, opens it, and that is the end of device management. There is no re-enumeration, no subscription to ALSA sequencer client announce/remove events, and `MidiManager::isOpen()` has no caller anywhere in the tree. Consequences: * **"app starts first → wait for controller" is not implemented.** When no matching port exists, `main` prints and `return 1` (`src/main.cpp:128-129`). The waiting is done by `Restart=on-failure` + `RestartSec=5` in `packaging/nanokontroller2.service` — i.e. the process flaps until the device shows up. That works by accident of the exit code rather than by design, and it also means every restart re-parses the config and re-builds all actions. * **Disconnect while running is a silent dead end.** RtMidi's ALSA input thread stops when the port subscription goes away; nothing in the app observes that. `m_open` stays `true`, `MidiManager::sendControlChange` keeps catching the resulting `RtMidiError` and returning `false`, and that `false` is discarded by `LedManager::Set`'s callers (`Action_Connection::Refresh_LED_State`). The process stays alive, exit code never happens, so systemd never restarts it. The app is up and inert — which is the exact state the issue says must never persist. * **Hibernate/resume and reboot land in this same hole.** PipeWire generally survives a suspend cycle, so the WirePlumber path is not the one that breaks; a USB re-enumeration on resume is indistinguishable from an unplug/replug, and the only recovery path is the one that does not exist. ### 2. The teardown path is the mirror image of the reconnect path, and it is missing `on_core_disconnected` is careful: it calls `AudioService::Reset()`, which disconnects every per-node signal handler bound to `this` before dropping the cache (`src/AudioService.cpp:78-89`). Normal shutdown does none of that. `main` quits the loop, calls `m_MidiManager.close()`, and lets `~AppState` run. There is no `wp_core_disconnect` anywhere in the tree and no `AudioService::Reset()` on the exit path. Member destruction order in `AppState` is `Actions → AudioService → LedManager → MidiManager → Core → Loop`, so `AudioService` is destroyed **while the core it registered object managers with is still alive**, and the core's own teardown happens afterwards. `WpCore`'s finalize calls `wp_core_disconnect()`, and its dispose runs `wp_registry_clear()` *before* chaining to GObject's dispose ([wireplumber 0.5.8 `lib/wp/core.c`](https://github.com/PipeWire/wireplumber/blob/0.5.8/lib/wp/core.c)) — that is, registry teardown and proxy destruction run at a point where handlers are still connected and `AudioService` is already gone. Every `g_signal_connect(..., this)` in `AudioService::Init` and every per-node handler that `Reset()` would have removed is a callback into freed memory in that window. `on_core_disconnected` itself is registered with `&State`, which has the same problem one level up. That is my primary hypothesis for the crash dump: shutdown runs the destruction in the opposite order from the one the reconnect path established as correct, and nothing severs the callbacks first. [INFERENCE] — I have not reproduced it; see the questions below. Two further candidates I cannot rule out without the backtrace, both in `MidiManager::handleRawMessage` (`src/MidiManager.cpp:140-178`): * `m_handler(ev)` is called unconditionally, but the RtMidi callback is installed inside `open()` (`src/MidiManager.cpp:70`) while the handler is only assigned several statements later in `main` (`src/main.cpp:132`) — after `LedManager::ResetState()` has pushed ~30 CC messages at the device. Any inbound byte in that window is an empty `std::function` call → uncaught `std::bad_function_call` → `abort()` → core dump. A restart is exactly when that window is most likely to be hit, because the user may still be touching a control. * `msg[1]` and `msg[2]` are read without checking `msg.size()`. `ignoreTypes(true, true, true)` filters sysex, timing clock and active sensing, but not 1-byte start/continue/stop/reset (0xFA–0xFC, 0xFF) nor 2-byte program change / channel pressure. Those are out-of-bounds `std::vector` reads. ### 3. "Reconcile state" has an asymmetry worth naming After any gap, the two directions are not equally recoverable: * **LEDs (app → device):** `LedManager` holds a write-through cache (`state_`) that is only seeded by the single `ResetState()` at startup. A device that disappears and comes back arrives with all LEDs dark while the cache still claims they are lit, and `LedManager::Toggle` reads that cache — so the first press after a reconnect does the opposite of what the LED shows. The actions do re-push LED state, but only in response to *WirePlumber* events (`on_node_available`, `on_default_nodes_changed`); a MIDI-only reconnect triggers none of those. * **Faders/knobs (device → app):** the nanoKONTROL2's physical positions cannot be read back. After any restart or resume, `Action_Volume::m_LastVolume` starts out unset (`m_bHasVolume == false`) while the hardware sits at some arbitrary position; the divergence is invisible until the user moves the control. So "reconcile state" means two different mechanisms, and only one of them is a pure software problem. ### 4. Smaller findings in the same area * `AudioService::Init` guards the *links* object manager with `if (!ObjectManager)` (`src/AudioService.cpp:22`) — the node manager's pointer, not `LinksObjectManager`. Harmless today only because `Reset()` nulls both; it breaks the moment `Init` runs twice without a `Reset` in between. * `on_core_disconnected` adds a new 500 ms retry timeout (and a `g_new` allocation) per emission. If "disconnected" fires more than once before a reconnect succeeds, the timers stack and `AudioService::Init` runs repeatedly. * `packaging/nanokontroller2.service` has `Restart=on-failure`, so the inert-after-unplug state in §1 is explicitly *not* covered, and a missing config file produces a process that runs forever with zero actions (`AppSettingsManager::GetAppSettings` returns empty settings and `main` carries on). * Unrelated to resilience but visible here: `g_timeout_add(1, consume_queue_timeout, …)` polls the MIDI queue at 1 kHz, i.e. a wakeup every millisecond for the life of the process. ### Questions 1. Can you attach the backtrace (`coredumpctl info` for that dump, plus the journal lines around it)? It discriminates directly between the three candidates above: WirePlumber teardown is a SIGSEGV deep in glib/wp frames, the empty-handler case is a SIGABRT with `std::bad_function_call` in an RtMidi thread, the short-message case is a SIGSEGV inside `MidiManager::handleRawMessage`. 2. Does the dump come from the process being *stopped* or from the newly started one? The PIDs in the journal around the restart settle this, and the two point at opposite ends of the lifecycle. 3. Was the controller plugged in and were you touching a control at the moment of the restart? A crash that only happens with the device connected and active points at the MIDI thread; one that happens on every `systemctl --user restart` regardless points at teardown. ### Related issues * #27 / #1 (MPRIS media buttons) — adds a third external dependency whose availability fluctuates (D-Bus name, players appearing and vanishing). Whatever the connection lifecycle ends up looking like here will govern that subsystem too. * #18 / #22 — established that nodes are identified by PipeWire bound id and that actions re-query live state rather than caching ids. That is why the WirePlumber reconnect path survives id churn, and it is the contrast that makes the MIDI side's missing path stand out. * #6 (layer/profile system) — introduces state that would have to survive the same restarts this issue is about. * #26 — unrelated subject, just the other open infrastructure issue.
Author
Owner

Regarding your questions, I don't have the dump or log at hand. But I remember the stack trace listing some form of "abort" and/or "kill" functions, and I'm fairly certain it was the stopped process.

It happened as part of a systemctl --user restart nanokontroller2 and the newly started process worked fine. I also only saw one "stopped" and "started" line each in systemctl --user status. I believe the controller was plugged in at the time.

Regarding your questions, I don't have the dump or log at hand. But I remember the stack trace listing some form of "abort" and/or "kill" functions, and I'm fairly certain it was the stopped process. It happened as part of a `systemctl --user restart nanokontroller2` and the newly started process worked fine. I also only saw one "stopped" and "started" line each in `systemctl --user status`. I believe the controller was plugged in at the time.
Collaborator

That narrows it usefully, even without the dump.

What "abort" plus "kill" tells us

That frame pair is the standard glibc abort chain — abort() → raise() → __pthread_kill_implementation. It means the process killed itself: it was not SIGSEGV, and it was not systemd escalating to SIGKILL. Combined with "it was the stopped process" and "the new one worked fine", the window is the shutdown path of the old process, between g_main_loop_quit and main returning.

What that eliminates

Both MidiManager candidates from my first comment are out:

  • The empty-handler window (std::bad_function_call) exists only between MidiManager::open() installing the RtMidi callback and main assigning setEventHandler. That is startup code. The stopped process had its handler installed since startup, and the new process started cleanly — so this is not it.
  • The unchecked msg[1]/msg[2] read is an out-of-bounds read in the RtMidi thread; it produces SIGSEGV or silent garbage, not an abort, and it would fire at arbitrary moments rather than at stop. (Still a latent defect, just not this one.)

I can also rule out a race with the MIDI thread during teardown: MidiManager::close() runs before ~AppState, and m_in.reset() there runs ~MidiInAlsa, which writes the trigger pipe and joins the input thread (rtmidi RtMidi.cpp). By the time any destructor in AppState runs, that thread is gone. RtMidi's own teardown is also a poor abort candidate: MidiInAlsa::~MidiInAlsa only calls closePort() and ALSA cleanup, and the paths that do report problems there go through error() with WARNING severity, which prints instead of throwing.

What is left

The WirePlumber teardown, and the abort signature actually fits it better than a segfault would. A use-after-free that lands inside an allocator — freeing an already-freed pointer, or unreffing a GObject whose memory has been reused — is normally caught by glibc's own heap consistency check, which prints free(): invalid pointer / double free detected and calls abort(). PipeWire's SPA assertions and an uncaught C++ exception reaching std::terminate end in the same three frames. All three are symptoms of the same structural fact.

The fact itself is in the member order of AppState (src/main.cpp:23-34). Destruction runs Actions → AudioService → LedManager → MidiManager → Core → Loop, so AudioService is destroyed first and the WpCore it registered everything with is destroyed last. At that last step:

  • wp_core_dispose calls wp_registry_clear() and only then chains to GObject's dispose, which is what destroys signal handlers (wireplumber 0.5.8 lib/wp/core.c:403-411). So the whole registry teardown — globals dropped, proxies destroyed, object-removed emitted — happens while every handler is still connected.
  • Those handlers all carry a raw this: the two object managers (src/AudioService.cpp:26-27, 36-37) and three per-node handlers attached in on_node_added (src/AudioService.cpp:456-458). The only code that severs them is AudioService::Reset() (src/AudioService.cpp:78-89), and the exit path never calls it. There is no wp_core_disconnect() anywhere in the tree either.

So the reconnect path is careful about exactly this hazard and the exit path ignores it. on_core_disconnected resets the service before anything else touches it; shutdown does the reverse.

One detail I could not settle from the published sources: wp_registry_install_object_manager lives in a private registry translation unit I couldn't fetch, so I can't say whether the registry holds a strong reference to an installed WpObjectManager or a weak one. It matters only for which way the corruption goes, not whether it exists — strong ref means the manager outlives ~AudioService and emits into freed memory; weak means the registry keeps an entry for a manager that ~AudioService already unreffed. [INFERENCE] on which of the two.

A secondary contributor in the same window: Actions are destroyed first, and ~IActionBase unconditionally dereferences m_audioService (src/Actions/IAction.cpp:4-8). That ordering happens to be correct today, but it is correct by declaration order alone — nothing expresses the dependency.

Two things that would still sharpen this

  1. If the dump is still around (coredumpctl list keeps them for a while by default), the first line of the abort message is decisive: free(): … / malloc(): … means the allocator caught a double or invalid free, terminate called after throwing … means an uncaught exception, and anything with spa_assert or g_assertion_message points at a library invariant instead. Those three sit on different code paths inside the same teardown.
  2. Does a plain systemctl --user stop nanokontroller2 abort the same way? If yes, the restart is irrelevant and this is purely the exit path. If it only happens on restart, something about the two processes overlapping is involved and my reasoning above is incomplete.
That narrows it usefully, even without the dump. ## What "abort" plus "kill" tells us That frame pair is the standard glibc abort chain — `abort()` → `raise()` → `__pthread_kill_implementation`. It means the process killed *itself*: it was not SIGSEGV, and it was not systemd escalating to SIGKILL. Combined with "it was the stopped process" and "the new one worked fine", the window is the shutdown path of the old process, between `g_main_loop_quit` and `main` returning. ## What that eliminates Both MidiManager candidates from my first comment are out: * **The empty-handler window** (`std::bad_function_call`) exists only between `MidiManager::open()` installing the RtMidi callback and `main` assigning `setEventHandler`. That is startup code. The stopped process had its handler installed since startup, and the new process started cleanly — so this is not it. * **The unchecked `msg[1]`/`msg[2]` read** is an out-of-bounds *read* in the RtMidi thread; it produces SIGSEGV or silent garbage, not an abort, and it would fire at arbitrary moments rather than at stop. (Still a latent defect, just not this one.) I can also rule out a race with the MIDI thread during teardown: `MidiManager::close()` runs before `~AppState`, and `m_in.reset()` there runs `~MidiInAlsa`, which writes the trigger pipe and joins the input thread ([rtmidi `RtMidi.cpp`](https://github.com/thestk/rtmidi/blob/master/RtMidi.cpp)). By the time any destructor in `AppState` runs, that thread is gone. RtMidi's own teardown is also a poor abort candidate: `MidiInAlsa::~MidiInAlsa` only calls `closePort()` and ALSA cleanup, and the paths that do report problems there go through `error()` with WARNING severity, which prints instead of throwing. ## What is left The WirePlumber teardown, and the abort signature actually fits it *better* than a segfault would. A use-after-free that lands inside an allocator — freeing an already-freed pointer, or unreffing a GObject whose memory has been reused — is normally caught by glibc's own heap consistency check, which prints `free(): invalid pointer` / `double free detected` and calls `abort()`. PipeWire's SPA assertions and an uncaught C++ exception reaching `std::terminate` end in the same three frames. All three are symptoms of the same structural fact. The fact itself is in the member order of `AppState` (`src/main.cpp:23-34`). Destruction runs `Actions → AudioService → LedManager → MidiManager → Core → Loop`, so **`AudioService` is destroyed first and the `WpCore` it registered everything with is destroyed last**. At that last step: * `wp_core_dispose` calls `wp_registry_clear()` and only *then* chains to GObject's dispose, which is what destroys signal handlers ([wireplumber 0.5.8 `lib/wp/core.c:403-411`](https://github.com/PipeWire/wireplumber/blob/0.5.8/lib/wp/core.c)). So the whole registry teardown — globals dropped, proxies destroyed, `object-removed` emitted — happens while every handler is still connected. * Those handlers all carry a raw `this`: the two object managers (`src/AudioService.cpp:26-27, 36-37`) and three per-node handlers attached in `on_node_added` (`src/AudioService.cpp:456-458`). The only code that severs them is `AudioService::Reset()` (`src/AudioService.cpp:78-89`), and the exit path never calls it. There is no `wp_core_disconnect()` anywhere in the tree either. So the reconnect path is careful about exactly this hazard and the exit path ignores it. `on_core_disconnected` resets the service *before* anything else touches it; shutdown does the reverse. One detail I could not settle from the published sources: `wp_registry_install_object_manager` lives in a private registry translation unit I couldn't fetch, so I can't say whether the registry holds a strong reference to an installed `WpObjectManager` or a weak one. It matters only for which way the corruption goes, not whether it exists — strong ref means the manager outlives `~AudioService` and emits into freed memory; weak means the registry keeps an entry for a manager that `~AudioService` already unreffed. [INFERENCE] on which of the two. A secondary contributor in the same window: `Actions` are destroyed first, and `~IActionBase` unconditionally dereferences `m_audioService` (`src/Actions/IAction.cpp:4-8`). That ordering happens to be correct today, but it is correct by declaration order alone — nothing expresses the dependency. ## Two things that would still sharpen this 1. If the dump is still around (`coredumpctl list` keeps them for a while by default), the first line of the abort message is decisive: `free(): …` / `malloc(): …` means the allocator caught a double or invalid free, `terminate called after throwing …` means an uncaught exception, and anything with `spa_assert` or `g_assertion_message` points at a library invariant instead. Those three sit on different code paths inside the same teardown. 2. Does a plain `systemctl --user stop nanokontroller2` abort the same way? If yes, the restart is irrelevant and this is purely the exit path. If it only happens on `restart`, something about the two processes overlapping is involved and my reasoning above is incomplete.
Author
Owner

Here is the dump:

           PID: 54410 (nanoKontroller2)
           TID: 54410 (nanoKontroller2)
           UID: 1000 (lucas)
           GID: 1000 (lucas)
        Signal: 6 (ABRT) si_code: SI_TKILL
     Timestamp: Sat 2026-11-14 18:18:26 CET (4s ago)
  Command Line: /usr/bin/nanoKontroller2 --config /home/lucas/.config/nanokontroller2/config.json
    Executable: /usr/bin/nanoKontroller2
 Control Group: /user.slice/user-1000.slice/user@1000.service/app.slice/nanokontroller2.service
          Unit: user@1000.service
     User Unit: nanokontroller2.service
         Slice: user-1000.slice
     Owner UID: 1000 (lucas)
       Boot ID: 97c8e9750b7349c4b15e56b59f8f678f
    Machine ID: 4abbf80bad05417f873d8ea8876ed6c8
      Hostname: olymp
       Storage: /var/lib/systemd/coredump/core.nanoKontroller2.1000.97c8e9750b7349c4b15e56b59f8f678f.54410.1794676706000000.zst (present)
  Size on Disk: 411.7K
       Message: Process 54410 (nanoKontroller2) of user 1000 dumped core.
                
                Stack trace of thread 54410:
                #0  0x00007fe841eaf0ab pthread_kill (libc.so.6 + 0xaf0ab)
                #1  0x00007fe841e44ad8 raise (libc.so.6 + 0x44ad8)
                #2  0x00007fe841e2579f abort (libc.so.6 + 0x2579f)
                #3  0x00007fe841e26a33 n/a (libc.so.6 + 0x26a33)
                #4  0x00007fe841eba55e n/a (libc.so.6 + 0xba55e)
                #5  0x00007fe841ebc300 n/a (libc.so.6 + 0xbc300)
                #6  0x00007fe841ebc3c4 n/a (libc.so.6 + 0xbc3c4)
                #7  0x000056257ea43a21 n/a (/usr/bin/nanoKontroller2 (deleted) + 0x4da21)
                #8  0x00007fe84273d0a7 n/a (libgobject-2.0.so.0 + 0x430a7)
                #9  0x00007fe84273d6c9 g_signal_emit_valist (libgobject-2.0.so.0 + 0x436c9)
                #10 0x00007fe84273d78c g_signal_emit (libgobject-2.0.so.0 + 0x4378c)
                #11 0x00007fe84278d992 n/a (libwireplumber-0.5.so.0 + 0x1f992)
                #12 0x00007fe841db1920 pw_proxy_destroy (libpipewire-0.3.so.0 + 0x81920)
                #13 0x00007fe841d5a565 pw_core_disconnect (libpipewire-0.3.so.0 + 0x2a565)
                #14 0x00007fe8427ad33c n/a (libwireplumber-0.5.so.0 + 0x3f33c)
                #15 0x00007fe84271d416 g_object_unref (libgobject-2.0.so.0 + 0x23416)
                #16 0x000056257ea4e304 n/a (/usr/bin/nanoKontroller2 (deleted) + 0x58304)
                
                Stack trace of thread 54439:
                #0  0x00007fe841f4ad0d syscall (libc.so.6 + 0x14ad0d)
                #1  0x00007fe84261abd5 g_cond_wait (libglib-2.0.so.0 + 0xa9bd5)
                #2  0x00007fe84259962d g_async_queue_pop_unlocked (libglib-2.0.so.0 + 0x2862d)
                #3  0x00007fe842623a69 n/a (libglib-2.0.so.0 + 0xb2a69)
                #4  0x00007fe842623573 n/a (libglib-2.0.so.0 + 0xb2573)
                #5  0x00007fe841eace5c n/a (libc.so.6 + 0xace5c)
                #6  0x00007fe841f4d92c n/a (libc.so.6 + 0x14d92c)
                
                Stack trace of thread 54436:
                #0  0x00007fe841eb5f82 n/a (libc.so.6 + 0xb5f82)
                #1  0x00007fe841f4dbe2 epoll_wait (libc.so.6 + 0x14dbe2)
                #2  0x00007fe841718529 n/a (libspa-support.so + 0x1d529)
                #3  0x00007fe8417018e9 n/a (libspa-support.so + 0x68e9)
                #4  0x00007fe841dbe0c1 n/a (libpipewire-0.3.so.0 + 0x8e0c1)
                #5  0x00007fe841eace5c n/a (libc.so.6 + 0xace5c)
                #6  0x00007fe841f4d92c n/a (libc.so.6 + 0x14d92c)
                
                Stack trace of thread 54440:
                #0  0x00007fe841eb5f82 n/a (libc.so.6 + 0xb5f82)
                #1  0x00007fe841f3e3c3 ppoll (libc.so.6 + 0x13e3c3)
                #2  0x00007fe8425e109e n/a (libglib-2.0.so.0 + 0x7009e)
                #3  0x00007fe8425e11a2 g_main_context_iteration (libglib-2.0.so.0 + 0x701a2)
                #4  0x00007fe8425e11f2 n/a (libglib-2.0.so.0 + 0x701f2)
                #5  0x00007fe842623573 n/a (libglib-2.0.so.0 + 0xb2573)
                #6  0x00007fe841eace5c n/a (libc.so.6 + 0xace5c)
                #7  0x00007fe841f4d92c n/a (libc.so.6 + 0x14d92c)
                ELF object binary architecture: AMD x86-64
Here is the dump: ``` PID: 54410 (nanoKontroller2) TID: 54410 (nanoKontroller2) UID: 1000 (lucas) GID: 1000 (lucas) Signal: 6 (ABRT) si_code: SI_TKILL Timestamp: Sat 2026-11-14 18:18:26 CET (4s ago) Command Line: /usr/bin/nanoKontroller2 --config /home/lucas/.config/nanokontroller2/config.json Executable: /usr/bin/nanoKontroller2 Control Group: /user.slice/user-1000.slice/user@1000.service/app.slice/nanokontroller2.service Unit: user@1000.service User Unit: nanokontroller2.service Slice: user-1000.slice Owner UID: 1000 (lucas) Boot ID: 97c8e9750b7349c4b15e56b59f8f678f Machine ID: 4abbf80bad05417f873d8ea8876ed6c8 Hostname: olymp Storage: /var/lib/systemd/coredump/core.nanoKontroller2.1000.97c8e9750b7349c4b15e56b59f8f678f.54410.1794676706000000.zst (present) Size on Disk: 411.7K Message: Process 54410 (nanoKontroller2) of user 1000 dumped core. Stack trace of thread 54410: #0 0x00007fe841eaf0ab pthread_kill (libc.so.6 + 0xaf0ab) #1 0x00007fe841e44ad8 raise (libc.so.6 + 0x44ad8) #2 0x00007fe841e2579f abort (libc.so.6 + 0x2579f) #3 0x00007fe841e26a33 n/a (libc.so.6 + 0x26a33) #4 0x00007fe841eba55e n/a (libc.so.6 + 0xba55e) #5 0x00007fe841ebc300 n/a (libc.so.6 + 0xbc300) #6 0x00007fe841ebc3c4 n/a (libc.so.6 + 0xbc3c4) #7 0x000056257ea43a21 n/a (/usr/bin/nanoKontroller2 (deleted) + 0x4da21) #8 0x00007fe84273d0a7 n/a (libgobject-2.0.so.0 + 0x430a7) #9 0x00007fe84273d6c9 g_signal_emit_valist (libgobject-2.0.so.0 + 0x436c9) #10 0x00007fe84273d78c g_signal_emit (libgobject-2.0.so.0 + 0x4378c) #11 0x00007fe84278d992 n/a (libwireplumber-0.5.so.0 + 0x1f992) #12 0x00007fe841db1920 pw_proxy_destroy (libpipewire-0.3.so.0 + 0x81920) #13 0x00007fe841d5a565 pw_core_disconnect (libpipewire-0.3.so.0 + 0x2a565) #14 0x00007fe8427ad33c n/a (libwireplumber-0.5.so.0 + 0x3f33c) #15 0x00007fe84271d416 g_object_unref (libgobject-2.0.so.0 + 0x23416) #16 0x000056257ea4e304 n/a (/usr/bin/nanoKontroller2 (deleted) + 0x58304) Stack trace of thread 54439: #0 0x00007fe841f4ad0d syscall (libc.so.6 + 0x14ad0d) #1 0x00007fe84261abd5 g_cond_wait (libglib-2.0.so.0 + 0xa9bd5) #2 0x00007fe84259962d g_async_queue_pop_unlocked (libglib-2.0.so.0 + 0x2862d) #3 0x00007fe842623a69 n/a (libglib-2.0.so.0 + 0xb2a69) #4 0x00007fe842623573 n/a (libglib-2.0.so.0 + 0xb2573) #5 0x00007fe841eace5c n/a (libc.so.6 + 0xace5c) #6 0x00007fe841f4d92c n/a (libc.so.6 + 0x14d92c) Stack trace of thread 54436: #0 0x00007fe841eb5f82 n/a (libc.so.6 + 0xb5f82) #1 0x00007fe841f4dbe2 epoll_wait (libc.so.6 + 0x14dbe2) #2 0x00007fe841718529 n/a (libspa-support.so + 0x1d529) #3 0x00007fe8417018e9 n/a (libspa-support.so + 0x68e9) #4 0x00007fe841dbe0c1 n/a (libpipewire-0.3.so.0 + 0x8e0c1) #5 0x00007fe841eace5c n/a (libc.so.6 + 0xace5c) #6 0x00007fe841f4d92c n/a (libc.so.6 + 0x14d92c) Stack trace of thread 54440: #0 0x00007fe841eb5f82 n/a (libc.so.6 + 0xb5f82) #1 0x00007fe841f3e3c3 ppoll (libc.so.6 + 0x13e3c3) #2 0x00007fe8425e109e n/a (libglib-2.0.so.0 + 0x7009e) #3 0x00007fe8425e11a2 g_main_context_iteration (libglib-2.0.so.0 + 0x701a2) #4 0x00007fe8425e11f2 n/a (libglib-2.0.so.0 + 0x701f2) #5 0x00007fe842623573 n/a (libglib-2.0.so.0 + 0xb2573) #6 0x00007fe841eace5c n/a (libc.so.6 + 0xace5c) #7 0x00007fe841f4d92c n/a (libc.so.6 + 0x14d92c) ELF object binary architecture: AMD x86-64 ```
Collaborator

omp-deck is holding this event: the model provider's usage was at 90% as of 2026-11-14T17:18:57.628Z, at or above the 90% ceiling omp-deck keeps for scheduled work. The quota window resets at 2026-11-16T08:00:00.410Z. The turn starts automatically once usage drops, and this comment is removed then.

omp-deck is holding this event: the model provider's usage was at 90% as of 2026-11-14T17:18:57.628Z, at or above the 90% ceiling omp-deck keeps for scheduled work. The quota window resets at 2026-11-16T08:00:00.410Z. The turn starts automatically once usage drops, and this comment is removed then.
Sign in to join this conversation.
No milestone
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
lucas/nanoKontroller2#24
No description provided.