Skip to content

I2C File Descriptor Leak Causing "Too Many Open Files" Error #1065

@stamateviorel

Description

@stamateviorel

Summary
AmpliPi is leaking I2C file descriptors, causing the application to crash with OSError: [Errno 24] Too many open files after running for several hours.

Environment

AmpliPi Version: 0.4.8
Hardware: Raspberry Pi
Python Version: 3.7
OS: Raspbian/Raspberry Pi OS

Problem Description
The AmpliPi service crashes repeatedly with the following error:
textNov 07 20:43:02 amplipi authbind[851]: OSError: [Errno 24] Too many open files
Nov 07 20:43:02 amplipi authbind[851]: ERROR:asyncio:socket.accept() out of system resource

Root Cause Analysis
Investigation reveals that the application is leaking file descriptors to /dev/i2c-1. The process accumulates over 1000 open file handles to the I2C device, eventually hitting the system limit of 1024.
Diagnostic Output:

bash$ lsof -p 851 | grep CHR | awk '{print $NF}' | sort | uniq -c | sort -rn
   1014 /dev/i2c-1
      2 /dev/gpiomem
      1 /dev/null
bash$ cat /proc/851/limits | grep "open files"
Max open files            1024                 1048576              files
bash$ ls -l /proc/851/fd | wc -l
1025

The process has 1025 open file descriptors with 1014 of them being handles to /dev/i2c-1.

Expected vs Actual Behavior
Expected: The I2C bus should be opened once and reused, OR file handles should be properly closed after each I2C operation using context managers.
Actual: Each I2C operation opens a new file handle to /dev/i2c-1 that is never closed, causing file descriptor exhaustion over time.

Likely Code Issue
The problem is likely in code that accesses I2C devices (preamps, audio chips, etc.). The code probably looks something like this:

:x: Bad (current):
pythonbus = smbus.SMBus(1)  # Opens /dev/i2c-1
bus.write_byte_data(addr, reg, value)

Never closes the file descriptor!

:white_check_mark: Good (should be):
python# Option 1: Use context manager
with smbus.SMBus(1) as bus:
    bus.write_byte_data(addr, reg, value)

Automatically closes

# Option 2: Open once, reuse everywhere
class AudioController:
    def __init__(self):
        self.bus = smbus.SMBus(1)  # Open once
    
    def set_volume(self, addr, value):
        self.bus.write_byte_data(addr, reg, value)  # Reuse
    
    def __del__(self):
        self.bus.close()  # Clean up
Workaround (Temporary Fix)
Increase the file descriptor limit to prevent immediate crashes:
bashsystemctl --user edit amplipi.service --full
Add under the [Service] section:
iniLimitNOFILE=8192
Then reload and restart:
bashsystemctl --user daemon-reload
systemctl --user restart amplipi.service

⚠️ Note: This is just a workaround that buys time. The underlying code leak still needs to be fixed.

Suggested Fix for Developers

Search the codebase for all smbus.SMBus() or similar I2C access calls
Choose one approach:

Use context managers (with statements) to ensure handles are closed
Open the I2C bus once at application startup and reuse the same handle
Explicitly call .close() on I2C handles when done

Consider using a singleton pattern or dependency injection to share a single I2C bus instance across the application

Additional Notes
This issue will affect any AmpliPi installation running continuously. The time to failure depends on how frequently I2C operations occur (volume changes, input switching, preamp adjustments, etc.).
Happy to provide additional diagnostic information or logs if needed!

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