midi-harbor/specs/011-midi-service-crash-warning/research.md
2026-09-28 13:59:10 -05:00

50 lines
3.1 KiB
Markdown

# Research: MIDI Service Crash Warning
The investigation behind this spec, under its number in the project-wide research log.
---
## R-101: Other applications after the MIDI service dies
**Status**: **CLOSED** (2026-09-27). Built as T236 to T238.
A cue sent from a presentation program on one Mac, over a network port, did nothing on a second
Mac. The second machine's unified log, collected with `sudo log collect` because the user running
it was not an administrator, gave the order, timed from the device dropping:
| Time | What happened |
|---|---|
| 0.000 s | A generic USB MIDI adapter behind a USB 2.0 hub stalled and dropped off the bus |
| 0.394 s | It enumerated again |
| 0.746 s | `MIDIServer` crashed with a segmentation fault in `GetObjectTree`, inside Apple's code |
| 0.749 s | A video switcher's control software logged `ObjectTreeCache: refresh status -308` |
| 0.765 s | launchd started a new `MIDIServer` |
| about 4.5 s | The daemon noticed, replaced itself (T192) and had its network port connected again 0.3 s later |
The presentation program and the switcher's software had been running for hours and stayed
attached to the dead service until relaunched, as every CoreMIDI client does (R-079). Since the restart
the network port had received one message over the network: the cue most likely arrived and came
out of the automatic port with nothing listening. The daemon's only record was a warning in its
in-memory history, which nobody read, and its log said nothing about traffic.
The daemon cannot reconnect another application, and should not quit one while it is in use. Listing the applications left attached was considered, by comparing their start times
with the new server's and checking which link CoreMIDI, and dropped at the owner's request: the
warning names none. What was built:
- A `midi_server_replaced` event and `StatusSummary.midi_server_replaced_at`, set by the process
that replaced the one that lost the service. `status` warns above its table for as long as that
process runs, the window shows a banner until it is dismissed, and the replacement posts one
desktop notification through `osascript`, which a bare executable run by launchd can use. The
sandboxed App Store helper posts none; its app shows the banner.
- `last_received` and `last_sent` in `TrafficCounters`, and a network port's
`automatic_port_counters`, shown in `status` and the diagnostic report. A network port's own
counts say what the network carried; its automatic port's say what it passed to the
applications here. CoreMIDI offers no way to see whether any application was listening to a
source, so that part stays unanswerable.
- A line in the log for each endpoint whose traffic moved, from a task reading the counters every
10 s, at most once a minute while traffic keeps moving. The data path is unchanged.
A grace period before forgetting absent applications' ports after a replacement was considered
and left out. The first device scan runs inside the daemon's start, before the binary knows it is
a replacement, and all it would keep is an unrouted port's entry, which returns when the
application does.