Sửa lỗi OSError: [Errno 24] Too many open files trong Python

intermediate🐍 Python2026-05-14| Python 2.7+ / Python 3.x trên Linux, macOS, Unix — máy chủ hoặc máy trạm có giới hạn file descriptor ở cấp độ hệ điều hành

Error Message

OSError: [Errno 24] Too many open files
#python#oserror#file-descriptor#giới-hạn-tài-nguyên#ulimit

TL;DR — Sửa Nhanh

Tiến trình của bạn đã mở quá nhiều file (hoặc socket) mà hệ điều hành cho phép và không bao giờ đóng chúng lại. Bạn đã chạm đến giới hạn file descriptor.

Cách sửa nhanh nhất trên production: tăng giới hạn cho user đang chạy:

ulimit -n 65536

Điều đó giúp bạn có thêm không gian. Sau đó hãy sửa nguyên nhân gốc rễ trước lần deploy tiếp theo — chỉ dùng ulimit thôi sẽ không cứu được bạn mãi mãi.

Lỗi Này Trông Như Thế Nào

Traceback (most recent call last):
  File "worker.py", line 47, in process
    f = open(path, 'r')
OSError: [Errno 24] Too many open files

Socket cũng báo lỗi tương tự:

OSError: [Errno 24] Too many open files
  File "server.py", line 112, in accept
    conn, addr = self.sock.accept()

Dù trường hợp nào thì nguyên nhân gốc rễ cũng giống nhau. File descriptor (FD) được dùng chung cho file, socket, pipe và một số cơ chế IPC. Bạn đơn giản là đã dùng hết.

Nguyên Nhân Gốc Rễ

Mỗi file hoặc socket đang mở tiêu thụ một file descriptor. Linux và macOS áp đặt giới hạn mềm theo từng tiến trình — thường là 1024 trên Linux. Kiểm tra giới hạn của bạn:

ulimit -n
# hoặc
cat /proc/sys/fs/file-max  # giới hạn cứng toàn hệ thống

Hai thủ phạm chiếm phần lớn các trường hợp:

  • Rò rỉ FD: code mở file hoặc socket nhưng không bao giờ đóng lại. Chúng tích lũy âm thầm cho đến khi bạn chạm giới hạn.
  • Cạn kiệt thực sự: một trình crawler, bộ xử lý log, hoặc connection pool thực sự cần nhiều FD hơn mức mặc định cho phép.

Kiểm tra xem tiến trình của bạn đang mở bao nhiêu FD ngay lúc này:

# thay PID bằng ID tiến trình của bạn
ls /proc/PID/fd | wc -l

# hoặc dùng lsof
lsof -p PID | wc -l

Con số tăng dần đến giới hạn mà không bao giờ giảm? Đó là rò rỉ.

Cách Sửa 1 — Ngăn Rò Rỉ (Cách Sửa Phổ Biến Nhất)

Dùng câu lệnh with. Context manager của Python sẽ gọi close() ngay cả khi có exception xảy ra giữa chừng — không cần dọn dẹp thủ công.

Pattern sai:

f = open('data.csv', 'r')
content = f.read()
# quên f.close() — nếu exception xảy ra ở trên, FD sẽ bị rò rỉ

Đã sửa:

with open('data.csv', 'r') as f:
    content = f.read()
# FD được giải phóng ở đây, dù có chuyện gì xảy ra

Socket cũng hoạt động tương tự:

import socket

with socket.create_connection(('example.com', 80)) as sock:
    sock.sendall(b'GET / HTTP/1.0\r\n\r\n')
    response = sock.recv(4096)
# socket tự động đóng

Cách Sửa 2 — Đóng Tường Minh Trong Code Chạy Lâu

Đôi khi bạn không thể dùng with — chẳng hạn với thuộc tính của object hoặc handle ở cấp class. Hãy dùng try/finally thay thế:

f = open('output.log', 'a')
try:
    f.write(line)
finally:
    f.close()

HTTP session cũng có bẫy tương tự. Hãy dùng chúng như context manager:

