Increase FD_SETSIZE on Windows to support more subprocesses

Earlier versions of Emacs were limited to at most 32 subprocesses or
network connections on Windows due to the 64-object limit imposed by
WaitForMultipleObjects.  To overcome this, a simple waiting thread
pool is implemented, allowing Emacs to efficiently wait on up to
2048 objects.

Each thread in the pool can wait on up to 63 objects, and a total of
32 threads are used, together with the main thread, to expand the
waiting capability.  This enables Emacs to support approximately 1024
subprocesses, which is comparable to the 'pty' method on GNU/Linux
when using the default FD_SETSIZE of 1024.

To minimize overhead, the threads remain active instead of being
frequently created and destroyed, reducing unnecessary system
resource consumption.  Idle threads can be terminated after a
period of inactivity to free up memory.

* src/w32.h (FD_SETSIZE): Change from 64 to 2048.
* src/w32.c (term_ntproc): Call 'free_wait_pool' to free waiting
threads.
* src/w32proc.c (WFO_ABANDONED, WFO_TIMEOUT, WFO_FAILED)
(WFO_MAX_WAIT): New macros.
(wait_objects_context, wait_objects_pool, wait_objects_info):
New structures for managing the thread pool.
(wait_objects_pool, wait_objects_info): New static variables for
managing the thread pool.
(wait_objects_thread, start_wait_objects, stop_wait_objects)
(end_wait_and_return, shrink_wait_pool, free_wait_pool):
New functions for waiting and managing the thread pool.
(wait_for_objects, msg_wait_for_objects): New functions as
replacements for WaitForMultipleObjects and
MsgWaitForMultipleObjects.
(wait_pid): Use 'wait_for_objects' and new macros.
(sys_select): Make some variables static to avoid stack
allocation.  Use 'wait_for_objects', 'msg_wait_for_objects', and
the new macros.
This commit is contained in:
Yue Yi 2025-03-11 00:30:40 +08:00 committed by Eli Zaretskii
parent 5d4056c765
commit e02466a579
3 changed files with 812 additions and 30 deletions

View file

