diff --git a/src/Linux.Bluetooth/Adapter.cs b/src/Linux.Bluetooth/Adapter.cs index f70acd1..60ed7f5 100644 --- a/src/Linux.Bluetooth/Adapter.cs +++ b/src/Linux.Bluetooth/Adapter.cs @@ -36,12 +36,7 @@ public class Adapter : IAdapter1, IDisposable private IObjectManager ObjectManager => _objectManager ?? throw new InvalidOperationException("Adapter object manager has not been initialized."); - ~Adapter() - { - Dispose(); - } - - internal static async Task CreateAsync(IAdapter1 proxy) + public static async Task CreateAsync(IAdapter1 proxy) { var adapter = new Adapter { @@ -76,8 +71,6 @@ public void Dispose() _connTrackedDevices.Clear(); } - - GC.SuppressFinalize(this); } public event DeviceChangeEventHandlerAsync DeviceFound @@ -267,24 +260,40 @@ public Task WatchPropertiesAsync(Action handler) private async void FireEventForExistingDevicesAsync() { - var devices = await this.GetDevicesAsync(); - foreach (var device in devices) + // async void: exceptions must not escape. + try { - _deviceFound?.Invoke(this, new DeviceFoundEventArgs(device, isStateChange: false)); + var devices = await this.GetDevicesAsync(); + foreach (var device in devices) + { + _deviceFound?.Invoke(this, new DeviceFoundEventArgs(device, isStateChange: false)); + } + } + catch (Exception ex) + { + Console.Error.WriteLine($"Existing-devices replay threw: {ex.Message}"); } } private async void OnDeviceAddedAsync((ObjectPath objectPath, IDictionary> interfaces) args) { - if (BlueZManager.IsMatch(BluezConstants.DeviceInterface, args.objectPath, args.interfaces, this)) + // async void: exceptions must not escape. + try { - var device = Connection.System.CreateProxy(BluezConstants.DbusService, args.objectPath); + if (BlueZManager.IsMatch(BluezConstants.DeviceInterface, args.objectPath, args.interfaces, this)) + { + var device = Connection.System.CreateProxy(BluezConstants.DbusService, args.objectPath); - var dev = await Device.CreateAsync(device); - _deviceFound?.Invoke(this, new DeviceFoundEventArgs(dev)); + var dev = await Device.CreateAsync(device); + _deviceFound?.Invoke(this, new DeviceFoundEventArgs(dev)); - // Relay this device's connection state if anyone is listening to the adapter-level events. - TrackDeviceForConnection(args.objectPath); + // Relay this device's connection state if anyone is listening to the adapter-level events. + TrackDeviceForConnection(args.objectPath); + } + } + catch (Exception ex) + { + Console.Error.WriteLine($"InterfacesAdded handler threw: {ex.Message}"); } } @@ -412,23 +421,31 @@ private async void FireEventIfPropertyAlreadyTrueAsync(AdapterEventHandlerAsync private void OnPropertyChanges(PropertyChanges changes) { - foreach (var pair in changes.Changed) + // Runs on the DBus receive loop: consumer throws must not escape. + try { - switch (pair.Key) + foreach (var pair in changes.Changed) { - case "Powered": - if (true.Equals(pair.Value)) - { - _poweredOn?.Invoke(this, new BlueZEventArgs()); - } - else - { - PoweredOff?.Invoke(this, new BlueZEventArgs()); - } - - break; + switch (pair.Key) + { + case "Powered": + if (true.Equals(pair.Value)) + { + _poweredOn?.Invoke(this, new BlueZEventArgs()); + } + else + { + PoweredOff?.Invoke(this, new BlueZEventArgs()); + } + + break; + } } } + catch (Exception ex) + { + Console.Error.WriteLine($"Adapter property handler threw: {ex.Message}"); + } } } } diff --git a/src/Linux.Bluetooth/Agent.cs b/src/Linux.Bluetooth/Agent.cs index 0560779..de29f70 100644 --- a/src/Linux.Bluetooth/Agent.cs +++ b/src/Linux.Bluetooth/Agent.cs @@ -62,11 +62,6 @@ public class Agent : IAgent1, IDisposable private event AgentDisplayPasskeyEventHandlerAsync? _passkeyDisplayed; private event AgentOperationCancelledEventHandlerAsync? _operationCancelled; - ~Agent() - { - Dispose(); - } - private Agent(Connection connection, string capability = DefaultCapability, ObjectPath? objectPath = null) { _connection = connection; @@ -99,7 +94,6 @@ internal static async Task CreateAsync(Connection connection, string capa public void Dispose() { UnregisterObject(); - GC.SuppressFinalize(this); } /// diff --git a/src/Linux.Bluetooth/AgentManager.cs b/src/Linux.Bluetooth/AgentManager.cs index f29741e..f2841b6 100644 --- a/src/Linux.Bluetooth/AgentManager.cs +++ b/src/Linux.Bluetooth/AgentManager.cs @@ -14,11 +14,6 @@ public class AgentManager : IAgentManager1, IDisposable { private readonly IAgentManager1 _proxy; - ~AgentManager() - { - Dispose(); - } - private AgentManager(IAgentManager1 proxy) { _proxy = proxy; @@ -47,7 +42,6 @@ internal static Task CreateAsync(Connection connection) /// public void Dispose() { - GC.SuppressFinalize(this); } /// diff --git a/src/Linux.Bluetooth/BlueZManager.cs b/src/Linux.Bluetooth/BlueZManager.cs index 11ab1ba..73dcd6f 100644 --- a/src/Linux.Bluetooth/BlueZManager.cs +++ b/src/Linux.Bluetooth/BlueZManager.cs @@ -47,6 +47,14 @@ public static async Task> GetAdaptersAsync() return await Task.WhenAll(adapters.Select(Adapter.CreateAsync)); } + /// Get the adapter proxies without creating signal watchers. + /// Select a proxy, then call on it. + /// The adapter proxies. + public static Task> GetAdapterProxiesAsync() + { + return GetProxiesAsync(BluezConstants.AdapterInterface, rootObject: null); + } + // Normalize a 16, 32 or 128 bit UUID. public static string NormalizeUUID(string uuid) { diff --git a/src/Linux.Bluetooth/Device.cs b/src/Linux.Bluetooth/Device.cs index d0ac774..c13ab5a 100644 --- a/src/Linux.Bluetooth/Device.cs +++ b/src/Linux.Bluetooth/Device.cs @@ -27,11 +27,6 @@ public class Device : IDevice1, IDisposable private IDevice1 Proxy => _proxy ?? throw new InvalidOperationException("Device has not been initialized."); - ~Device() - { - Dispose(); - } - internal static async Task CreateAsync(IDevice1 proxy) { var device = new Device @@ -48,8 +43,6 @@ public void Dispose() { _propertyWatcher?.Dispose(); _propertyWatcher = null; - - GC.SuppressFinalize(this); } public event DeviceEventHandlerAsync Connected diff --git a/src/Linux.Bluetooth/GattCharacteristic.cs b/src/Linux.Bluetooth/GattCharacteristic.cs index c633e01..01fce21 100644 --- a/src/Linux.Bluetooth/GattCharacteristic.cs +++ b/src/Linux.Bluetooth/GattCharacteristic.cs @@ -131,16 +131,23 @@ private async void Subscribe() private void OnPropertyChanges(PropertyChanges changes) { - // Console.WriteLine("OnPropertyChanges called."); - foreach (var pair in changes.Changed) + // Runs on the DBus receive loop: consumer throws must not escape. + try { - switch (pair.Key) + foreach (var pair in changes.Changed) { - case "Value": - _onValue?.Invoke(this, new GattCharacteristicValueEventArgs((byte[])pair.Value)); - break; + switch (pair.Key) + { + case "Value": + _onValue?.Invoke(this, new GattCharacteristicValueEventArgs((byte[])pair.Value)); + break; + } } } + catch (Exception ex) + { + Console.Error.WriteLine($"Characteristic value handler threw: {ex.Message}"); + } } } } diff --git a/src/Linux.Bluetooth/GattServer/GattServer.cs b/src/Linux.Bluetooth/GattServer/GattServer.cs index 05354d5..6d74a18 100644 --- a/src/Linux.Bluetooth/GattServer/GattServer.cs +++ b/src/Linux.Bluetooth/GattServer/GattServer.cs @@ -28,11 +28,6 @@ public GattServer(Adapter adapter) _gattManager = Connection.CreateProxy(BluezConstants.DbusService, adapter.ObjectPath); } - ~GattServer() - { - Dispose(); - } - public void Dispose() { Task.Run(async () => @@ -41,9 +36,8 @@ public void Dispose() await UnregisterGattApplication(); }).Wait(); - Console.Error.WriteLine("Disposed Gatt server."); + Debug.WriteLine("Disposed Gatt server."); Connection.Dispose(); - GC.SuppressFinalize(this); } public async Task InitializeAsync() @@ -108,7 +102,15 @@ public async Task UnregisterGattApplication() if (_gattApplication is not null) { // unregister the application before unregister objects - await _gattManager.UnregisterApplicationAsync(_gattApplication.ObjectPath); + try + { + await _gattManager.UnregisterApplicationAsync(_gattApplication.ObjectPath); + } + catch (DBusException ex) when (ex.ErrorName == "org.bluez.Error.DoesNotExist") + { + // BlueZ already dropped it (e.g. bluetoothd restarted): that is the wanted end state. + } + foreach (GattService service in _gattApplication.Services) { @@ -149,7 +151,15 @@ public async Task UnregisterAdvertisement() { if (_advertisement is not null) { - await _advManager.UnregisterAdvertisementAsync(_advertisement.ObjectPath); + try + { + await _advManager.UnregisterAdvertisementAsync(_advertisement.ObjectPath); + } + catch (DBusException ex) when (ex.ErrorName == "org.bluez.Error.DoesNotExist") + { + // BlueZ already dropped it (e.g. bluetoothd restarted): that is the wanted end state. + } + Connection.UnregisterObject(_advertisement); Debug.WriteLine($"Advertisement {_advertisement.ObjectPath} unregistered"); _advertisement = null;