while auditing this line, i began to wonder: is there a practical "floor" we should be enforcing rather than allowing the process to attempt a start with values that guarantee a crash on non-Windows systems?
while manually incrementing limits during local functional tests, i realised that setting min_fd to values between 0 and 10 consistently triggers a failure. specifically, if the environment provides fewer than 5 descriptors, the node cannot even initialise its basic logging or database handles before the OS kernel denies further requests.
i observed that the functional test framework itself requires a slightly higher overhead to capture stdout, but the core issue remains: a limit that is technically "non-negative" can still be physically impossible for a bitcoin node to survive.
through manual isolation, i've identified two distinct failure thresholds:
- test framework's floor (11 fds): required for the python test runner to successfully pipe stdout and manage the node process.
- binary floor (≤ 4 fds): below this value, the
bitcoind binary fails to start entirely, as it cannot satisfy basic requirements for standard i/o and its primary log file/entropy source.
proof of crash
<details open>
<summary>the test i used.</summary>
--- a/test/functional/feature_init.py
+++ b/test/functional/feature_init.py
@@ -348,6 +349,15 @@ class InitTest(BitcoinTestFramework):
+ def init_rlimit_too_low(self):
+ """Test that bitcoind behaves predictably when the limit is too low to function."""
+ if self.RLIM_INFINITY is None:
+ return
+
+ self.log.info("Testing node startup with a very low fd limit")
+ # current behavior: crashes with OSError 24
+ self.restart_node_with_fd_limit(8)
+
def run_test(self):
self.init_rlimit_test()
self.init_rlimit_large_test()
+ self.init_rlimit_too_low()
</details>
<details>
<summary>my logs after the crash.</summary>
...
2026-04-16T18:49:47.300353Z TestFramework.node1 (DEBUG): RPC successfully started
2026-04-16T18:49:47.301798Z TestFramework (INFO): Testing node startup with RLIM_INFINITY fd limit
2026-04-16T18:49:47.302125Z TestFramework (DEBUG): Current RLIMIT_NOFILE limits (soft=2048, hard=1048576), trying to set soft limit to -1
2026-04-16T18:49:47.302377Z TestFramework (INFO): Skipping rlimit test: cannot set soft limit (hard=1048576)
2026-04-16T18:49:47.302592Z TestFramework (INFO): Testing node startup with fd limit above INT_MAX
2026-04-16T18:49:47.302776Z TestFramework (DEBUG): Current RLIMIT_NOFILE limits (soft=2048, hard=1048576), trying to set soft limit to 2147483648
2026-04-16T18:49:47.302951Z TestFramework (INFO): Skipping rlimit test: cannot set soft limit (hard=1048576)
2026-04-16T18:49:47.303120Z TestFramework (INFO): Testing node startup with very low fd limit
2026-04-16T18:49:47.303285Z TestFramework (DEBUG): Current RLIMIT_NOFILE limits (soft=2048, hard=1048576), trying to set soft limit to 8
2026-04-16T18:49:47.303530Z TestFramework.node1 (DEBUG): Stopping node
2026-04-16T18:49:47.708177Z TestFramework.node1 (DEBUG): Node stopped
2026-04-16T18:49:47.708435Z TestFramework (DEBUG): Restored previous RLIMIT_NOFILE limits (soft=2048, hard=1048576)
2026-04-16T18:49:47.708521Z TestFramework (ERROR): Unexpected exception:
Traceback (most recent call last):
File "/home/oldman/Documents/btc/btc-core/review-tests/bitcoin/test/functional/test_framework/test_framework.py", line 144, in main
self.run_test()
File "/home/oldman/Documents/btc/btc-core/review-tests/bitcoin/build/test/functional/feature_init.py", line 379, in run_test
self.init_rlimit_neg()
File "/home/oldman/Documents/btc/btc-core/review-tests/bitcoin/build/test/functional/feature_init.py", line 360, in init_rlimit_neg
self.restart_node_with_fd_limit(8)
File "/home/oldman/Documents/btc/btc-core/review-tests/bitcoin/build/test/functional/feature_init.py", line 337, in restart_node_with_fd_limit
self.restart_node(1)
File "/home/oldman/Documents/btc/btc-core/review-tests/bitcoin/test/functional/test_framework/test_framework.py", line 548, in restart_node
self.start_node(i, extra_args)
File "/home/oldman/Documents/btc/btc-core/review-tests/bitcoin/test/functional/test_framework/test_framework.py", line 504, in start_node
node.start(*args, **kwargs)
File "/home/oldman/Documents/btc/btc-core/review-tests/bitcoin/test/functional/test_framework/test_node.py", line 279, in start
stdout = tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/tempfile.py", line 721, in NamedTemporaryFile
file = _io.open(dir, mode, buffering=buffering,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/tempfile.py", line 718, in opener
fd, name = _mkstemp_inner(dir, prefix, suffix, flags, output_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/tempfile.py", line 395, in _mkstemp_inner
fd = _os.open(file, flags, 0o600)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
OSError: [Errno 24] Too many open files: '/tmp/test_runner_₿_🏃_20260416_214851/feature_init_0/node1/stdout/tmpaekl08vn'
2026-04-16T18:49:47.710279Z TestFramework (DEBUG): Closing down network thread
2026-04-16T18:49:47.760813Z TestFramework (INFO): Not stopping nodes as test failed. The dangling processes will be cleaned up later.
2026-04-16T18:49:47.761167Z TestFramework (WARNING): Not cleaning up dir /tmp/test_runner_₿_🏃_20260416_214851/feature_init_0
2026-04-16T18:49:47.761426Z TestFramework (ERROR): Test failed. Test logging available at /tmp/test_runner_₿_🏃_20260416_214851/feature_init_0/test_framework.log
...
</details>
suggestion
rather than allowing the process to hit a OSError: [Errno 24] Too many open files mid-boot, we could perhaps treat this as a validation gate. it might be more robust to notify the user that the provided limit is insufficient for basic operation, providing a "fail-fast" mechanism rather than an ungraceful crash.
given that the node fundamentally requires a small handful of descriptors just to "breathe" (logging, leveldb, entropy), do you think it is worth adjusting the c++ logic to cater for these very low-value edge cases, or is the current assertion sufficient for our purposes?