Skip to content

RaspberryPi.module_init() leaks an SPI file descriptor on every call #428

Description

@qnology

Summary

RaspberryPi.module_init() in epdconfig.py calls self.SPI.open(0, 0) unconditionally. spidev's open() does not close an already-open handle, so every call after the first leaks one /dev/spidev0.0 file descriptor.

A long-running program that redraws the panel periodically will eventually exhaust its file descriptors. Because the leaked descriptors consume the process-wide NOFILE budget, the first visible symptom is usually unrelated network calls failing, which makes this quite hard to trace back.

This is reachable from the documented API

Switching between 4Gray and 1Gray requires calling init(mode) again — there is no other API for it. The bundled example does exactly that:

RaspberryPi_JetsonNano/python/examples/epd_3in7_test.py

23:    epd.init(0)
88:    epd.init(1)         # 1 Gary mode
103:   epd.init(0)
114:   epd3in7.epdconfig.module_exit()

Three init() calls, one module_exit() — so running the example as shipped leaks two descriptors. It exits immediately so nobody notices, but a daemon doing the same thing does not.

Measured

Raspberry Pi 3B, epd3in7, refreshing about once a minute (a full 4Gray repaint plus a 1Gray partial, so init() is called on each cycle):

open FDs:  1024   <- the default NOFILE soft limit
of which:  1012 x /dev/spidev0.*

Time from a cold start to hitting the ceiling: ~16.7 hours. After that, every outbound HTTPS request failed with:

Failed to establish a new connection: [Errno 22] Invalid argument

...which is what sent me looking at the network for two days. The display had simply frozen while the process stayed alive and healthy-looking.

Quick check for anyone else seeing this:

ls -l /proc/<pid>/fd | grep -c spidev     # healthy: 1

Suggested fix

The same guard SunriseX3.module_init() already uses in this file:

if self.Flag == 0:
    self.Flag = 1
    self.SPI.open(2, 0)
    ...

Applying that pattern to RaspberryPi, and resetting Flag in module_exit() so a genuine teardown still re-opens. PR to follow.

Possibly related

#87 and #32 report the mirror-image symptom — Bad file descriptor when init() is called after sleep(), i.e. the handle being closed rather than leaked. Both stem from the same thing: module_init()/module_exit() track no state about whether the SPI handle is open. Tracking it addresses both directions.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions