[PATCH] Windows jobserver updates for GNU make 4.1
Troy Runkel <[email protected]> Wed, 24 Feb 2016 17:27:57 +0000
| Newsgroups | gmane.comp.gnu.make.windows |
|---|---|
| Message-ID | <[email protected]> |
This patch expands the maximum number of simultaneous jobs on Windows from 63 (limit dictated by Windows MAXIMUM_WAIT_OBJECTS) to 4095. It also fixes a situation where GNU make on Windows could deadlock and/or suffer memory corruption issues if -j or -j63 was used. The problem was due to the way that $(shell ...) commands are handled. --Troy Runkel _______________________________________________ Make-w32 mailing list [email protected] https://lists.gnu.org/mailman/listinfo/make-w32
windows_jobserver_update.diff
(application/octet-stream, 9.4 KB)
diff -Naur make-4.1-orig/job.c make-4.1/job.c
--- make-4.1-orig/job.c 2016-02-22 16:19:54.657281947 -0500
+++ make-4.1/job.c 2016-02-22 16:20:00.393718803 -0500
@@ -1713,7 +1713,7 @@
if (!c->remote
&& ((job_slots_used > 0 && load_too_high ())
#ifdef WINDOWS32
- || (process_used_slots () >= MAXIMUM_WAIT_OBJECTS)
+ || process_table_full()
#endif
))
{
@@ -2149,8 +2149,8 @@
time_t now;
#ifdef WINDOWS32
- /* sub_proc.c cannot wait for more than MAXIMUM_WAIT_OBJECTS children */
- if (process_used_slots () >= MAXIMUM_WAIT_OBJECTS)
+ /* sub_proc.c is limited in the number of objects it can wait for. */
+ if (process_table_full())
return 1;
#endif
diff -Naur make-4.1-orig/main.c make-4.1/main.c
--- make-4.1-orig/main.c 2016-02-22 16:19:54.665221503 -0500
+++ make-4.1/main.c 2016-02-22 16:20:00.400834834 -0500
@@ -1984,13 +1984,10 @@
if (job_slots > 1)
{
#ifdef WINDOWS32
- /* sub_proc.c cannot wait for more than MAXIMUM_WAIT_OBJECTS objects
- * and one of them is the job-server semaphore object. Limit the
- * number of available job slots to (MAXIMUM_WAIT_OBJECTS - 1). */
-
- if (job_slots >= MAXIMUM_WAIT_OBJECTS)
+ /* sub_proc.c is limited in the number of objects it can wait for. */
+ if (job_slots > max_slots_with_jobserver())
{
- job_slots = MAXIMUM_WAIT_OBJECTS - 1;
+ job_slots = max_slots_with_jobserver();
DB (DB_JOBS, (_("Jobserver slots limited to %d\n"), job_slots));
}
diff -Naur make-4.1-orig/w32/include/sub_proc.h make-4.1/w32/include/sub_proc.h
--- make-4.1-orig/w32/include/sub_proc.h 2016-02-22 16:19:55.045178306 -0500
+++ make-4.1/w32/include/sub_proc.h 2016-02-22 16:20:00.774255389 -0500
@@ -44,7 +44,8 @@
EXTERN_DECL(HANDLE process_easy, (char** argv, char** env,
int outfd, int errfd));
EXTERN_DECL(BOOL process_kill, (HANDLE proc, int signal));
-EXTERN_DECL(int process_used_slots, (VOID_DECL));
+EXTERN_DECL(BOOL process_table_full, (VOID_DECL));
+EXTERN_DECL(int max_slots_with_jobserver, (VOID_DECL));
/* support routines */
EXTERN_DECL(long process_errno, (HANDLE proc));
diff -Naur make-4.1-orig/w32/subproc/sub_proc.c make-4.1/w32/subproc/sub_proc.c
--- make-4.1-orig/w32/subproc/sub_proc.c 2016-02-22 16:19:55.056084112 -0500
+++ make-4.1/w32/subproc/sub_proc.c 2016-02-23 12:16:04.265358637 -0500
@@ -14,6 +14,7 @@
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>. */
+#include <assert.h>
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
@@ -36,6 +37,12 @@
#include "w32err.h"
#include "debug.h"
+#define GMAKE_MAXIMUM_WAIT_OBJECTS (MAXIMUM_WAIT_OBJECTS * MAXIMUM_WAIT_OBJECTS)
+
+/* We need to move these special-case return codes out-of-band */
+#define GMAKE_WAIT_TIMEOUT 0xFFFF0102L
+#define GMAKE_WAIT_ABANDONED_0 0x00080000L
+
static char *make_command_line(char *shell_name, char *exec_path, char **argv);
typedef struct sub_process_t {
@@ -57,7 +64,7 @@
} sub_process;
/* keep track of children so we can implement a waitpid-like routine */
-static sub_process *proc_array[MAXIMUM_WAIT_OBJECTS];
+static sub_process *proc_array[GMAKE_MAXIMUM_WAIT_OBJECTS];
static int proc_index = 0;
static int fake_exits_pending = 0;
@@ -65,6 +72,69 @@
static char jobserver_semaphore_name[MAX_PATH + 1];
static HANDLE jobserver_semaphore = NULL;
+/*
+ * Address the scalability limit intrisic to WaitForMultipleOjects by
+ * calling WaitForMultipleObjects on 64 element chunks of the input
+ * array with 0 timeout. Exit with an appropriately conditioned result
+ * or repeat again every 10 ms if no handle has signaled and the
+ * requested timeout was not zero.
+ */
+DWORD GmakeWaitForMultipleObjects(
+ DWORD nCount,
+ const HANDLE *lpHandles,
+ BOOL bWaitAll,
+ DWORD dwMilliseconds
+)
+{
+ assert(nCount <= GMAKE_MAXIMUM_WAIT_OBJECTS);
+
+ if (nCount <= MAXIMUM_WAIT_OBJECTS) {
+ DWORD retVal = WaitForMultipleObjects(nCount, lpHandles, bWaitAll, dwMilliseconds);
+ return (retVal == WAIT_TIMEOUT) ? GMAKE_WAIT_TIMEOUT : retVal;
+ } else {
+ for (;;) {
+ DWORD objectCount = nCount;
+ int blockCount = 0;
+ DWORD retVal;
+
+ assert(bWaitAll == FALSE); /* This logic only works for this use case */
+ assert(dwMilliseconds == 0 || dwMilliseconds == INFINITE); /* No support for timeouts */
+
+ for (; objectCount > 0; blockCount++) {
+ DWORD n = objectCount <= MAXIMUM_WAIT_OBJECTS ? objectCount : MAXIMUM_WAIT_OBJECTS;
+ objectCount -= n;
+ retVal = WaitForMultipleObjects(n, &lpHandles[blockCount * MAXIMUM_WAIT_OBJECTS],
+ FALSE, 0);
+ switch (retVal) {
+ case WAIT_TIMEOUT:
+ retVal = GMAKE_WAIT_TIMEOUT;
+ continue;
+ break;
+ case WAIT_FAILED:
+ fprintf(stderr,"WaitForMultipleOjbects failed waiting with error %d\n", GetLastError());
+ break;
+ default:
+ if (retVal >= WAIT_ABANDONED_0) {
+ assert(retVal < WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS);
+ retVal += blockCount * MAXIMUM_WAIT_OBJECTS - WAIT_ABANDONED_0 + GMAKE_WAIT_ABANDONED_0;
+ } else {
+ assert(retVal < WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS);
+ retVal += blockCount * MAXIMUM_WAIT_OBJECTS;
+ }
+ break;
+ }
+
+ return retVal;
+
+ }
+
+ if (dwMilliseconds == 0) return retVal;
+
+ Sleep(10); /* Sleep for 10 ms */
+ }
+ }
+}
+
/* Open existing jobserver semaphore */
int open_jobserver_semaphore(const char* name)
{
@@ -142,13 +212,13 @@
*/
int wait_for_semaphore_or_child_process()
{
- HANDLE handles[MAXIMUM_WAIT_OBJECTS];
- DWORD dwHandleCount = 1;
+ HANDLE handles[GMAKE_MAXIMUM_WAIT_OBJECTS];
+ DWORD dwHandleCount = 0;
DWORD dwEvent;
int i;
/* Add jobserver semaphore to first slot. */
- handles[0] = jobserver_semaphore;
+ handles[dwHandleCount++] = jobserver_semaphore;
/* Build array of handles to wait for */
for (i = 0; i < proc_index; i++)
@@ -160,7 +230,7 @@
handles[dwHandleCount++] = (HANDLE) proc_array[i]->pid;
}
- dwEvent = WaitForMultipleObjects(
+ dwEvent = GmakeWaitForMultipleObjects(
dwHandleCount, // number of objects in array
handles, // array of objects
FALSE, // wait for any object
@@ -212,7 +282,7 @@
static sub_process *
process_wait_for_any_private(int block, DWORD* pdwWaitStatus)
{
- HANDLE handles[MAXIMUM_WAIT_OBJECTS];
+ HANDLE handles[GMAKE_MAXIMUM_WAIT_OBJECTS];
DWORD retval, which;
int i;
@@ -229,7 +299,7 @@
/* wait for someone to exit */
if (!fake_exits_pending) {
- retval = WaitForMultipleObjects(proc_index, handles, FALSE, (block ? INFINITE : 0));
+ retval = GmakeWaitForMultipleObjects(proc_index, handles, FALSE, (block ? INFINITE : 0));
which = retval - WAIT_OBJECT_0;
} else {
fake_exits_pending--;
@@ -239,10 +309,10 @@
/* If the pointer is not NULL, set the wait status result variable. */
if (pdwWaitStatus)
- *pdwWaitStatus = retval;
+ *pdwWaitStatus = (retval == GMAKE_WAIT_TIMEOUT) ? WAIT_TIMEOUT : retval;
/* return pointer to process */
- if ((retval == WAIT_TIMEOUT) || (retval == WAIT_FAILED)) {
+ if ((retval == GMAKE_WAIT_TIMEOUT) || (retval == WAIT_FAILED)) {
return NULL;
}
else {
@@ -264,25 +334,37 @@
}
/*
- * Use this function to register processes you wish to wait for by
- * calling process_file_io(NULL) or process_wait_any(). This must be done
- * because it is possible for callers of this library to reuse the same
- * handle for multiple processes launches :-(
+ * Returns true when we have no more available slots in our process table.
*/
-void
-process_register(HANDLE proc)
+BOOL
+process_table_full()
{
- if (proc_index < MAXIMUM_WAIT_OBJECTS)
- proc_array[proc_index++] = (sub_process *) proc;
+ extern int shell_function_pid;
+
+ /* Reserve slots for jobserver_semaphore if we have one and the shell function if not active */
+ return(proc_index >= GMAKE_MAXIMUM_WAIT_OBJECTS - has_jobserver_semaphore() - (shell_function_pid == 0));
}
/*
- * Return the number of processes that we are still waiting for.
+ * Returns the maximum number of job slots we can support when using the jobserver.
*/
int
-process_used_slots(void)
+max_slots_with_jobserver()
+{
+ /* Reserve slots for jobserver_semaphore and shell function */
+ return(GMAKE_MAXIMUM_WAIT_OBJECTS - 2);
+}
+/*
+ * Use this function to register processes you wish to wait for by
+ * calling process_file_io(NULL) or process_wait_any(). This must be done
+ * because it is possible for callers of this library to reuse the same
+ * handle for multiple processes launches :-(
+ */
+void
+process_register(HANDLE proc)
{
- return proc_index;
+ assert(proc_index < GMAKE_MAXIMUM_WAIT_OBJECTS);
+ proc_array[proc_index++] = (sub_process *) proc;
}
/*
@@ -1441,7 +1523,7 @@
HANDLE hProcess, tmpIn, tmpOut, tmpErr;
DWORD e;
- if (proc_index >= MAXIMUM_WAIT_OBJECTS) {
+ if (process_table_full()) {
DB (DB_JOBS, ("process_easy: All process slots used up\n"));
return INVALID_HANDLE_VALUE;
}