SniTrayImpl.close() gives the I/O thread two seconds to stop and, if it is still alive after that, skips dbus_connection_close, dbus_connection_unref and the arena close, logging a warning instead. That was added in 0.1.3 because the alternative is worse: freeing the connection while the thread is inside a dbus_* call segfaults inside libdbus, and leaking one connection at shutdown does not.
It is a backstop, not a solution. The remaining paths that can hold the thread past the join budget are:
Consumer callbacks. fire invokes every onEvent handler on the I/O thread, so a handler that blocks blocks the loop. The Tray interface documents that handlers run on the backend's dispatch thread and that consumers should hop themselves, but "documented" is not "bounded".
dbus_connection_flush against a socket nobody is draining. Bounded in practice by the kernel buffer and the daemon, not by anything libtray controls.
Two directions. Handing events to consumers from a small executor rather than the I/O thread removes the first and changes the threading contract, which is a public-API decision. Replacing the blocking flush with dbus_connection_get_dispatch_status plus a non-blocking write loop removes the second at the cost of more state in the loop.
Until one of them lands, a consumer whose handler blocks for seconds gets a leaked connection at shutdown rather than a crash, which is the intended trade.
SniTrayImpl.close()gives the I/O thread two seconds to stop and, if it is still alive after that, skipsdbus_connection_close,dbus_connection_unrefand the arena close, logging a warning instead. That was added in 0.1.3 because the alternative is worse: freeing the connection while the thread is inside adbus_*call segfaults inside libdbus, and leaking one connection at shutdown does not.It is a backstop, not a solution. The remaining paths that can hold the thread past the join budget are:
Consumer callbacks.
fireinvokes everyonEventhandler on the I/O thread, so a handler that blocks blocks the loop. TheTrayinterface documents that handlers run on the backend's dispatch thread and that consumers should hop themselves, but "documented" is not "bounded".dbus_connection_flushagainst a socket nobody is draining. Bounded in practice by the kernel buffer and the daemon, not by anything libtray controls.Two directions. Handing events to consumers from a small executor rather than the I/O thread removes the first and changes the threading contract, which is a public-API decision. Replacing the blocking flush with
dbus_connection_get_dispatch_statusplus a non-blocking write loop removes the second at the cost of more state in the loop.Until one of them lands, a consumer whose handler blocks for seconds gets a leaked connection at shutdown rather than a crash, which is the intended trade.