select is buggy
,
, and
provide the same functionality with different API.
Of them,
is the oldest and the best known. It is utilized widely. However,
and similar
do not scale well with the number of files, sockets, and other kernel objects that have file descriptors. Even worse,
and
may crash programs. They should not be used in enterprise‑strength codes. It is advisable to replace them at the first opportunity by
,
, or
that are safe.
Preliminary Notes and Terminology
-
File descriptors () are Swiss knifes of UNIX / Linux. They serve as identifiers of all types of operation system kernel‑level objects: open «usual» files, directories, devices, sockets, pipes,
events created by , signal acceptors created by , and many more. There is a saying that «everything is a file» in UNIX. Or, using more precise wording by Linus Torvalds, «everything is a file descriptor or a process».
As it is customary in manpages and other documentation, I am going to use a shorthand. Instead of saying that a function is working on an object with file descriptor
, I will say that it works on the
itself. For example, I'm going to say that a code reads from
, instead of «from a socket that has file descriptor
».
Pretty much everything that I am going to say about is applied to as well. Similarly, what I will tell about is also applied to . For the reason, and are mentioned very seldom if at all.
Symptoms and Diagnostics
An application crashes, likely with a
signal. An usual call tracing in a dump file does not work. For an experienced programmer, the stack looks suspiciously reminiscent of what happens after out of bounds modifications of fixed size arrays. The stack is corrupted.
Debug prints may show that the crash happens when your program calls a standard system call
, or when preparing arguments for the call, or soon after returning from the call. Unfortunately, the debug prints not always are available. Or their last lines may be lost. Or a gibberish may be printed.
Unless you already suspected what is causing the crashes, it is unlikely that you are going to check values of
monitored by
. If you will do it when crashes occurred, e.g. by logging the
in a debug print or by running command
1 | ls -l /proc/PID/fd | less
|
where PID denotes a process ID, you may see that the
was bigger than 1023.
Great! You probably found what crashes the program. Very likely your codes are OK, except they should not use
.
Why should not be used
A few programmers would expect it from a standard facility, but
is buggy. The bug is in API, that is impossible to fix.
Instead, it is documented as a limitation:
should not be used for monitoring that are equal or bigger than a compile‑time constant defined in a header file. For Linux,
usually is equal to 1024.
What if you ignore the limitation and try to monitor a socket
with a bigger
?
The Linux documentation does not answer the question. The behavior is undefined. Anything may happen. Particularly, the code may corrupt a stack. In a practice, it
will corrupt a stack, though depending on content and layout of stack data, not all buggy programs crash as soon as
exceeds 1023. They may crash after it surpasses a higher threshold, like 3000 or 6000. For smaller values,
is «only» going to corrupt local data stored on a stack, instead of crashing. Sarcasm is intended.
Note:
will corrupt stack and may crash an application
when even a single watched
is equal or exceeds
.
You need to monitor an absolutely legit
assigned to a socket when it was created. Unfortunately, the
is equal or exceeds
. What can you do in such a situation other than print a diagnostic message and abort?
So, the limitation actually is a bug!
Informal explanation
In most of doctor's offices, you must fill a form describing your health. Some of the forms contain very long lists of health problems, that you must read thoroughly and check the respective boxes. Other forms simply provide a few blank lines, which you fill by names of relevant illnesses. Alternatively, a staff just asks if anything changed since your previous visit. API of
,
and
are similar to the above forms / questions:
-
Arguments of are long lists of checkboxes, one per . Most of the checkboxes are irrelevant and will be left empty.
-
takes a list of to watch; it does not care about the irrelevant ones. You must supply the information at every call to both and , much as you must fill forms at every visit to some of doctors offices.
-
The facility is more like a doctor office that has your information on file and asks only for updates. The first time, a calling code tells what must be added to a monitored list. Subsequently, it provides only updates: what new to add, what to delete, and similar.
It is pretty obvious that
has a more efficient API than
, because the former does not read and discard the irrelevant checkboxes. The bigger issue with
is that,
unlike with paper forms processed manually, in programming you cannot write on margins of fixed size forms. If its size is fixed, it is fixed. Filling more checkboxes than a maximum provided means writing out of bounds of a fixed size array. Assuming that the array is in a stack, the stack will be corrupted.
Solution
Just replace all
in your codes by
. This is the easiest way to fix the problem. The
API does no impose the arbitrary limitations on values of
.
can monitor any
.
As a bonus, the
API usually is more efficient. Some people say that this is
the reason why
is preferable to
. For me it sounds as a recommendation to keep out of a plague because the disease causes headaches. Though technically correct, it would be a serious understatement.
The difference is not abstract: it affects business decisions. Codes that are prone to crash should be fixed as soon as possible, typically in the next maintenance release. On the other hand, it is seldom advisable to modify correct, stable and proved production applications just to eliminate small to moderate inefficiencies.
Notes:
-
Sometimes it is more appropriate to use . Just somewhat less simple than replacing by .
-
There are workarounds the system limitations on , but the workarounds are unreliable and/or much more complicated than replacing by . And they are very inefficient.
Yuriy Koblents-Mishke
December 6, 2013