Repository navigation
Conversation
added 5 commits
October 29, 2026 18:30
socket_handler() only runs when the bus socket is readable. A dbus_connection_send_with_reply_and_block() anywhere else reads all pending messages while waiting for its reply and leaves them in libdbus' queue; with the socket drained, nothing dispatches them until an unrelated message arrives. With systemd-logind this loses Pause/ResumeDevice signals: each ResumeDevice carries an FD, so socket_handler() handles one per main loop pass, and an input hotplug during a VT switch makes config/udev call TakeDevice in the same pass, swallowing the rest. The server then never re-enters the VT, or comes back with dead input devices. Queue a work proc from a dispatch status function, so queued messages are dispatched outside the blocking caller before the server blocks again. Check the status once after connecting, since messages queued before the function was set produce no status transition. Assisted-by: Claude:claude-opus-5 Signed-off-by: galternat <galternat.twins223@simplelogin.com>
libdbus closes its socket as soon as it notices the bus went away, before the Disconnected message is queued, while the descriptor is still registered with SetNotifyFd() until teardown(). The closed socket drops out of epoll, so socket_handler() never runs again and Disconnected waits for some unrelated libdbus call to dispatch it, if one ever comes; until then the server does not reconnect. Meanwhile the number can be reused: a client accepted on it is never added to epoll, since ospoll_add() only updates the existing entry, and the RemoveNotifyFd() in teardown() frees whatever is registered under that number as if it were a struct notify_fd. Register a duplicate owned by dbus-core instead and close it in teardown(). It keeps the number reserved until then, and the hangup keeps it readable, so socket_handler() runs again and dispatches Disconnected. Assisted-by: Claude:claude-opus-5 Signed-off-by: galternat <galternat.twins223@simplelogin.com>
message_filter() clears bus_info.connection before calling teardown(), so the reference taken by dbus_bus_get() is never dropped and every bus restart leaks the old connection. Leave it to teardown() to release it. The disconnected connection is still valid, and dbus_connection_dispatch() holds its own reference while running filters, so it is finalized only after message_filter() returns. Assisted-by: Claude:claude-opus-5 Signed-off-by: galternat <galternat.twins223@simplelogin.com>
message_filter() sees every message on the system bus connection, including method returns and errors, which carry no path. A reply that arrives after its dbus_connection_send_with_reply_and_block() timed out has no pending call left to consume it, so it reaches the filter and dbus_message_get_path() returns NULL into strcmp(). Skip messages without a path before comparing it with the session. Assisted-by: Claude:claude-opus-5 Signed-off-by: galternat <galternat.twins223@simplelogin.com>
systemd_logind_ack_pause() takes the minor number before the major one, but message_filter() passes major first, so acknowledging a "pause" request names the wrong device. logind rejects it as not taken and, since it completes a pending session switch only once every device is paused, the switch never happens. logind only asks for a graceful pause on seats without VTs; on VT seats the acknowledgement comes from systemd_logind_drop_master(), whose arguments match the declaration. Take major before minor, like the rest of the file, and adjust systemd_logind_drop_master() accordingly. Assisted-by: Claude:claude-opus-5 Signed-off-by: galternat <galternat.twins223@simplelogin.com>
galternat
force-pushed
the
fix/dbus-core-queued-dispatch
branch
from
November 3, 2026 16:48
82dc6e3 to
0e8828b
Compare
Author
|
Apparently, this was hit by other users during suspend/resume, see https://gitlab.freedesktop.org/xorg/xserver/-/issues/1915, this should fix that too. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
With systemd-logind, an input device hotplugged while switching back to the
server's VT can leave X in one of these states:
dead, and further VT switches hang, since the server still owns the VT;
disabled (only the freshly hotplugged one works);
The log has
systemd-logind: got pause for ...lines but some or all of thematching
got resume for ...lines are missing. I hit this in a VM where somedevices are added automatically by the VM agent during VT switches, but it
reproduces on real systems too, with much worse consequences since this
can leave the machine permanently stuck with no input working.
Cause
socket_handler()only runs when the system bus socket is readable, anddbus_connection_read_write_dispatch()either dispatches a queued message orreads, never both. Every
ResumeDevicecarries an FD, so the kernel returnsone per
recvmsg(), and the handler gets through one signal per main looppass. When config/udev handles the hotplug later in the same pass, its
TakeDevicecall (dbus_connection_send_with_reply_and_block()) reads theremaining signals into libdbus' queue while waiting for its reply. The socket
is then empty, and nothing dispatches them until some unrelated message
arrives.
Confirmed by bpftrace on an unpatched server, switching back to its VT:
After that the bus socket is never reported readable again.
Changes
A dispatch status function queues a work proc whenever libdbus' queue
becomes non-empty, including inside another caller's blocking call. Work
procs run at the top of every
WaitForSomething()iteration, before theserver can block again; that also holds after timer callbacks such as
reconnect_timer(), sincecheck_timers()returns 0 when it ran a timer.libdbus closes its socket as soon as it sees the bus go away, before
Disconnectedis queued. The closed socket drops out of epoll, soDisconnectedwas never dispatched and the server never reconnected. Thestale registration also makes a client accepted on the recycled number hang
(
ospoll_add()only updates the existing entry), and once teardown doesrun, which commit 1 makes possible,
RemoveNotifyFd()would free thatclient's data as a
struct notify_fd. Polling a duplicate owned bydbus-core keeps the number reserved until teardown and keeps the hangup
visible to
socket_handler().message_filter()clearedbus_info.connectionbefore callingteardown(), so the reference fromdbus_bus_get()was never dropped andevery bus restart leaked the old connection. This predates the series, but
until commit 2 the
Disconnectedpath hardly ever ran.A reply arriving after its blocking call timed out goes to the filter, and
dbus_message_get_path()returns NULL intostrcmp().message_filter()passed major/minor in the opposite order tosystemd_logind_ack_pause(), so the acknowledgement named the wrong device.logind only asks for a graceful pause on seats without VTs, where the
session switch then never completes.
Reproducer
With a logind-managed X on VT1 and a text console on VT2, run the following
from VT2 as root:
chvt 1; ./uinput-hold 10 1This creates new input devices right after switching to VT1 and should trigger
the bug. Make sure you have no unsaved work, as a reboot may be required after
this. I managed to recover a few times by running sysrq+K on VT1 and switching
to a live X server on another VT (switching to VT2 didn't work), but I wouldn't rely
on it.
Here's the required
uinput-hold.c:Compile via
gcc -std=c99 -Wall -o uinput-hold uinput-hold.c.Tests
The current master (483a172) X hangs quite reliably on my setup. With the
patch applied, it finishes the switch correctly even after trying it multiple times.
Clean shutdown (SIGTERM) still runs teardown and closes the duplicate. Commits
4 and 5 were checked by inspection, as they are trivial related bug fixes.