@ -10451,9 +10451,10 @@ check_windows_init_file (void)
}
}
/* from w32fns.c */
/* from w32fns.c */
extern void remove_w32_kbdhook (void);
/* from w32proc.c */
extern void free_wait_pool (void);
void
term_ntproc (int ignored)
{
@ -10463,11 +10464,12 @@ term_ntproc (int ignored)
term_timers ();
/* shutdown the socket interface if necessary */
/* shutdown the socket interface if necessary. */
term_winsock ();
term_w32select ();
/* exit all worker threads of sys_select if necessary. */
free_wait_pool ();
#if HAVE_NATIVE_IMAGE_API
w32_gdiplus_shutdown ();
#endif

View file

@ -29,7 +29,11 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
/* File descriptor set emulation. */
/* MSVC runtime library has limit of 64 descriptors by default */
#define FD_SETSIZE 64
#ifdef FD_SETSIZE
# undef FD_SETSIZE
#endif
#define FD_SETSIZE 2048
typedef struct {
unsigned int bits[FD_SETSIZE / 32];
} fd_set;

View file

@ -881,7 +881,785 @@ alarm (int seconds)
}
/* Here's an overview of how support for waiting more than 64 objects
on MS-Windows.
As noted in the MS documentation, WaitForMultipleObjects can wait on
a maximum of MAXIMUM_WAIT_OBJECTS (64) objects. Due to this
limitation, earlier versions of Emacs (at least 30) could only
support up to 32 subprocesses or network connections.
The documentation suggests using multiple threads or a thread pool to
wait on a larger number of objects. With Windows 2000, Microsoft has
added new thread pooling functions to make thread creation,
destruction, and general management easier:
. https://jacobfilipp.com/MSJ/pooling.html
Thread pools implement waiting by calling WaitForMultipleObjects or
relying on completion ports (Windows 8 or above) in their internal
threads, as The Old New Thing said:
. https://devblogs.microsoft.com/oldnewthing/20081117-00/?p=20183
. https://devblogs.microsoft.com/oldnewthing/20220406-00/?p=106434
However, since the system thread pool in versions prior to Windows 7
does not support adjusting thread stack sizes by calling
SetThreadpoolStackInformation, using a large number of threads may
consume a lot of address space. This can have a significant impact on
the limited 2GB address space available on 32-bit systems. To avoid
this issue on Windows Vista and earlier systems and make the code as
generic as possible, a simple waiting thread pool is manually
implemented here.
The waiting thread pool contains a maximum of 32 threads, with each
thread capable of waiting on up to 63 objects (one object is reserved
for communication with the main thread). Combined with the main
thread's WaitForMultipleObjects call, this implementation supports
waiting on a maximum of 2048 objects. Since Windows only supports
creating subprocesses using 'pipe method, this allows Emacs to create
a maximum of approximately 1024 subprocesses. This limit is close to
the number of subprocesses that can be created using the 'pty method
on Linux when the default FD_SETSIZE is 1024.
Once created, the threads do not exit after finish waiting; instead,
they enter an infinite loop, waiting for an event to trigger and
begin their WaitForMultipleObjects tasks, thereby eliminating the
additional time and power consumption associated with frequently
creating and destroying threads. If a thread is not triggered after a
certain number of sys_select calls, it can be terminated to free up
resources. */
/* The values returned by WaitForMultipleObjects, WAIT_ABANDONED_0
(0x80) and WAIT_TIMEOUT (0x102), are less than 2048 (0x800), so
define new constants to replace them. 'WFO' prefix stands for
'WaitForObjects'. */
#define WFO_ABANDONED 0x10000
#define WFO_TIMEOUT 0x20002
#define WFO_FAILED 0xfffff
#define WFO_MAX_WAIT FD_SETSIZE
/* When debugging, use SetThreadDescription to provide additional
debugging information. They are already defined in w32fns.c. */
typedef BOOL (WINAPI *IsDebuggerPresent_Proc) (void);
typedef HRESULT (WINAPI *SetThreadDescription_Proc)
(HANDLE hThread, PCWSTR lpThreadDescription);
extern IsDebuggerPresent_Proc is_debugger_present;
extern SetThreadDescription_Proc set_thread_description;
/* Structure for waiting thread's context. */
typedef struct
{
/* Handle to the thread itself. */
HANDLE thread;
/* Handle to an event object that is signaled by the main thread
to tell thread start waiting. */
HANDLE wait_start;
/* Handle to an event object that is signaled when this thread is not
used for a period of time, it tells thread to quit. */
HANDLE wait_quit;
/* Handle to an event object that is signaled when wokrer thread is
ready to call WaitForMultipleObjects on the given objects. */
HANDLE wait_ready;
/* pHandles and nCount are part of the WaitForMultipleObjects
parameters, specifying the array of objects to wait on and the
number of objects. bWaitAll and dwMilliseconds are not needed. */
HANDLE *pHandles;
int nCount;
/* The return value of the thread's call to WaitForMultipleObjects. */
DWORD result;
/* Used to store GetLastError value of failed wait. */
DWORD errcode;
/* The thread's wait count. */
int call_count;
} wait_objects_context;
/* Structure for the waiting thread pool */
typedef struct
{
/* An array for the context of each worker thread. */
wait_objects_context wocs[32];
/* The number of the current threads for waiting. */
int count;
/* times of waiting in main thread. */
int main_thread_waits;
/* The event handle that signals the worker thread's timeout. */
HANDLE timeout_event;
/* The flag that indicates whether the thread pool is initialized. */
int init_flag;
} wait_objects_pool;
/* Structure for information about the grouping of waits. */
typedef struct
{
/* An array of handles used as parameters for WaitForMultipleObjects
in the main thread, which includes event handles wait_ready
signaled by the worker threads, as well as any possible remaining
wait objects. */
HANDLE hs[64];
/* The number of threads to be used. */
int nt;
/* The number of handles the main thread needs to wait for. */
int nh;
} wait_objects_info;
/* Declare wait_pool and wait_info as static variables to avoid
unnecessary stack allocation. */
static wait_objects_pool wait_pool;
static wait_objects_info wait_info;
/* Thread proc for worker threads waiting on objects. Each thread is
normally blocked until woken by the main thread to wait on objects.
When the wait completes, wait_ready is signaled to wake up the main
thread and the thread blocks itself again.
The worker thread needs to wait for the timeout event from the main
thread. When timeout_event is signaled by the main thread, it will
end the current wait and start the next round. Similarly, threads
also need to wait for an quit event, which usually occurs when the
thread has not been used for a certain period of time. */
static DWORD WINAPI
wait_objects_thread (void *arg)
{
wait_objects_context *ctx = NULL;
/* Thread's wait handle array. */
HANDLE hs[64] = {NULL};
/* start and quit event handle array. */
HANDLE start_or_quit[2] = {NULL};
/* return value of waiting. */
DWORD res = 0;
/* Thread's context. */
ctx = (wait_objects_context *) arg;
/* Place start event in start_or_quit[0]. */
start_or_quit[0] = ctx->wait_start;
/* Place quit event in start_or_quit[1]. */
start_or_quit[1] = ctx->wait_quit;
for (;;)
{
/* increase thread's wait call count by 1. */
ctx->call_count++;
/* The timeout event object is placed at the end.
nCount will not exceed 63. */
hs[ctx->nCount] = wait_pool.timeout_event;
/* The contents copied by different threads do not overlap, so it
is safe to use memcpy. */
memcpy (hs, ctx->pHandles, ctx->nCount * sizeof (HANDLE));
/* Start waiting. */
ctx->result = WaitForMultipleObjects (ctx->nCount + 1, hs,
FALSE, INFINITE);
/* Get the error code when the wait fails. */
if (ctx->result == WAIT_FAILED)
{
ctx->errcode = GetLastError ();
SetEvent (ctx->wait_ready);
return WFO_FAILED;
}
/* After waiting, signal wait_ready and wait for the wait_start
and wait_quit events from the main thread. */
if (!SetEvent (ctx->wait_ready))
{
ctx->errcode = GetLastError ();
return WFO_FAILED;
}
res = WaitForMultipleObjects (2, start_or_quit, FALSE, INFINITE);
switch (res)
{
/* wait_start, continue loop. */
case WAIT_OBJECT_0:
break;
/* wait_quit, exit with code 0. */
case WAIT_OBJECT_0 + 1:
/* Close its wait_quit event handle. */
if (!CloseHandle (start_or_quit[1]))
return GetLastError ();
return 0;
/* Failure to wait for the start and quit event from the main
thread should not occur. */
case WAIT_ABANDONED_0:
case WAIT_ABANDONED_0 + 1:
case WAIT_TIMEOUT:
case WAIT_FAILED:
ctx->errcode = GetLastError ();
return WFO_FAILED;
}
}
return 0;
}
/* Determine the grouping based on the given wait objects and assign
them to the worker threads to begin waiting, creating new worker
threads if necessary. The function also performs initialization of
some static resources. */
static int
start_wait_objects (wait_objects_info *p,
DWORD nCount,
HANDLE *lpHandles)
{
/* Initialization of static resources. */
if (!wait_pool.init_flag)
{
wait_pool.timeout_event = CreateEvent (NULL, TRUE, FALSE, NULL);
/* If resource initialization fails, exit immediately. */
if (!wait_pool.timeout_event)
return WFO_FAILED;
/* Set init_flag. */
wait_pool.init_flag = TRUE;
}
/* Check if all threads in the thread pool are working properly. If
any thread has exited, exit immediately. */
for (int i = 0; i < wait_pool.count; i++)
{
/* The MS documentation suggests that callers should call the
GetExitCodeThread function only after the thread has been
confirmed to have exited. Use the WaitForSingleObject with a
wait duration of zero to determine whether a thread has
exited. */
DWORD res = WaitForSingleObject (wait_pool.wocs[i].thread, 0);
/* Thread is alive. */
if (res == WAIT_TIMEOUT)
continue;
/* The thread unexpectedly terminated when waiting
wait_start and wait_quit. */
else if (res == WAIT_OBJECT_0)
{
/* The last-error code is kept in thread local storage so that
multiple threads do not overwrite each other's values. Pass
the error by using SetLastError to set the error value. */
SetLastError (wait_pool.wocs[i].errcode);
return WFO_FAILED;
}
/* Unexpected return value from WaitForSingleObject.
WAIT_FAILED or WAIT_ABANDONED. */
else
return WFO_FAILED;
}
/* Calculate the required number of threads and the number of objects
the main thread needs to wait for based on the number of objects to
be waited on. To avoid increasing the number of threads for a small
increase in the number of objects (e.g., using two threads for 65
objects), the following calculation allows the main thread's array
to hold up to 32 wait objects, excluding the thread event
handles. With this approach, the minimum number of wait objects for
the worker threads is 32, and it allows a maximum of 63 * 32 + 32 =
2048 objects to be waited on.
The number of child process in Emacs is limited by MAX_CHILDREN,
which is half of MAXDESC (FD_SETSIZE). When FD_SETSIZE is set to
2048, a maximum of 1024 child processes and network/serial are
allowed. Compared to network/serial, the process handles of child
processes are also waited on, which is why only a maximum of 1024
child processes can be created.
When there are 1024 child processes, the main thread needs to wait
for 64 objects, which seems to exceed the 63 allowed by
MsgWaitForMultipleObjects. However, due to the influence of
emacs_pipe, a maximum of only 1021 child processes can be created,
so the number of elements in the main thread's wait array will not
exceed 63. The explanation is as follows:
. emacs_pipe calls the pipe2 function located in w32.c.
. pipe2 makes sure that file descriptors return by _pipe less than
MAXDESC (2048), Therefore, the range of available file descriptor
values is from 3 to 2047 (0, 1, 2 for stdin, stdout and
stderr). Since pipe file descriptors are opened in pairs, the
actual range is from 3 to 2046, meaning there are 2044 available
file descriptors.
. When create_process creates a process using the 'pipe' method, it
creates two pipes, one for reading and one for writing. It then
closes one file descriptor for each pipe, as each pipe is only
used for reading or writing. This results in 4 file descriptors
being used initially for each process creation, and then 2 are
released. When only 2 empty slots remain, no new child processes
can be created.
. In the end, we can use 2042 file descriptors, which allows for
1021 child processes. */
/* Compared to using (nCount / 63), we use (nCount - 33) / 63, which
will cause a new thread to be added only when the number of wait
objects in the main thread's array exceeds 32. */
p->nt = 1 + (nCount - 33) / 63;
/* The number of objects the main thread needs to wait for is the
required number of threads plus the remaining objects. If the
existing threads are sufficient to wait for all the objects, then
nh is the number of threads. */
p->nh = (p->nt * 63 >= nCount) ? p->nt : (p->nt + nCount - p->nt * 63);
/* In the main thread's wait array hs, the first nt are thread event
handles, and the remaining are the remaining objects. */
if (p->nh != p->nt)
memcpy (p->hs + p->nt, lpHandles + p->nt * 63,
sizeof (HANDLE) * (nCount - p->nt * 63));
/* Set the pHandles and nCount parameters for each thread. Since the
waiting task is relatively simple, we do not need a dedicated
task queue mechanism. We can simply wait for the corresponding
objects in the order of the thread context array wait_pool.wocs. */
for (int i = 0; i < p->nt; i++)
{
/* If it is the last thread and the thread is sufficient to wait
all the objects, then the count needs to be calculated;
otherwise, it will be 63. */
int count = (i == p->nt - 1)
? (p->nt == p->nh) ? (nCount - i * 63) : 63 : 63;
wait_pool.wocs[i].nCount = count;
wait_pool.wocs[i].pHandles = lpHandles + i * 63;
}
/* Get current threads count. */
int orig_thread_count = wait_pool.count;
/* If the current thread pool is insufficient to wait for all the
objects, create new threads and initialize the event handles. */
while (wait_pool.count < p->nt)
{
wait_objects_context *ctx = wait_pool.wocs + wait_pool.count;
/* Create wait_start, wait_quit and wait_ready events. */
ctx->wait_start = CreateEvent (NULL, FALSE, FALSE, NULL);
if (!ctx->wait_start)
return WFO_FAILED;
ctx->wait_quit = CreateEvent (NULL, FALSE, FALSE, NULL);
if (!ctx->wait_quit)
return WFO_FAILED;
/* The wait_ready event is set to be manually reset because it may
be waited on multiple times. */
ctx->wait_ready = CreateEvent (NULL, TRUE, FALSE, NULL);
if (!ctx->wait_ready)
return WFO_FAILED;
/* Set call_count and errcode to ZERO. */
ctx->call_count = 0;
ctx->errcode = 0;
/* Creating new worker threads. Please refer to the comment in
w32proc.c within the new_child function, where the
reader_thread thread is created, to understand why these
parameters are needed. */
ctx->thread = CreateThread (NULL, 64 * 1024, wait_objects_thread,
ctx, 0x00010000, NULL);
/* CreateThread failed. */
if (!ctx->thread)
return WFO_FAILED;
/* Set Thread Description information. This may be handy with
debugging. */
if (is_debugger_present && is_debugger_present ()
&& set_thread_description)
{
if (set_thread_description
(ctx->thread, L"sys_select_worker_thread") != S_OK)
return WFO_FAILED;
}
wait_pool.count++;
}
/* Fill the main thread's wait array with the wait_ready event
handles of the required worker threads, and set them to be
unsignaled. */
for (int i = 0; i < p->nt; i++)
{
p->hs[i] = wait_pool.wocs[i].wait_ready;
/* Reset the wait_ready event and set wait_start event for threads
that are not newly created. Newly created threads start
execution immediately. */
if (i < orig_thread_count)
{
if (ResetEvent (wait_pool.wocs[i].wait_ready)
&& SetEvent (wait_pool.wocs[i].wait_start))
continue;
/* SetEvent or ResetEvent failed. */
else
return WFO_FAILED;
}
}
return 0;
}
/* Ensure that all worker threads have completed their wait and are in
the ready state. */
static int
stop_wait_objects (wait_objects_info *p)
{
/* Set timeout_event to tell all worker threads stop waiting. */
if (!SetEvent (wait_pool.timeout_event))
return WFO_FAILED;
/* Wait for all the used worker threads to signal wait_ready. This
typically takes no more than a few dozen microseconds, so a
timeout indicates that an error has occurred. */
DWORD result = WaitForMultipleObjects (p->nt, p->hs, TRUE, 20);
if (result == WAIT_FAILED || result == WAIT_ABANDONED)
return WFO_FAILED;
/* Timeout means there exists a thread exit abnormally. Since the
WAIT_FAILED error in the worker threads signals wait_ready, this
can only be an error occurring when the worker threads are waiting
for curr_start and wait_quit. */
if (result == WAIT_TIMEOUT)
{
/* Find the first dead thread. */
for (int i = 0; i < p->nt; i++)
{
if (WaitForSingleObject (wait_pool.wocs[i].thread, 0) == WAIT_OBJECT_0)
{
SetLastError (wait_pool.wocs[i].errcode);
return WFO_FAILED;
}
}
return WFO_FAILED;
}
/* Even if the wait succeeds, there is still a possibility that some
wait_ready might be signaled by a WAIT_FAILED error. To determine
if no WAIT_FAILED error occurred, check the errcode of all threads. */
for (int i = 0; i < p->nt; i++)
{
if (wait_pool.wocs[i].errcode)
{
SetLastError (wait_pool.wocs[i].errcode);
return WFO_FAILED;
}
}
/* Reset the timeout event. */
if (!ResetEvent (wait_pool.timeout_event))
return WFO_FAILED;
return 0;
}
/* After the main thread finishes waiting, obtain the signaled object
index based on the main thread's return value, or other possible
return values. */
static DWORD
end_wait_and_return (wait_objects_info *p, DWORD result)
{
/* Wait timeout in main thread. */
if (result == WAIT_TIMEOUT)
result = WFO_TIMEOUT;
/* Wait failed in main thread. */
else if (result == WAIT_FAILED)
return WFO_FAILED;
/* It seems that all the wait objects are just process handles and
event handles. WAIT_ABANDONED may not occur, but let's just
handle it here. */
else if (result >= WAIT_ABANDONED_0
&& result < p->nh + WAIT_ABANDONED_0)
{
result -= WAIT_ABANDONED_0;
/* The event object wait_ready is not mutex object. This is
unlikely to happen... */
if (result < p->nt)
return WFO_FAILED;
/* There are 62 * nt objects before the objects in the main
thread. Each thread can wait for 63 objects, and each
thread's wait_ready occupies one position in the main thread
array. Therefore, the index of the waiting object in the main
thread array should be incremented by (63 - 1) multiplied by
the number of worker threads. We add WFO_ABANDONED to
distinguish it from normal object index. */
result = result + 62 * p->nt + WFO_ABANDONED;
}
/* Wait succeed in main thread. */
else
{
/* Object index in main thread wait array. */
int idx = result - WAIT_OBJECT_0;
/* Object is in main thread's wait array. */
if (idx >= p->nt)
{
/* When the index is equal to the number of worker threads,
and the number of worker threads is equal to the number of
wait objects for the main thread, it indicates that the main
thread is using MsgWaitForMultipleObjects and that there are
no wait objects in the main thread's array. We should return
the total number of wait objects to indicate that a message
was received during the wait. */
if (p->nt == p->nh)
result = (idx - 1) * 63 + wait_pool.wocs[idx - 1].nCount;
/* Normal case. */
else
result = 62 * p->nt + idx;
}
/* Object is in worker threads. */
else
{
DWORD t_result = wait_pool.wocs[idx].result;
/* WAIT_FAILED or WAIT_TIMEOUT. Since the wait time in the
worker thread is set to INFINITE, WAIT_TIMEOUT should not
occur. */
if (t_result == WAIT_FAILED || t_result == WAIT_TIMEOUT)
{
SetLastError (wait_pool.wocs[idx].errcode);
return WFO_FAILED;
}
/* Abandoned. Compared to the waiting objects in the main
thread's array, the index in the worker threads needs to be
incremented by 63 times the number of preceding threads,
rather than 62. */
else if (t_result >= WAIT_ABANDONED_0
&& t_result < wait_pool.wocs[idx].nCount
+ WAIT_ABANDONED_0)
result = idx * 63 + t_result
+ WFO_ABANDONED - WAIT_ABANDONED_0;
/* The worker thread is signaled by timeout_event, but at this
point, timeout_event has not yet been signaled. */
else if (t_result == wait_pool.wocs[idx].nCount + WAIT_OBJECT_0)
return WFO_FAILED;
/* Normal object index. */
else
result = idx * 63 + (t_result - WAIT_OBJECT_0);
}
}
/* Ensure that all the worker threads are ready to begin the next
round of waiting, and check for any potential errors. */
if (stop_wait_objects (&wait_info) == WFO_FAILED)
return WFO_FAILED;
return result;
}
/* Check if there are inactive worker threads in the waiting thread
pool and let them exit. */
static int
shrink_wait_pool (void)
{
wait_pool.main_thread_waits++;
/* Check the thread every 64 Wait call. 64 is just a value that might
be reasonably suitable. */
if (wait_pool.main_thread_waits <= 64)
return 0;
wait_pool.main_thread_waits = 0;
/* return if no thread. */
if (wait_pool.count == 0)
return 0;
/* Each time, we only check the last worker thread, which helps avoid
terminating a large number of threads at the same time. */
int last = wait_pool.count - 1;
/* A call count of 0 indicates that the thread has not been used
during the past period of time. */
if (wait_pool.wocs[last].call_count == 0)
{
/* Signal wait_quit to let the worker thread exit. */
if (!SetEvent (wait_pool.wocs[last].wait_quit))
return WFO_FAILED;
wait_pool.wocs[last].wait_quit = NULL;
/* Close thread handle. */
if (!CloseHandle (wait_pool.wocs[last].thread))
return WFO_FAILED;
wait_pool.wocs[last].thread = NULL;
/* Close wait_start event handle. */
if (!CloseHandle (wait_pool.wocs[last].wait_start))
return WFO_FAILED;
wait_pool.wocs[last].wait_start = NULL;
/* Close wait_ready event handle */
if (!CloseHandle (wait_pool.wocs[last].wait_ready))
return WFO_FAILED;
wait_pool.wocs[last].wait_ready = NULL;
/* decrease the number of workder thread by 1. */
wait_pool.count--;
}
/* Reset call_count for each worker thread. */
for (int i = 0; i <= last; i++)
wait_pool.wocs[i].call_count = 0;
return 0;
}
/* Exit all worker threads and release the start and timeout
handles. This function is called when exiting Emacs. */
void
free_wait_pool (void)
{
/* Emacs has never used more than 32 child processes. */
if (wait_pool.init_flag == FALSE)
return;
for (int i = 0; i < wait_pool.count; i++)
{
/* Send quit event to earch worker thread. */
SetEvent (wait_pool.wocs[i].wait_quit);
/* Close wait_start, wait_ready event. */
CloseHandle (wait_pool.wocs[i].wait_start);
CloseHandle (wait_pool.wocs[i].wait_ready);
}
/* Wait all workder threads exit. Stop if failed. */
for (int i = 0; i < wait_pool.count; i++)
{
if (WaitForSingleObject (wait_pool.wocs[i].thread, 1)
!= WAIT_OBJECT_0)
break;
}
/* Close timeout event. */
CloseHandle (wait_pool.timeout_event);
return;
}
/* Replacement of WaitForMultipleObjects, with the bWaitAll parameter
removed. The function's return values are as follows:
[0 ~ nCount-1], the return value indicates the lpHandles array
index of the object that satisfied the wait.
[WFO_ABANDONED ~ nCount-1 + WFO_ABANDONED], the return value minus
WFO_ABANDONED indicates the lpHandles array index of an abandoned
mutex object that satisfied the wait.
[WFO_TIMEOUT], The time-out interval elapsed.
[WFO_FAILED], The function has failed. To get extended error
information, call GetLastError. */
static DWORD
wait_for_objects (DWORD nCount, HANDLE *lpHandles,
DWORD dwMilliseconds)
{
/* Check inactive worker threads and terminate them. */
if (shrink_wait_pool () == WFO_FAILED)
return WFO_FAILED;
/* If the number of wait objects does not exceed 64, directly call
WaitForMultipleObjects and convert the return value. */
if (nCount <= 64)
{
DWORD res = WaitForMultipleObjects (nCount, lpHandles, FALSE,
dwMilliseconds);
if (res == WAIT_TIMEOUT)
return WFO_TIMEOUT;
else if (res >= WAIT_OBJECT_0
&& res < WAIT_OBJECT_0 + nCount)
return res - WAIT_OBJECT_0;
else if (res >= WAIT_ABANDONED_0
&& res < WAIT_ABANDONED_0 + nCount)
return res + WFO_ABANDONED - WAIT_ABANDONED_0;
else
return WFO_FAILED;
}
/* If the wait time is 0, perform busy waiting. */
if (dwMilliseconds == 0)
{
int rest = nCount % 64;
int group = nCount / 64;
DWORD res, count;
for (int i = 0, offset = 0; i <= group; i++, offset += 64)
{
count = (i == group) ? rest : 64;
/* When the number of waits is a multiple of 64, skipping the
last wait with a count of 0. */
if (count == 0)
break;
res = WaitForMultipleObjects (count, lpHandles + offset,
FALSE, 0);
if (res == WAIT_TIMEOUT)
continue;
else if (res >= WAIT_OBJECT_0
&& res < WAIT_OBJECT_0 + count)
return offset + res - WAIT_OBJECT_0;
else if (res >= WAIT_ABANDONED_0
&& res < WAIT_ABANDONED_0 + count)
return offset + res + WFO_ABANDONED - WAIT_ABANDONED_0;
else
return WFO_FAILED;
}
return WFO_TIMEOUT;
}
/* If the number of objects is greater than 64 and the wait time is
not 0, use multithreaded waiting. */
if (start_wait_objects (&wait_info, nCount, lpHandles) == WFO_FAILED)
return WFO_FAILED;
DWORD res = WaitForMultipleObjects (wait_info.nh, wait_info.hs,
FALSE, dwMilliseconds);
/* If the main thread wait times out, call WaitForMultipleObjects
again with zero time-out interval to check if any objects have
completed. The default clock resolution of Windows is 64 Hz. If a
time less than 15.625ms is specified, the waiting time may be
longer than the specified time, and some objects that were not
completed in the previous call may now be completed. */
if (res == WAIT_TIMEOUT)
{
res = WaitForMultipleObjects (wait_info.nh, wait_info.hs,
FALSE, 0);
}
return end_wait_and_return (&wait_info, res);
}
/* Replacement of MsgWaitForMultipleObjects, with the bWaitAll and
dwWakeMask parameters removed. The function's return values are as
follows:
[0 ~ nCount-1], the return value indicates the lpHandles array
index of the object that satisfied the wait.
[nCount], New input of the type QS_ALLINPUT is available in the
thread's input queue.
[WFO_ABANDONED ~ nCount-1 + WFO_ABANDONED], the return value minus
WFO_ABANDONED indicates the lpHandles array index of an abandoned
mutex object that satisfied the wait.
[WFO_TIMEOUT], The time-out interval elapsed.
[WFO_FAILED], The function has failed. To get extended error
information, call GetLastError. */
static DWORD
msg_wait_for_objects (DWORD nCount, HANDLE *lpHandles,
DWORD dwMilliseconds)
{
/* Check inactive worker threads and terminate them. */
if (shrink_wait_pool () == WFO_FAILED)
return WFO_FAILED;
/* If the number of wait objects does not exceed 63, directly call
MsgWaitForMultipleObjects and convert the return value. */
if (nCount <= 63)
{
DWORD res = MsgWaitForMultipleObjects (nCount, lpHandles, FALSE,
dwMilliseconds, QS_ALLINPUT);
if (res == WAIT_TIMEOUT)
return WFO_TIMEOUT;
/* The return value of MsgWaitForMultipleObjects can be
WAIT_OBJECT_0 + nCount, indicating that a message was
received. So use (<=) rather than (<) here. */
else if (res >= WAIT_OBJECT_0
&& res <= WAIT_OBJECT_0 + nCount)
return res - WAIT_OBJECT_0;
else if (res >= WAIT_ABANDONED_0
&& res < WAIT_ABANDONED_0 + nCount)
return res + WFO_ABANDONED - WAIT_ABANDONED_0;
else
return WFO_FAILED;
}
/* If the wait time is 0, perform busy waiting. */
if (dwMilliseconds == 0)
{
int rest = nCount % 63;
int group = nCount / 63;
DWORD res, count;
for (int i = 0, offset = 0; i <= group; i++, offset += 63)
{
count = i == group ? rest : 63;
/* When the number of waits is a multiple of 63, skipping the
last wait with a count of 0. */
if (count == 0)
break;
res = MsgWaitForMultipleObjects (count, lpHandles + offset,
FALSE, 0, QS_ALLINPUT);
if (res == WAIT_TIMEOUT)
continue;
else if (res >= WAIT_OBJECT_0
&& res < WAIT_OBJECT_0 + count)
return offset + res - WAIT_OBJECT_0;
/* When a message is received during the wait, return nCount
directly. This is the distinction that needs to be made
compared to wait_for_objects. */
else if (res == WAIT_OBJECT_0 + count)
return nCount;
else if (res >= WAIT_ABANDONED_0
&& res < WAIT_ABANDONED_0 + count)
return offset + res + WFO_ABANDONED - WAIT_ABANDONED_0;
else
return WFO_FAILED;
}
return WFO_TIMEOUT;
}
/* If the number of objects is greater than 63 and the wait time is
not 0, use multithreaded waiting. */
if (start_wait_objects (&wait_info, nCount, lpHandles) == WFO_FAILED)
return WFO_FAILED;
DWORD res = MsgWaitForMultipleObjects (wait_info.nh, wait_info.hs,
FALSE, dwMilliseconds,
QS_ALLINPUT);
/* If the main thread wait times out, call MsgWaitForMultipleObjects
again with zero time-out interval to check if any objects have
completed. */
if (res == WAIT_TIMEOUT)
{
res = MsgWaitForMultipleObjects (wait_info.nh, wait_info.hs,
FALSE, 0, QS_ALLINPUT);
}
return end_wait_and_return (&wait_info, res);
}
/* Here's an overview of how support for subprocesses and
network/serial streams is implemented on MS-Windows.
@ -1566,15 +2344,15 @@ waitpid (pid_t pid, int *status, int options)
quitting in that case. */
if (!dont_wait)
maybe_quit ();
active = WaitForMultipleObjects (nh, wait_hnd, FALSE, timeout_ms);
} while (active == WAIT_TIMEOUT && !dont_wait);
active = wait_for_objects (nh, wait_hnd, timeout_ms);
} while (active == WFO_TIMEOUT && !dont_wait);
if (active == WAIT_FAILED)
if (active == WFO_FAILED)
{
errno = EBADF;
return -1;
}
else if (active == WAIT_TIMEOUT && dont_wait)
else if (active == WFO_TIMEOUT && dont_wait)
{
/* PID specifies our subprocess, but it didn't exit yet, so its
status is not yet available. */
@ -1583,15 +2361,14 @@ waitpid (pid_t pid, int *status, int options)
#endif
return 0;
}
else if (active >= WAIT_OBJECT_0
&& active < WAIT_OBJECT_0+MAXIMUM_WAIT_OBJECTS)
else if (active >= 0 && active < WFO_MAX_WAIT)
{
active -= WAIT_OBJECT_0;
;
}
else if (active >= WAIT_ABANDONED_0
&& active < WAIT_ABANDONED_0+MAXIMUM_WAIT_OBJECTS)
else if (active >= WFO_ABANDONED
&& active < WFO_ABANDONED + WFO_MAX_WAIT)
{
active -= WAIT_ABANDONED_0;
active -= WFO_ABANDONED;
}
else
emacs_abort ();
@ -2302,13 +3079,14 @@ int
sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds,
const struct timespec *timeout, const sigset_t *ignored)
{
SELECT_TYPE orfds, owfds;
static SELECT_TYPE orfds, owfds;
static child_process *cps[MAX_CHILDREN];
static HANDLE wait_hnd[MAXDESC + MAX_CHILDREN];
static int fdindex[MAXDESC]; /* mapping from wait handles back to descriptors */
DWORD timeout_ms, start_time;
int i, nh, nc, nr;
DWORD active;
child_process *cp, *cps[MAX_CHILDREN];
HANDLE wait_hnd[MAXDESC + MAX_CHILDREN];
int fdindex[MAXDESC]; /* mapping from wait handles back to descriptors */
child_process *cp;
timeout_ms =
timeout ? (timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000) : INFINITE;
@ -2501,12 +3279,11 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds,
/* Wait for input or child death to be signaled. If user input is
allowed, then also accept window messages. */
if (FD_ISSET (0, &orfds))
active = MsgWaitForMultipleObjects (nh + nc, wait_hnd, FALSE, timeout_ms,
QS_ALLINPUT);
active = msg_wait_for_objects (nh + nc, wait_hnd, timeout_ms);
else
active = WaitForMultipleObjects (nh + nc, wait_hnd, FALSE, timeout_ms);
active = wait_for_objects (nh + nc, wait_hnd, timeout_ms);
if (active == WAIT_FAILED)
if (active == WFO_FAILED)
{
DebPrint (("select.WaitForMultipleObjects (%d, %lu) failed with %lu\n",
nh + nc, timeout_ms, GetLastError ()));
@ -2517,7 +3294,7 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds,
errno = EINTR;
return -1;
}
else if (active == WAIT_TIMEOUT)
else if (active == WFO_TIMEOUT)
{
if (noninteractive)
{
@ -2526,15 +3303,14 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds,
}
return 0;
}
else if (active >= WAIT_OBJECT_0
&& active < WAIT_OBJECT_0+MAXIMUM_WAIT_OBJECTS)
else if (active >= 0 && active < WFO_MAX_WAIT)
{
active -= WAIT_OBJECT_0;
;
}
else if (active >= WAIT_ABANDONED_0
&& active < WAIT_ABANDONED_0+MAXIMUM_WAIT_OBJECTS)
else if (active >= WFO_ABANDONED
&& active < WFO_ABANDONED + WFO_MAX_WAIT)
{
active -= WAIT_ABANDONED_0;
active -= WFO_ABANDONED;
}
else
emacs_abort ();