Skip to content

Add Adapter.DeviceConnected / DeviceDisconnected events - #81

Open
Kinchul wants to merge 4 commits into
SuessLabs:developfrom
fiveco:vk/adapter-device-connected-events
Open

Add Adapter.DeviceConnected / DeviceDisconnected events#81
Kinchul wants to merge 4 commits into
SuessLabs:developfrom
fiveco:vk/adapter-device-connected-events

Conversation

@Kinchul

@Kinchul Kinchul commented Jul 15, 2026

Copy link
Copy Markdown

Details

Adapter exposes DeviceFound for device discovery, but there is no equivalent event for a device's connection state. To learn that a device connected, a caller must enumerate devices itself and wire up each Device's per-device Connected watch — and even then it can miss connections (see below).

This PR adds two adapter-level events as the connection-side twin of DeviceFound:

  • event DeviceChangeEventHandlerAsync DeviceConnected
  • event DeviceChangeEventHandlerAsync DeviceDisconnected

They fire for any device transitioning to/from connected, regardless of how its Device1 object came to exist.

De-duplication

Device.Connected can fire twice for one transition — once from the add-accessor replay (FireEventIfPropertyAlreadyTrueAsync) and once from the live PropertiesChanged. (There is an existing // TODO: Suppress duplicate event from OnPropertyChanges at that spot.) The adapter tracks per-device connected state and relays only on an actual edge, so DeviceConnected / DeviceDisconnected` fire exactly once per transition.

Design notes

  • Mirrors the existing DeviceFound event pattern (add-accessor bootstrap, DeviceFoundEventArgs carrying the Device).
  • Zero cost when unused: no per-device tracking proxy is created unless at least one of the two events has a subscriber.
  • Tracking state is guarded by a lock; the connected/disconnected edge uses Interlocked so concurrent PropertiesChanged callbacks can't double-fire.
  • netstandard2.0-compatible (no Dictionary.Remove(key, out value) overload).

Usage

adapter.DeviceConnected += async (sender, e) =>
{
    Console.WriteLine($"Connected: {e.Device.ObjectPath}");
};
adapter.DeviceDisconnected += async (sender, e) =>
{
    Console.WriteLine($"Disconnected: {e.Device.ObjectPath}");
};

@Kinchul
Kinchul marked this pull request as draft August 6, 2026 09:42
Kinchul added 2 commits August 6, 2026 17:44
TrackDeviceForConnection reserves a dictionary slot with a null sentinel before
awaiting Device.CreateAsync. Dispose iterated the values without a null check,
so disposing while a reservation was pending threw a NullReferenceException.
Reaching Dispose from the finalizer made that exception kill the process, and it
aborted the remaining teardown after the signal watchers had been disposed,
leaving the adapter unable to receive InterfacesAdded.
@Kinchul

Kinchul commented Aug 7, 2026

Copy link
Copy Markdown
Author

Pushed two follow-up fixes found while running this on hardware:

1. Exceptions escaping the new signal handlers. OnDeviceRemoved and the
connection-tracking replay run on the D-Bus receive loop. A subscriber throwing there
propagates into Tmds.DBus, which treats it as a fatal receive error, disconnects, and
discards every signal handler on the connection — method calls then keep working while all
events go silent. Both handlers now contain consumer exceptions.

2. Dispose threw when a tracking slot was still pending. TrackDeviceForConnection
reserves the dictionary slot with a null sentinel before awaiting Device.CreateAsync.
Dispose iterated the values without a null check, so disposing during that window threw
NullReferenceException and aborted the rest of the teardown after the watchers were already
disposed — leaving the adapter unable to receive InterfacesAdded.

@Kinchul
Kinchul marked this pull request as ready for review August 7, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants