[SV 46261] Use pselect() for jobserver where supported.

* Makefile.am, configure.ac: Check for pselect() and sys/select.h.
* main.c (main): Block SIGCHLD if we have pselect() support.
* posixos.c (jobserver_acquire): If we support pselect() then use
it to query the jobserver pipe, while also listening for SIGCHLD.
Also pselect() supports a timeout so avoid alarm() calls.
This commit is contained in:
Paul Smith 2016-03-08 23:07:14 -05:00
parent 8164c8ab3d
commit 85c788572d
6 changed files with 126 additions and 46 deletions

View file

@ -16,8 +16,8 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
AUTOMAKE_OPTIONS = 1.8 dist-bzip2 check-news
ACLOCAL_AMFLAGS = -I config
AUTOMAKE_OPTIONS = dist-bzip2 silent-rules std-options
ACLOCAL_AMFLAGS = -I config
MAKE_HOST = @MAKE_HOST@

View file

@ -18,7 +18,7 @@
AC_INIT([GNU make],[4.1.90],[bug-make@gnu.org])
AC_PREREQ([2.62])
AC_PREREQ([2.69])
# Autoconf setup
AC_CONFIG_AUX_DIR([config])
@ -29,7 +29,7 @@ AC_CONFIG_HEADERS([config.h])
# We have to enable "foreign" because ChangeLog is auto-generated
# We cannot enable -Werror because gettext 0.18.1 has invalid content
# When we update gettext to 0.18.3 or better we can add it again.
AM_INIT_AUTOMAKE([1.11.1 silent-rules foreign -Wall])
AM_INIT_AUTOMAKE([1.15 foreign -Werror -Wall])
# Checks for programs.
AC_USE_SYSTEM_EXTENSIONS
@ -51,7 +51,7 @@ AC_ISC_POSIX
AC_MINIX
# Enable gettext, in "external" mode.
AM_GNU_GETTEXT_VERSION([0.18.1])
AM_GNU_GETTEXT_VERSION([0.19.4])
AM_GNU_GETTEXT([external])
# This test must come as early as possible after the compiler configuration
@ -68,7 +68,8 @@ AC_HEADER_DIRENT
AC_HEADER_STAT
AC_HEADER_TIME
AC_CHECK_HEADERS([stdlib.h locale.h unistd.h limits.h fcntl.h string.h \
memory.h sys/param.h sys/resource.h sys/time.h sys/timeb.h])
memory.h sys/param.h sys/resource.h sys/time.h sys/timeb.h \
sys/select.h])
AM_PROG_CC_C_O
AC_C_CONST
@ -135,7 +136,7 @@ AC_CHECK_FUNCS([strdup strndup mkstemp mktemp fdopen fileno \
dup dup2 getcwd realpath sigsetmask sigaction \
getgroups seteuid setegid setlinebuf setreuid setregid \
getrlimit setrlimit setvbuf pipe strerror strsignal \
lstat readlink atexit isatty ttyname])
lstat readlink atexit isatty ttyname pselect])
# We need to check declarations, not just existence, because on Tru64 this
# function is not declared without special flags, which themselves cause

13
job.c
View file

@ -527,10 +527,11 @@ child_error (struct child *child,
/* Handle a dead child. This handler may or may not ever be installed.
If we're using the jobserver feature, we need it. First, installing it
ensures the read will interrupt on SIGCHLD. Second, we close the dup'd
read FD to ensure we don't enter another blocking read without reaping all
the dead children. In this case we don't need the dead_children count.
If we're using the jobserver feature without pselect(), we need it.
First, installing it ensures the read will interrupt on SIGCHLD. Second,
we close the dup'd read FD to ensure we don't enter another blocking read
without reaping all the dead children. In this case we don't need the
dead_children count.
If we don't have either waitpid or wait3, then make is unreliable, but we
use the dead_children count to reap children as best we can. */
@ -548,10 +549,6 @@ child_handler (int sig UNUSED)
/* The signal handler must called only once! */
signal (SIGCHLD, SIG_DFL);
#endif
/* This causes problems if the SIGCHLD interrupts a printf().
DB (DB_JOBS, (_("Got a SIGCHLD; %u unreaped children.\n"), dead_children));
*/
}
extern pid_t shell_function_pid;

12
main.c
View file

@ -1870,6 +1870,18 @@ main (int argc, char **argv, char **envp)
bsd_signal (SIGCLD, child_handler);
# endif
}
#ifdef HAVE_PSELECT
/* If we have pselect() then we need to block SIGCHLD so it's deferred. */
{
sigset_t block;
sigemptyset (&block);
sigaddset (&block, SIGCHLD);
if (sigprocmask (SIG_SETMASK, &block, NULL) < 0)
pfatal_with_name ("sigprocmask(SIG_SETMASK, SIGCHLD)");
}
#endif
#endif
#endif

View file

@ -46,7 +46,7 @@ unsigned int stdio_traced = 0;
#define OUTPUT_ISSET(_out) ((_out)->out >= 0 || (_out)->err >= 0)
#ifdef HAVE_FCNTL
#ifdef HAVE_FCNTL_H
# define STREAM_OK(_s) ((fcntl (fileno (_s), F_GETFD) != -1) || (errno != EBADF))
#else
# define STREAM_OK(_s) 1

130
posixos.c
View file

