dotnet / dotnet/iot

GpiodException from LibGpiodV2Driver escapes GpioDriver.TryCreate's catch filter, breaking the v1->v2->sysfs interrupt-driver fallback

Open
#2,602 1 comment 1 reaction 2 assignees Claimed by @krwq View on GitHub
untriaged
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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.