GpiodException from LibGpiodV2Driver escapes GpioDriver.TryCreate's catch filter, breaking the v1->v2->sysfs interrupt-driver fallback
- Dominant language
- C#
- Stars
- 2.4k
- Forks
- 630
- Avg merge
- 11d 3h
- Merged PRs (30d)
- 2
Description
## How I got here
I run a small self-hosted .NET app ([RaspiFanController](https://github.com/mu88/RaspiFanController)) on a Raspberry Pi via `docker compose`, using a chiseled .NET container image. It had been running untouched for months. After `docker compose down; git pull; docker compose up -d` (which recreated the container against a newer image), it crashed on startup with a `GpiodException`. Digging in, I found that a dependency bump from `System.Device.Gpio` 4.1.0 -> 4.2.0 (merged into my app back in April, but the container just hadn't been recreated since) seems to be the trigger - 4.1.0 never hit this code path at all.
## Summary (my best guess, please correct me)
`RaspberryPi3LinuxDriver.InitializeInterruptDriver()` tries `LibGpiodDriver` (v1), then `LibGpiodV2Driver` (v2), presumably falling back to `InterruptSysFsDriver` if both fail. In my case the fallback never kicked in for the v2 attempt, because its native-call wrapper turns any exception into a `GpiodException`, which `GpioDriver.TryCreate`'s filter doesn't catch:
```csharp
// GpioDriver.cs
catch (Exception x) when (x is PlatformNotSupportedException || x is DllNotFoundException)
```
```csharp
// LibGpiodProxyBase.cs - CallLibgpiod
catch (Exception e) when (e is not GpiodException)
{
throw new GpiodException("Exception while calling libgpiod", e);
}
```
So a `DllNotFoundException` (libgpiod.so.3 missing) or an errno failure (e.g. `/dev/gpiochip0` missing) from `LibGpiodV2Driver`'s constructor becomes a `GpiodException : IOException`, which isn't in `TryCreate`'s filter - so it escapes and crashes host startup instead of reaching `InterruptSysFsDriver`. Not sure if that's intentional (maybe `GpiodException` is meant to always surface?) or an oversight.
## Repro
Two crashes on a .NET 10 chiseled (Debian trixie) container, linux-arm64, `System.Device.Gpio` 4.2.0:
1. No libgpiod at all (chiseled images ship none):
```
GpiodException: Exception while calling libgpiod
---> DllNotFoundException: Unable to load shared library 'libgpiod.so.3' ...
```
2. After mounting libgpiod.so.3 in, but without `/dev/gpiochip0` mapped (only `/dev/gpiomem` was):
```
GpiodException: Could not open gpio chip at path '/dev/gpiochip0': Error: 'No such file or directory'
```
Both crash `IHost.StartAsync` from `new GpioController().OpenPin(pin, mode)`.
## Possible directions (not sure which, if any, is right)
- `GpioDriver.TryCreate`'s filter also catches `GpiodException`?
- `InitializeInterruptDriver()` (and the Pi 5 equivalent from #2490) catches `GpiodException` explicitly around the v2 attempt?
- `LibGpiodProxyBase.CallLibgpiod` doesn't wrap `DllNotFoundException`/library-load failures into `GpiodException` in the first place?
Would appreciate maintainer input on whether this is a real gap or expected behavior I'm misunderstanding.
## Possibly related
- #2489 / #2490 - same v1->v2 `TryCreate` pattern for Pi 5, might have the same gap.
- #2441 - the libgpiod v2 support request that presumably led to `InitializeInterruptDriver()` gaining the v2 attempt.
## Versions
- `System.Device.Gpio` 4.2.0 (not seen on 4.1.0, haven't tested versions in between)
- .NET 10.0.10, linux-arm64, chiseled/Debian trixie base image
- Raspberry Pi host has `libgpiod3` 2.2.1 (`libgpiod.so.3`) installed
---
*This issue was drafted with AI assistance (investigation and write-up), based on my own crash logs and debugging session.*
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.