[Libguestfs] [PATCH] lib: command: switch from select() to poll()

Richard W.M. Jones rjones at redhat.com
Wed Feb 26 18:41:30 UTC 2020


On Wed, Feb 26, 2020 at 03:14:37PM +0100, Pino Toscano wrote:
> On Wednesday, 26 February 2020 15:08:24 CET Daniel P. Berrangé wrote:
> > On Wed, Feb 26, 2020 at 02:39:04PM +0100, Pino Toscano wrote:
> > > select() has a maximum value for the FDs it can monitor, and since
> > > the libguestfs library can be used in other applications, this limit
> > > may be hit by users in case lots of FDs are opened.
> > > 
> > > As solution, switch to poll(): it has a slightly better interface to
> > > check what changed and for which FD, and it does not have a limit in the
> > > value of the FDs monitored.
> > > 
> > > poll() is supported on the platforms we support, so there is no need to
> > > use the gnulib module for it.
> > > ---
> > >  lib/command.c | 54 ++++++++++++++++++++++++++++++++++-----------------
> > >  1 file changed, 36 insertions(+), 18 deletions(-)
> > > 
> > > diff --git a/lib/command.c b/lib/command.c
> > > index f2161de9a..13b084934 100644
> > > --- a/lib/command.c
> > > +++ b/lib/command.c
> > > @@ -89,9 +89,8 @@
> > >  #include <signal.h>
> > >  #include <errno.h>
> > >  #include <assert.h>
> > > -#include <sys/types.h>
> > >  #include <sys/stat.h>
> > > -#include <sys/select.h>
> > > +#include <poll.h>
> > >  
> > >  #ifdef HAVE_SYS_TIME_H
> > >  #include <sys/time.h>
> > > @@ -650,37 +649,41 @@ run_child (struct command *cmd)
> > >  static int
> > >  loop (struct command *cmd)
> > >  {
> > > -  fd_set rset, rset2;
> > > -  int maxfd = -1, r;
> > > +  struct pollfd fds[2];
> > > +  int r;
> > >    size_t nr_fds = 0;
> > >    CLEANUP_FREE char *buf = safe_malloc (cmd->g, BUFSIZ);
> > >    ssize_t n;
> > >  
> > > -  FD_ZERO (&rset);
> > > +  memset (&fds, 0, sizeof fds);
> > > +
> > > +  fds[0].fd = cmd->errorfd;
> > > +  fds[1].fd = cmd->outfd;
> > >  
> > >    if (cmd->errorfd >= 0) {
> > > -    FD_SET (cmd->errorfd, &rset);
> > > -    maxfd = MAX (cmd->errorfd, maxfd);
> > > +    fds[0].events = POLLIN;
> > >      nr_fds++;
> > >    }
> > >  
> > >    if (cmd->outfd >= 0) {
> > > -    FD_SET (cmd->outfd, &rset);
> > > -    maxfd = MAX (cmd->outfd, maxfd);
> > > +    fds[1].events = POLLIN;
> > >      nr_fds++;
> > >    }
> > >  
> > >    while (nr_fds > 0) {
> > > -    rset2 = rset;
> > > -    r = select (maxfd+1, &rset2, NULL, NULL, NULL);
> > > +    r = poll (fds, 2, -1);
> > >      if (r == -1) {
> > >        if (errno == EINTR || errno == EAGAIN)
> > >          continue;
> > > -      perrorf (cmd->g, "select");
> > > +      perrorf (cmd->g, "poll");
> > > +      return -1;
> > > +    }
> > > +    if (fds[0].revents & POLLERR || fds[1].revents & POLLERR) {
> > > +      perrorf (cmd->g, "poll");
> > >        return -1;
> > >      }
> > >  
> > > -    if (cmd->errorfd >= 0 && FD_ISSET (cmd->errorfd, &rset2)) {
> > > +    if (fds[0].revents & POLLIN) {
> > >        /* Read output and send it to the log. */
> > >        n = read (cmd->errorfd, buf, BUFSIZ);
> > >        if (n > 0)
> > > @@ -689,20 +692,26 @@ loop (struct command *cmd)
> > >        else if (n == 0) {
> > >          if (close (cmd->errorfd) == -1)
> > >            perrorf (cmd->g, "close: errorfd");
> > > -        FD_CLR (cmd->errorfd, &rset);
> > > +        fds[0].fd = -1;
> > >          cmd->errorfd = -1;
> > >          nr_fds--;
> > >        }
> > >        else if (n == -1) {
> > >          perrorf (cmd->g, "read: errorfd");
> > >          close (cmd->errorfd);
> > > -        FD_CLR (cmd->errorfd, &rset);
> > > +        fds[0].fd = -1;
> > >          cmd->errorfd = -1;
> > >          nr_fds--;
> > >        }
> > >      }
> > > +    if (fds[0].revents & POLLHUP) {
> > > +      close (cmd->errorfd);
> > > +      fds[0].fd = -1;
> > > +      cmd->errorfd = -1;
> > > +      nr_fds--;
> > > +    }
> > 
> > Now fds[0] is -1, and fds[1] is still an open file
> > descriptor.
> > 
> > Next time around the loop it will call poll() on 'fds' array
> > which contains this FD == -1. In theory that results in POLLNVAL,
> > but on OS-X at least it causes poll() itself to return an error.
> 
> Hmm in the documentations there are these bits:
> 
> http://man7.org/linux/man-pages/man2/poll.2.html
> "The  field fd contains a file descriptor for an open file.  If this
> field is negative, then the corresponding events field is ignored and
> the revents field returns zero.  (This provides an easy way of ignoring
> a file descriptor for a single poll() call: simply negate the fd field.
> Note, however, that this technique can't be used to ignore file
> descriptor 0.)"
> 
> https://linux.die.net/man/3/poll
> "If the value of fd is less than 0, events shall be ignored, and revents
> shall be set to 0 in that entry on return from poll()."
> 
> So to my understanding it is fine, and indeed I did not get POLLNVAL
> in my testing.

In fact libnbd & nbdkit also rely on this behaviour.  I don't believe
we've ever tried either one on macOS though.  It seems to be mandated
by POSIX too:

https://pubs.opengroup.org/onlinepubs/009695399/functions/poll.html

  "If the value of fd is less than 0, events shall be ignored, and
  revents shall be set to 0 in that entry on return from poll()."

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
libguestfs lets you edit virtual machines.  Supports shell scripting,
bindings from many languages.  http://libguestfs.org




More information about the Libguestfs mailing list