140 points by furkansahin 24 hours ago | 103 comments
xorvoid 22 hours ago
moyix 19 hours ago
StefanBatory 1 hour ago
malux85 16 hours ago
saagarjha 5 hours ago
ape4 17 hours ago
WARNING: select() can monitor only file descriptors numbers that are
less than FD_SETSIZE (1024)—an unreasonably low limit for many modern
applications—and this limitation will not change. All modern applica‐
tions should instead use poll(2) or epoll(7), which do not suffer this
limitation.
time4tea 8 hours ago
reisse 15 hours ago
In the end we came up with a hack to open 4k file descriptors into /dev/null on start, then open the real files and sockets necessary for our app, then close that /dev/null descriptors and initialize the library.
danadam 20 hours ago
Did it change? Last time I checked it was 1024 (though it was long time ago).
> and no bounds checking!
_FORTIFY_SOURCE is not set? When I try to pass 1024 to FD_SET and FD_CLR on my (very old) machine I immediately get:
*** buffer overflow detected ***: ./a.out terminated
Aborted
(ok, with -O1 and higher)xorvoid 18 hours ago
Yes, _FORTIFY_SOURCE is a fabulous idea. I was just a bit shocked it wasn’t checked without _FORTIFY_SOURCE. If you’re doing FD_SET/FD_CLR, you’re about to make an (expensive) syscall. Why do you care to elide a cheap not-taken branch that’ll save your bacon some day? The overhead is so incredibly negligible.
Anyways, seriously just use poll(). The select() syscall needs to go away for good.
reisse 15 hours ago
> POSIX allows an implementation to define an upper limit, advertised via the constant FD_SETSIZE, on the range of file descriptors that can be specified in a file descriptor set. The Linux kernel imposes no fixed limit, but the glibc implementation makes fd_set a fixed-size type, with FD_SETSIZE defined as 1024, and the FD_*() macros operating according to that limit.
The code I've had a chance to work with (it had its roots in the 90s-00s, therefore the select()) mostly used 2048 and 4096.
> Anyways, seriously just use poll().
Oh please don't. poll() should be in the same grave as select() really. Either use libev/libuv or go down the rabbit hole of what is the bleeding edge IO multiplexer for your platform (kqueue/epoll/IOCP/io_uring...).
cryptonector 10 hours ago
jeroenhd 21 hours ago
Nowadays Windows seems to have capped the max amount of file handles per process to 2^16 (or 8096 if you're using raw C rather than Windows APIs). However, as on Windows not everything is a file, the amount of open handles is limited "only by memory", so Windows programs can do a lot of things UNIX programs can't do anymore when the file handle limit has been reached.
jchw 21 hours ago
Still, on the other hand, opening a lot of file descriptors will necessarily incur a lot of resource usage, so really if there's a more efficient way to do it, we should find it. That's definitely the case with the old way of doing inotify for recursive file watching; I believe most or all uses of inotify that work this way can now use fanotify instead much more efficiently (and kqueue exists on other UNIX-likes.)
In general having the limit be low is probably useful for sussing out issues like this though it definitely can result in a worse experience for users for a while...
> Feels a bit like Windows programming back when GDI handles were a limited resource.
IIRC it was also amusing because the limit was global (right?) and so you could have a handle leak cause the entire UI to go haywire. This definitely lead to some very interesting bugs for me over the years.
0xbadcafebee 15 hours ago
Same reason disks have quotas and containers have cpu & memory limits: to keep one crappy program from doinking the whole system. In general it's seen as poor form to let your server crash just because somebody allowed infinite loops/resource use in their program.
A lot of people's desktops, servers, even networks, crashing is just a program that was allowed to take up too many resources. Limits/quotas help more than they hurt.
saagarjha 5 hours ago
kevincox 14 hours ago
The correct solution is basically 1. On startup every process should set the soft limit to the hard limit, 2. Don't use select ever 3. Before execing any processes set the limit back down (in case the thing you exec uses select)
This silly dance is explained in more detail here: https://0pointer.net/blog/file-descriptor-limits.html
bombcar 20 hours ago
There was. Even if a file handle is 128 bytes or so, on a system with only 10s or 100s of KB you wouldn't want it to get out of control. On multi-user especially, you don't want one process going nuts to open so many files that it eats all available kernel RAM.
Today, not so much though an out-of-control program is still out of control.
mrguyorama 18 hours ago
It was the Windows 9x days, so of course you could also just royally screw things up by just writing to whatever memory or hardware you felt like, with few limits.
jchw 17 hours ago
You say that, but when I actually tried I found that despite not actually having robust memory protection, it's not as though it's particularly straightforward. You certainly wouldn't do it by accident... I can't imagine, anyway.
muststopmyths 12 hours ago
The C runtime has limitations as you indicated. The Win32 API does not.
File,Socket and other handles to NTOSKRNL objects (GDI is its own beast) are not limited by anything but available memory. some of the used memory is non-pageable in the kernel, and there is a limit to the non-pageable memory (1/8 of RAM, I think), so it's not as simple as RAM/(handlecount*storagecost per handle).
dwattttt 10 hours ago
taeric 21 hours ago
Granted, I can agree it is frustrating to hit an overall limit if you have tuned lower limits.
CactusRocket 21 hours ago
eddd-ddde 19 hours ago
Brian_K_White 17 hours ago
saagarjha 4 hours ago
The rationale Apple gives is something about using kernel resources, but a normal file descriptor also uses kernel resources, so I'm leaning towards implementation laziness or legacy like many of the examples here.
gajjanag 13 minutes ago
Disclaimer: I worked at Apple and poked xnu a bit.
raggi 21 hours ago
use std::io;
#[cfg(unix)]
fn raise_file_limit() -> io::Result<()> {
use libc::{getrlimit, setrlimit, rlimit, RLIMIT_NOFILE};
unsafe {
let mut rlim = rlimit {
rlim_cur: 0,
rlim_max: 0,
};
if getrlimit(RLIMIT_NOFILE, &mut rlim) != 0 {
return Err(io::Error::last_os_error());
}
rlim.rlim_cur = rlim.rlim_max;
if setrlimit(RLIMIT_NOFILE, &rlim) != 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
a_t48 17 hours ago
userbinator 6 hours ago
As you can see the max value reached is around 1600, which is way above the previous limit of 256.
Without asking and answering "why the hell does it need to keep so many files open", I don't think that's a good way to do things. Raising the limit is justified only if there is a real reason why the process needs to have that many files open simultaneously.
Izkata 22 hours ago
lsof -p $(echo $$)
The subshell isn't doing anything useful here, could just be: lsof -p $$
codedokode 20 hours ago
- it outputs memory-mapped files whose descriptor was closed (with "mem" type)
- for multi-thread processes it repeats every file for every thread
For example my system has 400 000 lines in lsof output and it is really difficult to figure out which of them count against the system-wide limit.
zx8080 21 hours ago
mattrighetti 21 hours ago
geocrasher 22 hours ago
ulimit -n 10000
to set permanently:
/etc/security/limits.conf
\* - nofile 10000
bombcar 20 hours ago
But ... that's a bad memory from long ago and far away.
geocrasher 11 hours ago
database64128 21 hours ago
nritchie 17 hours ago
database64128 6 hours ago
I doubt the kernel would actually allocate the resource space upfront. Like SO_SNDBUF and SO_RCVBUF, it's probably only allocated when it's actually needed.
saagarjha 5 hours ago
css 23 hours ago
[0]: https://github.com/ReagentX/imessage-exporter/issues/314#iss...
oatsandsugar 23 hours ago
But I reckon its unreasonable for us to ask our users to know this, and we'll have to fix the underlying cause.
amelius 4 hours ago
I hate to say it but it sounds like the developers of Unix were being lazy here.
trinix912 22 hours ago
L3viathan 16 hours ago
> At its core, a file descriptor (often abbreviated as fd) is simply a positive integer
A _non-negative_ integer.
DougN7 5 hours ago
magicalhippo 57 minutes ago
[1]: https://en.wikipedia.org/wiki/List_of_types_of_numbers#Signe...
cesarb 1 hour ago
bregma 3 hours ago
rkomorn 5 hours ago
20 hours ago
nasretdinov 23 hours ago
mhink 21 hours ago
loeg 23 hours ago
gizmo686 22 hours ago
I have had issues with not quite FD leaks, where we would open the same file a bunch of times for some tasks. It is not a leak because we close all of the FDs at the end of the task. In particular, this meant that it slipped past the explicit FD leak detection logic we had in our test harness. It also worked flawlessly in our long running stress tests.
For a while people assumed it was legit, because it only showed up on tasks that involved thousands of files, and the needed FD limit seemed to scale to the input file count.
loeg 19 hours ago
eviks 10 hours ago
That's unfortunate, is there no way to subscribe to open file events instead of polling for status?
Zaylan 12 hours ago
Increasing ulimit -n helped in the short term, but fixing the resource leaks was the real solution. Now I always keep an eye on file descriptors and use tools like lsof to double-check when something’s going wrong.
AdmiralAsshat 22 hours ago
JdeBP 18 hours ago
... but the one that comes with the operating system, on the BSDs, is fstat(1).
> 10u: Another file descriptor [...] likely used for additional terminal interactions.
The way that ZLE provides its user interface, and indeed what the Z shell does with the terminal in general, is quite interesting; and almost nothing like what one would expect from old books on the Bourne shell.
> it tries to open more files than the soft limit set by my shell
Your shell can change limits, but it isn't what is originally setting them. That is either the login program or the SSH daemon. On the BSDs, you can read about the configuration file that controls this in login.conf(5).
mzs 21 hours ago
JackYoustra 14 hours ago
NetOpWibby 7 hours ago
I really should close all these windows but meh.
jkol36 14 hours ago
NooneAtAll3 18 hours ago
what was causing so many open files?
pak9rabid 17 hours ago
gjvc 21 hours ago
quotemstr 23 hours ago
CactusRocket 21 hours ago
eviks 10 hours ago
quotemstr 20 hours ago
For example, imagine a world in which Linux had a RLIMIT_CUMULATIVE_IO:
"How else am I supposed to prevent programs wearing out my flash? Of course we should have this limit"
"Of course a program should get SIGIO after doing too much IO. It'll encourage use of compression"
"This is a security feature, dumbass. Crypto-encrypters need to write encrypted files, right? If you limit a program to writing 100MB, it can't do that much damage"
Yet we don't have a cumulative write(2) limit and the world keeps spinning. It's the same way with the limits we do have --- file number limits, vm.max_map_count, VSIZE, and so on. They're relicts of a different time, yet the I Like Limits people will retroactively justify their existence and resist attempts to make them more suitable for the modern world.
gnulinux 23 hours ago
quotemstr 22 hours ago
Dwedit 20 hours ago
quotemstr 20 hours ago
tedunangst 19 hours ago
quotemstr 16 hours ago
jcalvinowens 22 hours ago
One downside to your approach is that kernel memory is not swappable in Linux: the OOM failure mode could be much nastier than leaking memory in userspace. But almost any code in the real world is going to allocate some memory in userspace to go along with the FD, that will cause an OOM first.
duped 21 hours ago
jcalvinowens 21 hours ago
duped 20 hours ago
For example consider if you're opening/closing file descriptors concurrently. If the array never resizes the searches for free fds and close operations can happen without synchronization.
jcalvinowens 20 hours ago
Imagine a primitive UNIX with a global fixed size file descriptor probing hashtable indexed by FD+PID: that's more what I was getting at. I have no idea if such a thing really existed.
> If the array never resizes the searches for free fds and close operations can happen without synchronization.
No, you still have to (at the very least) serialize the lookups of the lowest available descriptor number if you care about complying with POSIX. In practice, you're almost certain to require more synchronization for other reasons. Threads share file descriptors.
The modern Linux implementation is not so terrible IMHO: https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds...
JdeBP 18 hours ago
quotemstr 20 hours ago
josephcsible 18 hours ago
Borg3 18 hours ago
Just keep value sane ;) 4096 or 5120 should be okish.
josephcsible 17 hours ago
quotemstr 16 hours ago
josephcsible 15 hours ago
mattrighetti 21 hours ago
I like to think that if something is there then there's a reason for it, it's just that I'm not that smart to see it :) jokes aside, I could see this as a security measure? A malware that tries to encrypt your whole filesystem in a single shot could be blocked or at least slowed down with this limit.
JdeBP 18 hours ago
kstrauser 23 hours ago
If you write a program that wants to have a million files open at once, you're almost certainly doing it wrong. Is there a real, inherent reason why the OS can't or shouldn't allow that, though?
toast0 20 hours ago
This isn't a real issue though. Usually, you can just set the soft limit to the often much higher hard limit; at worst, you just have to reboot with a big number for max fds; too many open files is a clear indicator of a missing config, and off we go. The defaults limits are small and that usually works because most of the time a program opening 1M fds is broken.
Kind of annoying when Google decides their container optimized OS should go from soft and hard limits of 1M to soft limit 1024, hard limit 512k though.
quotemstr 22 hours ago
A file descriptor is just the name of a kernel resource. Why shouldn't I be able to have a ton of inotify watches, sockets, dma_buf texture descriptors, or memfd file descriptors? Systems like DRM2 work around FD limits by using their own ID namespaces instead of file descriptors and make the system thereby uglier and more bug-prone. Some programs that regularly bump up against default FD limits are postgres, nginx, the docker daemon, watchman, and notoriously, JetBrains IDEs.
Why? Why do we live like this?
kstrauser 20 hours ago
Like, there’s not a limit on how many times you can call malloc() AFAIK, and the logic for limiting the number of those calls seems to be the same as for open files. “If you call malloc too many times, your program is buggy and you should fix it!” isn’t a thing, but yet allocating an open file is locked down hard.
jeffbee 16 hours ago
hulitu 21 hours ago
Yes, because you are not alone in this universe. A user does usually run more than one program and all programs shall have access to resources (cpu time, memory, disk space).
Dylan16807 17 hours ago
Especially when most of these resources go back to memory. If you want a limit, limit memory. Don't make it overcomplicated.
kstrauser 20 hours ago
LAC-Tech 17 hours ago
1024 on my workstation. seems low.
jeffbee 16 hours ago
The only reason you'd want 1024 as a limit is if you intend to start a process that might have been naively written to use `select` without checking the limits.
MobiusHorizons 15 hours ago
LAC-Tech 14 hours ago