Repository navigation
Refactor/ashell services crate - #988
MalpenZibo wants to merge 9 commits into
Conversation
11e3668 to
f838627
Compare
|
One blocker: the Bluetooth stream can die for good
I reproduced it on real hardware. Discovery was on, with a couple of unpaired devices nearby. Whenever one of them showed up, I removed it again with
So this one is new in this PR. The old code built the event stream once at startup, so it only hit that path once. I also reproduced it deterministically with a mock BlueZ, and there the same race inside What I'd suggest:
Smaller stuff
|
9e7a478 to
24bd8d8
Compare
|
I had a look, it is already coming up nicely. My question is how separately used do you want to have it, Another thing is that we never run any tests in the CI.
From my perspective it already looks fine. I ran multiple LLMs on it and they found --- LLM (my opinion yes theoretically, practically very unlikely under normal circumstances) One blocker: a signal flood during a read deadlocks the service for good zbus queues at most 64 messages per match rule, and when a queue is full its socket reader waits ( The mock emits N
The single PropertiesChanged rule is what makes this reachable. It has no The general pattern isn't new: flooding Fix I verified against the same scenarios: keep consuming - while let Some(event) = events.next().await {
+ // Something changed while the last read was in flight: read again
+ let mut stale = false;
+
+ loop {
+ if !stale {
+ match events.next().await {
+ Some(Event::Bluez(running)) => bluez_running = running,
+ Some(Event::Changed) => {}
+ None => return,
+ }
+ }
+ stale = false;
// One action fires a burst of signals (pairing: Paired, Connected,
// Battery1, Percentage): read once for all those already queued.
- let mut event = Some(event);
- while let Some(current) = event {
- if let Event::Bluez(running) = current {
+ while let Some(event) = events.next().now_or_never().flatten() {
+ if let Event::Bluez(running) = event {
bluez_running = running;
}
- event = events.next().now_or_never().flatten();
}
// Reading bluez while it isn't running would activate it
let data = if bluez_running {
- match Self::read_data(&conn).await {
+ // Keep consuming events while waiting for the reply: once a zbus
+ // signal queue is full, the connection stops reading replies too.
+ let mut read = pin!(Self::read_data(&conn));
+ let result = loop {
+ match select(read.as_mut(), events.next()).await {
+ Either::Left((result, _)) => break result,
+ Either::Right((Some(event), _)) => {
+ if let Event::Bluez(running) = event {
+ bluez_running = running;
+ }
+ stale = true;
+ }
+ Either::Right((None, _)) => return,
+ }
+ };
+ match result { let rule = MatchRule::builder()
.msg_type(Type::Signal)
+ .sender(BLUEZ)?
.interface("org.freedesktop.DBus.Properties")?With that patch: N=65 and N=200 both produce the snapshot and the Powered flip, and Smaller things worth fixing in this PR
--- /LLM |
|
About the separation: it makes sense in the long run to have a dedicated documentation that maybe could be linked in the website. For now, we do not have any real external consumers of these services, so it's not crucial. I think that we could open dedicated issues/prs about the documentation without slowing down this migration. I will check for the CI issue |
… crate Turn the repo into a Cargo workspace and add crates/ashell-services, a library for services that do not depend on iced or any other UI toolkit, so the same code can back other UIs. The crate imposes no service trait. Bluetooth is exposed as a cloneable handle with async command methods, a data() snapshot and an updates() stream that yields a fresh snapshot on every change. Each service sits behind a cargo feature of the same name, with no default features. src/services/bluetooth.rs wraps that API into the iced ReadOnlyService and Service traits and keeps the UI policy (optimistic toggle, 15s discovery, refresh after each command), so the settings module and the network service are unchanged. CI and make check now lint the whole workspace.
The soft-block check and the /dev/rfkill watch lived on the bluetooth service even though the NetworkManager and IWD backends also use them to derive airplane mode. Move them to an rfkill utility module behind their own feature, enabled by bluetooth, so network no longer depends on the bluetooth service.
Describe the workspace layout, the trait-free service API and the iced glue in src/services, the per-service cargo features and the rfkill helpers, and point the bluetooth D-Bus references at the new crate.
Both the iced glue and a guido consumer had to duplicate the command enum, the dispatch to the handle methods, the toggle and discovery policy and a refresh after every command. Move all of that into the crate so glue code only forwards commands and publishes snapshots. - BluetoothCommand and Bluetooth::execute live in the crate. Toggle reads the real adapter and rfkill state instead of trusting a UI copy, and StartDiscovery stops on its own after 15 seconds. - updates() yields the current state as soon as it is subscribed, which also closes the gap between the initial read and the subscription. - updates() is complete: it also watches Paired and Alias, and rebuilds the per-device watchers when devices are added or removed, so devices that appear later are tracked too. Commands no longer refresh by hand. - Property streams yield their current value first; skip it so each (re)subscription reads a single snapshot instead of one per property. - Devices are sorted by name, and the data types derive PartialEq and Default, so reactive UIs can diff snapshots without spurious updates.
Run the Nix build when only crates/ changes, update the documented lint commands to the workspace-wide ones, and fix the bluetooth entry in the project layout and the Service trait signature.
…ription Build BluetoothData from a single GetManagedObjects reply instead of reading each device's properties one call at a time: the snapshot is consistent, and a device can't vanish halfway through. Watch changes with one event stream, subscribed once: bluez's owner (owner_watch, which never activates bluez), InterfacesAdded/Removed, a single PropertiesChanged rule on /org/bluez filtered to the properties shown, and rfkill. Adding or removing a device doesn't rebuild a stream per device. Signals already queued are read as one burst, and unchanged snapshots aren't sent again. Also: - rfkill::set_bluetooth_soft_block() for airplane mode, so both network backends stop shelling out to /usr/sbin/rfkill on their own. - Drop redundant feature edges and the unused BluetoothData re-export.
Keep consuming events while a snapshot read is in flight and read again if anything arrived: once a zbus signal queue fills up, the connection stops reading replies, so a burst of more than 64 signals during GetManagedObjects deadlocked the service. The PropertiesChanged rule now only accepts signals sent by bluez. Bluez methods are called with no_autostart, so commands never activate bluez. Failing to watch /dev/rfkill only disables soft block notifications instead of the whole service, and devices with the same alias are ordered by path so snapshots don't differ by order alone.
Run the ashell-services unit tests in CI and make check, and bump the version of every workspace member on pre-release.
24bd8d8 to
39e2c44
Compare
No description provided.