From eead50b4cd355f06ddc292f40f2a30afbe531a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Alp=20Y=C4=B1lmaz?= <96022931+mustafalpyilmaz@users.noreply.github.com> Date: Wed, 11 Feb 2026 01:46:22 +0300 Subject: [PATCH] Fix setrlimit crash when hard file descriptor limit < 65535 (#1430) ## Summary `exo` crashes on startup when the system's hard file descriptor limit is below 65535, which occurs in macOS LaunchDaemon environments, Docker containers, and other restricted setups. **Root cause:** `resource.setrlimit(resource.RLIMIT_NOFILE, (max(soft, 65535), hard))` raises `ValueError` when `hard < 65535` because the soft limit cannot exceed the hard limit. **Fix:** - **`main.py`**: Clamp the target soft limit to `min(max(soft, 65535), hard)` so it never exceeds the hard limit - **`utils_mlx.py`**: Query current limits instead of hardcoding `(2048, 4096)`, which both crashed on restricted systems and incorrectly lowered the hard limit when it was set higher ## Test plan - [x] `basedpyright` passes with 0 errors - [x] `ruff check` passes - [x] Verify startup works on system with hard limit < 65535 (tested in macOS LaunchDaemon with hard limit 10240) - [x] Verify startup still works on default macOS (hard limit typically unlimited) --- src/exo/main.py | 3 ++- src/exo/worker/engines/mlx/utils_mlx.py | 3 --- src/exo/worker/runner/runner.py | 4 ++++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/exo/main.py b/src/exo/main.py index c3f165b4..e1ef3a70 100644 --- a/src/exo/main.py +++ b/src/exo/main.py @@ -251,7 +251,8 @@ class Node: def main(): args = Args.parse() soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) - resource.setrlimit(resource.RLIMIT_NOFILE, (max(soft, 65535), hard)) + target = min(max(soft, 65535), hard) + resource.setrlimit(resource.RLIMIT_NOFILE, (target, hard)) mp.set_start_method("spawn") # TODO: Refactor the current verbosity system diff --git a/src/exo/worker/engines/mlx/utils_mlx.py b/src/exo/worker/engines/mlx/utils_mlx.py index a1fa097d..8117c6cc 100644 --- a/src/exo/worker/engines/mlx/utils_mlx.py +++ b/src/exo/worker/engines/mlx/utils_mlx.py @@ -1,6 +1,5 @@ import json import os -import resource import sys import time from pathlib import Path @@ -63,8 +62,6 @@ from exo.worker.engines.mlx.auto_parallel import ( from exo.worker.runner.bootstrap import logger Group = mx.distributed.Group -# Needed for 8 bit model -resource.setrlimit(resource.RLIMIT_NOFILE, (2048, 4096)) # TODO: Test this diff --git a/src/exo/worker/runner/runner.py b/src/exo/worker/runner/runner.py index 2764f024..0b3cdb16 100644 --- a/src/exo/worker/runner/runner.py +++ b/src/exo/worker/runner/runner.py @@ -1,5 +1,6 @@ import base64 import json +import resource import time from collections.abc import Generator from functools import cache @@ -112,6 +113,9 @@ def main( event_sender: MpSender[Event], task_receiver: MpReceiver[Task], ): + soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) + resource.setrlimit(resource.RLIMIT_NOFILE, (min(max(soft, 2048), hard), hard)) + instance, runner_id, shard_metadata = ( bound_instance.instance, bound_instance.bound_runner_id,