@ -21,6 +21,9 @@ this program. If not, see <http://www.gnu.org/licenses/>. */
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#if defined(HAVE_PSELECT) && defined(HAVE_SYS_SELECT_H)
# include <sys/select.h>
#endif
#include "debug.h"
#include "job.h"
@ -33,7 +36,9 @@ this program. If not, see <http://www.gnu.org/licenses/>. */
/* These track the state of the jobserver pipe. Passed to child instances. */
static int job_fds[2] = { -1, -1 };
/* Used to signal read() that a SIGCHLD happened. Always CLOEXEC. */
/* Used to signal read() that a SIGCHLD happened. Always CLOEXEC.
If we use pselect() this will never be created and always -1.
*/
static int job_rfd = -1;
/* Token written to the pipe (could be any character...) */
@ -42,11 +47,16 @@ static char token = '+';
static int
make_job_rfd ()
{
#ifdef HAVE_PSELECT
/* Pretend we succeeded. */
return 0;
#else
EINTRLOOP (job_rfd, dup (job_fds[0]));
if (job_rfd >= 0)
CLOSE_ON_EXEC (job_rfd);
return job_rfd;
#endif
}
void
@ -80,19 +90,21 @@ jobserver_parse_arg (const char* arg)
DB (DB_JOBS,
(_("Jobserver client (fds %d,%d)\n"), job_fds[0], job_fds[1]));
#ifdef HAVE_FCNTL
# define FD_OK(_f) ((fcntl ((_f), F_GETFD) != -1) || (errno != EBADF))
#ifdef HAVE_FCNTL_H
# define FD_OK(_f) (fcntl ((_f), F_GETFD) != -1)
#else
# define FD_OK(_f) 1
#endif
/* Create a duplicate pipe, that will be closed in the SIGCHLD handler. If
this fails with EBADF, the parent has closed the pipe on us because it
didn't think we were a submake. If so, warn then default to -j1. */
/* Make sure our pipeline is valid, and (possibly) create a duplicate pipe,
that will be closed in the SIGCHLD handler. If this fails with EBADF,
the parent has closed the pipe on us because it didn't think we were a
submake. If so, warn and default to -j1. */
if (!FD_OK (job_fds[0]) || !FD_OK (job_fds[1]) || make_job_rfd () < 0)
{
if (errno != EBADF)
pfatal_with_name (_("dup jobserver"));
pfatal_with_name (_("jobserver pipeline"));
O (error, NILF,
_("warning: jobserver unavailable: using -j1. Add '+' to parent make rule."));
@ -186,7 +198,85 @@ void jobserver_post_child ()
#endif
}
/* The acquire algorithm goes like this (from job.c):
void
jobserver_signal ()
{
if (job_rfd >= 0)
{
close (job_rfd);
job_rfd = -1;
}
}
void
jobserver_pre_acquire ()
{
/* Make sure we have a dup'd FD. */
if (job_rfd < 0 && job_fds[0] >= 0 && make_job_rfd () < 0)
pfatal_with_name (_("duping jobs pipe"));
}
#ifdef HAVE_PSELECT
/* Use pselect() to atomically wait for both a signal and a file descriptor.
It also provides a timeout facility so we don't need to use SIGALRM.
This method relies on the fact that SIGCHLD will be blocked everywhere,
and only unblocked (atomically) within the pselect() call, so we can
never miss a SIGCHLD.
*/
int
jobserver_acquire (int timeout)
{
sigset_t empty;
fd_set readfds;
struct timespec spec;
struct timespec *specp = NULL;
int r;
char intake;
sigemptyset (&empty);
FD_ZERO (&readfds);
FD_SET (job_fds[0], &readfds);
if (timeout)
{
/* Alarm after one second (is this too granular?) */
spec.tv_sec = 1;
spec.tv_nsec = 0;
specp = &spec;
}
r = pselect (job_fds[0]+1, &readfds, NULL, NULL, specp, &empty);
if (r == -1)
{
/* Better be SIGCHLD. */
if (errno != EINTR)
pfatal_with_name (_("pselect jobs pipe"));
return 0;
}
if (r == 0)
/* Timeout. */
return 0;
/* The read FD is ready: read it! */
EINTRLOOP (r, read (job_fds[0], &intake, 1));
if (r < 0)
pfatal_with_name (_("read jobs pipe"));
/* What does it mean if read() returns 0? It shouldn't happen because only
the master make can reap all the tokens and close the write side...?? */
return r;
}
#else
/* This method uses a "traditional" UNIX model for waiting on both a signal
and a file descriptor. However, it's complex and since we have a SIGCHLD
handler installed we need to check ALL system calls for EINTR: painful!
Read a token. As long as there's no token available we'll block. We
enable interruptible system calls before the read(2) so that if we get a
@ -264,28 +354,6 @@ set_child_handler_action_flags (int set_handler, int set_alarm)
#endif
}
void
jobserver_signal ()
{
if (job_rfd >= 0)
{
close (job_rfd);
job_rfd = -1;
}
}
void
jobserver_pre_acquire ()
{
/* Make sure we have a dup'd FD. */
if (job_rfd < 0 && job_fds[0] >= 0)
{
DB (DB_JOBS, ("Duplicate the job FD\n"));
if (make_job_rfd () < 0)
pfatal_with_name (_("duping jobs pipe"));
}
}
int
jobserver_acquire (int timeout)
{
@ -317,4 +385,6 @@ jobserver_acquire (int timeout)
return 0;
}
#endif
#endif /* MAKE_JOBSERVER */