Cygwin: sigproc: Allow more child processes per process

256 children per process is a bit tight in some scenarios.

Fix this by revamping the `procs' array.  Convert it to an
extensible class child_procs and rename procs to chld_procs.
Fix code throughout to use matching class methods rather than
direct access.

To allow a lot more child processes while trying to avoid
allocations at DLL startup, maintain two arrays within class
child_procs, one using a default size for 255 (i686) or 1023
(x86_64) children, the other, dynamically allocated on overflowing
the first array, giving room for another 1023 (i686) or 4095
(x86_64) processes.

On testing with a simple reproducer on a x86_64 machine with
4 Gigs RAM, a system memory overflow occured after forking
about 1450 child processes, so this simple dynamic should
suffice for a while.

Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
This commit is contained in:
Corinna Vinschen 2020-08-28 15:22:58 +02:00
parent 163daed37c
commit 7c963c7ba0
2 changed files with 93 additions and 54 deletions

View File

@ -39,8 +39,6 @@ enum child_status
/* Change this value if you get a message indicating that it is out-of-sync. */ /* Change this value if you get a message indicating that it is out-of-sync. */
#define CURR_CHILD_INFO_MAGIC 0xecc930b9U #define CURR_CHILD_INFO_MAGIC 0xecc930b9U
#define NPROCS 256
#include "pinfo.h" #include "pinfo.h"
struct cchildren struct cchildren
{ {

View File

@ -38,19 +38,55 @@ int __sp_ln;
bool no_thread_exit_protect::flag; bool no_thread_exit_protect::flag;
char NO_COPY myself_nowait_dummy[1] = {'0'};// Flag to sig_send that signal goes to /* Flag to sig_send that signal goes to current process but no wait is
// current process but no wait is required required. */
char NO_COPY myself_nowait_dummy[1] = {'0'};
#define Static static NO_COPY #define Static static NO_COPY
/* All my children info. Avoid expensive constructor ops at DLL startup */
class child_procs {
#ifdef __i386__
static const int _NPROCS = 255;
static const int _NPROCS_2 = 1023;
#else
static const int _NPROCS = 1023;
static const int _NPROCS_2 = 4095;
#endif
int _count;
uint8_t _procs[(_NPROCS + 1) * sizeof (pinfo)] __attribute__ ((__aligned__));
pinfo *_procs_2;
public:
int count () const { return _count; }
int add_one () { return ++_count; }
int del_one () { return --_count; }
int reset () { return _count = 0; }
pinfo &operator[] (int idx)
{
if (idx >= _NPROCS)
{
if (!_procs_2)
{
/* Use HeapAlloc to avoid propagating this memory area
to the child processes. */
_procs_2 = (pinfo *) HeapAlloc (GetProcessHeap (),
HEAP_GENERATE_EXCEPTIONS
| HEAP_ZERO_MEMORY,
(_NPROCS_2 + 1) * sizeof (pinfo));
}
return _procs_2[idx - _NPROCS];
}
return ((pinfo *) _procs)[idx];
}
int max_child_procs () const { return _NPROCS + _NPROCS_2; }
};
Static child_procs chld_procs;
Static int nprocs; // Number of deceased children /* Start of queue for waiting threads. */
Static char cprocs[(NPROCS + 1) * sizeof (pinfo)];// All my children info Static waitq waitq_head;
#define procs ((pinfo *) cprocs) // All this just to avoid expensive
// constructor operation at DLL startup
Static waitq waitq_head; // Start of queue for wait'ing threads
Static muto sync_proc_subproc; // Control access to subproc stuff /* Controls access to subproc stuff. */
Static muto sync_proc_subproc;
_cygtls NO_COPY *_sig_tls; _cygtls NO_COPY *_sig_tls;
@ -163,8 +199,8 @@ pid_exists (pid_t pid)
static inline bool static inline bool
mychild (int pid) mychild (int pid)
{ {
for (int i = 0; i < nprocs; i++) for (int i = 0; i < chld_procs.count (); i++)
if (procs[i]->pid == pid) if (chld_procs[i]->pid == pid)
return true; return true;
return false; return false;
} }
@ -174,6 +210,7 @@ mychild (int pid)
int __reg2 int __reg2
proc_subproc (DWORD what, uintptr_t val) proc_subproc (DWORD what, uintptr_t val)
{ {
int slot;
int rc = 1; int rc = 1;
int potential_match; int potential_match;
int clearing; int clearing;
@ -197,14 +234,15 @@ proc_subproc (DWORD what, uintptr_t val)
*/ */
case PROC_ADD_CHILD: case PROC_ADD_CHILD:
/* Filled up process table? */ /* Filled up process table? */
if (nprocs >= NPROCS) if (chld_procs.count () >= chld_procs.max_child_procs ())
{ {
sigproc_printf ("proc table overflow: hit %d processes, pid %d\n", sigproc_printf ("proc table overflow: hit %d processes, pid %d\n",
nprocs, vchild->pid); chld_procs.count (), vchild->pid);
rc = 0; rc = 0;
set_errno (EAGAIN); set_errno (EAGAIN);
break; break;
} }
if (vchild != myself) if (vchild != myself)
{ {
vchild->uid = myself->uid; vchild->uid = myself->uid;
@ -219,13 +257,14 @@ proc_subproc (DWORD what, uintptr_t val)
break; break;
case PROC_ATTACH_CHILD: case PROC_ATTACH_CHILD:
procs[nprocs] = vchild; slot = chld_procs.count ();
rc = procs[nprocs].wait (); chld_procs[slot] = vchild;
rc = chld_procs[slot].wait ();
if (rc) if (rc)
{ {
sigproc_printf ("added pid %d to proc table, slot %d", vchild->pid, sigproc_printf ("added pid %d to proc table, slot %d", vchild->pid,
nprocs); slot);
nprocs++; chld_procs.add_one ();
} }
break; break;
@ -260,7 +299,7 @@ proc_subproc (DWORD what, uintptr_t val)
goto scan_wait; goto scan_wait;
case PROC_EXEC_CLEANUP: case PROC_EXEC_CLEANUP:
while (nprocs) while (chld_procs.count ())
remove_proc (0); remove_proc (0);
for (w = &waitq_head; w->next != NULL; w = w->next) for (w = &waitq_head; w->next != NULL; w = w->next)
CloseHandle (w->next->ev); CloseHandle (w->next->ev);
@ -274,7 +313,8 @@ proc_subproc (DWORD what, uintptr_t val)
if (val) if (val)
sigproc_printf ("clear waiting threads"); sigproc_printf ("clear waiting threads");
else else
sigproc_printf ("looking for processes to reap, nprocs %d", nprocs); sigproc_printf ("looking for processes to reap, count %d",
chld_procs.count ());
clearing = val; clearing = val;
scan_wait: scan_wait:
@ -312,7 +352,7 @@ proc_subproc (DWORD what, uintptr_t val)
} }
if (global_sigs[SIGCHLD].sa_handler == (void *) SIG_IGN) if (global_sigs[SIGCHLD].sa_handler == (void *) SIG_IGN)
for (int i = 0; i < nprocs; i += remove_proc (i)) for (int i = 0; i < chld_procs.count (); i += remove_proc (i))
continue; continue;
} }
@ -352,35 +392,35 @@ _cygtls::remove_wq (DWORD wait)
Called on process exit. Called on process exit.
Also called by spawn_guts to disassociate any subprocesses from this Also called by spawn_guts to disassociate any subprocesses from this
process. Subprocesses will then know to clean up after themselves and process. Subprocesses will then know to clean up after themselves and
will not become procs. */ will not become chld_procs. */
void void
proc_terminate () proc_terminate ()
{ {
sigproc_printf ("nprocs %d", nprocs); sigproc_printf ("child_procs count %d", chld_procs.count ());
if (nprocs) if (chld_procs.count ())
{ {
sync_proc_subproc.acquire (WPSP); sync_proc_subproc.acquire (WPSP);
proc_subproc (PROC_CLEARWAIT, 1); proc_subproc (PROC_CLEARWAIT, 1);
/* Clean out proc processes from the pid list. */ /* Clean out proc processes from the pid list. */
for (int i = 0; i < nprocs; i++) for (int i = 0; i < chld_procs.count (); i++)
{ {
/* If we've execed then the execed process will handle setting ppid /* If we've execed then the execed process will handle setting ppid
to 1 iff it is a Cygwin process. */ to 1 iff it is a Cygwin process. */
if (!have_execed || !have_execed_cygwin) if (!have_execed || !have_execed_cygwin)
procs[i]->ppid = 1; chld_procs[i]->ppid = 1;
if (procs[i].wait_thread) if (chld_procs[i].wait_thread)
procs[i].wait_thread->terminate_thread (); chld_procs[i].wait_thread->terminate_thread ();
/* Release memory associated with this process unless it is 'myself'. /* Release memory associated with this process unless it is 'myself'.
'myself' is only in the procs table when we've execed. We reach 'myself' is only in the chld_procs table when we've execed. We
here when the next process has finished initializing but we still reach here when the next process has finished initializing but we
can't free the memory used by 'myself' since it is used later on still can't free the memory used by 'myself' since it is used
during cygwin tear down. */ later on during cygwin tear down. */
if (procs[i] != myself) if (chld_procs[i] != myself)
procs[i].release (); chld_procs[i].release ();
} }
nprocs = 0; chld_procs.reset ();
sync_proc_subproc.release (); sync_proc_subproc.release ();
} }
sigproc_printf ("leaving"); sigproc_printf ("leaving");
@ -866,7 +906,8 @@ cygheap_exec_info::alloc ()
cygheap_exec_info *res = cygheap_exec_info *res =
(cygheap_exec_info *) ccalloc_abort (HEAP_1_EXEC, 1, (cygheap_exec_info *) ccalloc_abort (HEAP_1_EXEC, 1,
sizeof (cygheap_exec_info) sizeof (cygheap_exec_info)
+ (nprocs * sizeof (children[0]))); + (chld_procs.count ()
* sizeof (children[0])));
res->sigmask = _my_tls.sigmask; res->sigmask = _my_tls.sigmask;
return res; return res;
} }
@ -942,10 +983,10 @@ child_info_spawn::cleanup ()
inline void inline void
cygheap_exec_info::record_children () cygheap_exec_info::record_children ()
{ {
for (nchildren = 0; nchildren < nprocs; nchildren++) for (nchildren = 0; nchildren < chld_procs.count (); nchildren++)
{ {
children[nchildren].pid = procs[nchildren]->pid; children[nchildren].pid = chld_procs[nchildren]->pid;
children[nchildren].p = procs[nchildren]; children[nchildren].p = chld_procs[nchildren];
/* Set inheritance of required child handles for reattach_children /* Set inheritance of required child handles for reattach_children
in the about-to-be-execed process. */ in the about-to-be-execed process. */
children[nchildren].p.set_inheritance (true); children[nchildren].p.set_inheritance (true);
@ -1126,13 +1167,13 @@ checkstate (waitq *parent_w)
{ {
int potential_match = 0; int potential_match = 0;
sigproc_printf ("nprocs %d", nprocs); sigproc_printf ("child_procs count %d", chld_procs.count ());
/* Check already dead processes first to see if they match the criteria /* Check already dead processes first to see if they match the criteria
* given in w->next. */ * given in w->next. */
int res; int res;
for (int i = 0; i < nprocs; i++) for (int i = 0; i < chld_procs.count (); i++)
if ((res = stopped_or_terminated (parent_w, procs[i]))) if ((res = stopped_or_terminated (parent_w, chld_procs[i])))
{ {
remove_proc (i); remove_proc (i);
potential_match = 1; potential_match = 1;
@ -1140,40 +1181,40 @@ checkstate (waitq *parent_w)
} }
sigproc_printf ("no matching terminated children found"); sigproc_printf ("no matching terminated children found");
potential_match = -!!nprocs; potential_match = -!!chld_procs.count ();
out: out:
sigproc_printf ("returning %d", potential_match); sigproc_printf ("returning %d", potential_match);
return potential_match; return potential_match;
} }
/* Remove a proc from procs by swapping it with the last child in the list. /* Remove a proc from chld_procs by swapping it with the last child in the list.
Also releases shared memory of exited processes. */ Also releases shared memory of exited processes. */
static int static int
remove_proc (int ci) remove_proc (int ci)
{ {
if (have_execed) if (have_execed)
{ {
if (_my_tls._ctinfo != procs[ci].wait_thread) if (_my_tls._ctinfo != chld_procs[ci].wait_thread)
procs[ci].wait_thread->terminate_thread (); chld_procs[ci].wait_thread->terminate_thread ();
} }
else if (procs[ci] && procs[ci]->exists ()) else if (chld_procs[ci] && chld_procs[ci]->exists ())
return 1; return 1;
sigproc_printf ("removing procs[%d], pid %d, nprocs %d", ci, procs[ci]->pid, sigproc_printf ("removing chld_procs[%d], pid %d, child_procs count %d",
nprocs); ci, chld_procs[ci]->pid, chld_procs.count ());
if (procs[ci] != myself) if (chld_procs[ci] != myself)
procs[ci].release (); chld_procs[ci].release ();
if (ci < --nprocs) if (ci < chld_procs.del_one ())
{ {
/* Wait for proc_waiter thread to make a copy of this element before /* Wait for proc_waiter thread to make a copy of this element before
moving it or it may become confused. The chances are very high that moving it or it may become confused. The chances are very high that
the proc_waiter thread has already done this by the time we the proc_waiter thread has already done this by the time we
get here. */ get here. */
if (!have_execed && !exit_state) if (!have_execed && !exit_state)
while (!procs[nprocs].waiter_ready) while (!chld_procs[chld_procs.count ()].waiter_ready)
yield (); yield ();
procs[ci] = procs[nprocs]; chld_procs[ci] = chld_procs[chld_procs.count ()];
} }
return 0; return 0;
} }