import requests

with requests.Session() as session:
    resp = session.get('https://api.example.com/data')
    print(resp.json())

Cách Sửa 3 — Tăng Giới Hạn OS

Các server có độ đồng thời cao và các bộ xử lý file hàng loạt có thể thực sự cần hàng nghìn FD. Mặc định 1024 đơn giản là quá thấp.

Tạm thời (chỉ cho phiên shell hiện tại):

ulimit -n 65536

Vĩnh viễn cho một user — chỉnh sửa /etc/security/limits.conf:

# /etc/security/limits.conf
www-data soft nofile 65536
www-data hard nofile 65536

Đăng xuất và đăng nhập lại để thay đổi có hiệu lực. Xác nhận bằng ulimit -n.

Cho một systemd service — thêm vào phần [Service]:

[Service]
LimitNOFILE=65536

Sau đó áp dụng: sudo systemctl daemon-reload && sudo systemctl restart yourservice

Cách Sửa 4 — Dùng Module resource Trong Python

Cần tăng giới hạn từ bên trong script — không có systemd, không có quyền truy cập shell? Module resource xử lý điều đó khi khởi động:

import resource

soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
print(f'Giới hạn hiện tại: soft={soft}, hard={hard}')

# Tăng giới hạn mềm lên đến giới hạn cứng
resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard))

Một lưu ý: nếu không có quyền root, bạn chỉ có thể tăng giới hạn mềm lên đến giới hạn cứng. Bất kỳ điều gì vượt quá đó đều cần dùng ulimit hoặc limits.conf.

Cách Sửa 5 — Giới Hạn Kích Thước Connection Pool (requests, aiohttp, databases)

Dưới tải nặng, các HTTP client và DB driver có thể mở kết nối nhanh hơn tốc độ chúng được trả về pool. Hãy cố định kích thước pool một cách tường minh:

import requests
from requests.adapters import HTTPAdapter

session = requests.Session()
adapter = HTTPAdapter(pool_connections=10, pool_maxsize=20)
session.mount('https://', adapter)
session.mount('http://', adapter)

Với asyncio, semaphore giúp kiểm soát số lượng file mở đồng thời:

import asyncio

async def process_files(paths):
    sem = asyncio.Semaphore(100)  # giới hạn tối đa 100 FD đồng thời

    async def open_with_limit(path):
        async with sem:
            # thao tác file hoặc mạng ở đây
            pass

    await asyncio.gather(*[open_with_limit(p) for p in paths])

Điều chỉnh con số 100 đó dựa trên ulimit của bạn. Quy tắc an toàn: giữ dưới 50% giới hạn mềm của bạn.

Xác Nhận Bản Sửa

Theo dõi số FD theo thời gian thực trong khi workload đang chạy:

# Số FD trực tiếp cho PID
watch -n1 'ls /proc/PID/fd | wc -l'

# Kiểm tra giới hạn mà tiến trình thực sự thấy
cat /proc/PID/limits | grep 'open files'

Con số ổn định nghĩa là rò rỉ đã được vá. Vẫn còn tăng? Dùng lsof để tìm thủ phạm:

lsof -p PID | sort -k9 | head -40

Các entry lặp đi lặp lại của cùng một đường dẫn file chính là chỗ rò rỉ — đó chính xác là nơi cần xem xét.

Danh Sách Kiểm Tra Nhanh

  • Chạy ulimit -n — mặc định có còn là 1024 không? Hãy tăng lên.
  • Kiểm tra ls /proc/PID/fd | wc -l — bạn đang gần giới hạn đến mức nào?
  • Tìm trong code các lệnh gọi open( không nằm trong block with.
  • Tìm các socket chưa đóng, các lệnh gọi requests.get() trực tiếp, hoặc DB cursor.
  • Dùng thread hoặc async? Đảm bảo số lượng file mở song song có giới hạn — không dùng gather() không giới hạn hoặc ThreadPoolExecutor với số worker không giới hạn.

Related Error Notes