Skip to content

Commit

Permalink
Replace use of select() by poll(). Fixes NixOS#798.
Browse files Browse the repository at this point in the history
`select()` only works with FD numbers <= 1023.

Since we have no explicit control over FD numbers,
`select()` can fail when there are many files open.

This commit uses `poll()` instead, which doesn't suffer
from this restriction.

`poll()` doesn't work on Windows, but that's not
a supported platform for Nixops anyway.
  • Loading branch information
nh2 committed Jun 2, 2018
1 parent 2609acc commit 5ded7a0
Showing 1 changed file with 32 additions and 1 deletion.
33 changes: 32 additions & 1 deletion nixops/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,34 @@ def __str__(self):
return "{0} (exit code {1})".format(self.message, self.exitcode)


def poll_file_like_objects(objs, timeout=None, eventmask=None):
"""
Like `select.select()`, but implemented using `poll()`.
Given `objs` must be a list of file-like objects that with
a `fileno()` method.
Like `select.select()` and unlike `select.poll()` it returns a
subset of the passed-in `objs`, instead of plain integer FDs,
and the `timeout` given is in seconds, not milliseconds.
Be aware that if `timeout` is smaller than a millisecond,
float-to-int conversion may round it down to 0.
"""
fileno_obj_map = { f.fileno(): f for f in objs }
poller = select.poll()
for fileno in fileno_obj_map.keys():
# `register()` doesn't accept `eventmask` as a keyword arg
if eventmask is not None:
poller.register(fileno, eventmask)
else:
poller.register(fileno)
filenos_with_events = poller.poll(timeout * 1000)
return [ fileno_obj_map[fileno] for (fileno, event) in filenos_with_events ]


READ_ONLY_POLL_MASK = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR


def logged_exec(command, logger, check=True, capture_stdout=False, stdin=None,
stdin_string=None, env=None):
"""
Expand Down Expand Up @@ -110,7 +138,10 @@ def logged_exec(command, logger, check=True, capture_stdout=False, stdin=None,
# background but keep the parent's stdout/stderr open,
# preventing an EOF. FIXME: Would be better to catch
# SIGCHLD.
(r, w, x) = select.select(fds, [], [], 1)

r = poll_file_like_objects(fds, timeout=1, # 1 second
eventmask=READ_ONLY_POLL_MASK)

if len(r) == 0 and process.poll() is not None:
break
if capture_stdout and process.stdout in r:
Expand Down

0 comments on commit 5ded7a0

Please sign in to comment.