From 7c963c7ba030b9e110eefd6412eff4d6189f29e7 Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Fri, 28 Aug 2020 15:22:58 +0200 Subject: [PATCH] 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 --- winsup/cygwin/child_info.h | 2 - winsup/cygwin/sigproc.cc | 145 ++++++++++++++++++++++++------------- 2 files changed, 93 insertions(+), 54 deletions(-) diff --git a/winsup/cygwin/child_info.h b/winsup/cygwin/child_info.h index e5aa28144..505eaef23 100644 --- a/winsup/cygwin/child_info.h +++ b/winsup/cygwin/child_info.h @@ -39,8 +39,6 @@ enum child_status /* Change this value if you get a message indicating that it is out-of-sync. */ #define CURR_CHILD_INFO_MAGIC 0xecc930b9U -#define NPROCS 256 - #include "pinfo.h" struct cchildren { diff --git a/winsup/cygwin/sigproc.cc b/winsup/cygwin/sigproc.cc index ba1e64b33..43da97bc3 100644 --- a/winsup/cygwin/sigproc.cc +++ b/winsup/cygwin/sigproc.cc @@ -38,19 +38,55 @@ int __sp_ln; bool no_thread_exit_protect::flag; -char NO_COPY myself_nowait_dummy[1] = {'0'};// Flag to sig_send that signal goes to - // current process but no wait is required +/* Flag to sig_send that signal goes to current process but no wait is + required. */ +char NO_COPY myself_nowait_dummy[1] = {'0'}; #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 -Static char cprocs[(NPROCS + 1) * sizeof (pinfo)];// All my children info -#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 +/* Start of queue for waiting threads. */ +Static waitq waitq_head; -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; @@ -163,8 +199,8 @@ pid_exists (pid_t pid) static inline bool mychild (int pid) { - for (int i = 0; i < nprocs; i++) - if (procs[i]->pid == pid) + for (int i = 0; i < chld_procs.count (); i++) + if (chld_procs[i]->pid == pid) return true; return false; } @@ -174,6 +210,7 @@ mychild (int pid) int __reg2 proc_subproc (DWORD what, uintptr_t val) { + int slot; int rc = 1; int potential_match; int clearing; @@ -197,14 +234,15 @@ proc_subproc (DWORD what, uintptr_t val) */ case PROC_ADD_CHILD: /* 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", - nprocs, vchild->pid); + chld_procs.count (), vchild->pid); rc = 0; set_errno (EAGAIN); break; } + if (vchild != myself) { vchild->uid = myself->uid; @@ -219,13 +257,14 @@ proc_subproc (DWORD what, uintptr_t val) break; case PROC_ATTACH_CHILD: - procs[nprocs] = vchild; - rc = procs[nprocs].wait (); + slot = chld_procs.count (); + chld_procs[slot] = vchild; + rc = chld_procs[slot].wait (); if (rc) { sigproc_printf ("added pid %d to proc table, slot %d", vchild->pid, - nprocs); - nprocs++; + slot); + chld_procs.add_one (); } break; @@ -260,7 +299,7 @@ proc_subproc (DWORD what, uintptr_t val) goto scan_wait; case PROC_EXEC_CLEANUP: - while (nprocs) + while (chld_procs.count ()) remove_proc (0); for (w = &waitq_head; w->next != NULL; w = w->next) CloseHandle (w->next->ev); @@ -274,7 +313,8 @@ proc_subproc (DWORD what, uintptr_t val) if (val) sigproc_printf ("clear waiting threads"); 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; scan_wait: @@ -312,7 +352,7 @@ proc_subproc (DWORD what, uintptr_t val) } 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; } @@ -352,35 +392,35 @@ _cygtls::remove_wq (DWORD wait) Called on process exit. Also called by spawn_guts to disassociate any subprocesses from this process. Subprocesses will then know to clean up after themselves and - will not become procs. */ + will not become chld_procs. */ void proc_terminate () { - sigproc_printf ("nprocs %d", nprocs); - if (nprocs) + sigproc_printf ("child_procs count %d", chld_procs.count ()); + if (chld_procs.count ()) { sync_proc_subproc.acquire (WPSP); proc_subproc (PROC_CLEARWAIT, 1); /* 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 to 1 iff it is a Cygwin process. */ if (!have_execed || !have_execed_cygwin) - procs[i]->ppid = 1; - if (procs[i].wait_thread) - procs[i].wait_thread->terminate_thread (); + chld_procs[i]->ppid = 1; + if (chld_procs[i].wait_thread) + chld_procs[i].wait_thread->terminate_thread (); /* Release memory associated with this process unless it is 'myself'. - 'myself' is only in the procs table when we've execed. We reach - here when the next process has finished initializing but we still - can't free the memory used by 'myself' since it is used later on - during cygwin tear down. */ - if (procs[i] != myself) - procs[i].release (); + 'myself' is only in the chld_procs table when we've execed. We + reach here when the next process has finished initializing but we + still can't free the memory used by 'myself' since it is used + later on during cygwin tear down. */ + if (chld_procs[i] != myself) + chld_procs[i].release (); } - nprocs = 0; + chld_procs.reset (); sync_proc_subproc.release (); } sigproc_printf ("leaving"); @@ -866,7 +906,8 @@ cygheap_exec_info::alloc () cygheap_exec_info *res = (cygheap_exec_info *) ccalloc_abort (HEAP_1_EXEC, 1, sizeof (cygheap_exec_info) - + (nprocs * sizeof (children[0]))); + + (chld_procs.count () + * sizeof (children[0]))); res->sigmask = _my_tls.sigmask; return res; } @@ -942,10 +983,10 @@ child_info_spawn::cleanup () inline void 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].p = procs[nchildren]; + children[nchildren].pid = chld_procs[nchildren]->pid; + children[nchildren].p = chld_procs[nchildren]; /* Set inheritance of required child handles for reattach_children in the about-to-be-execed process. */ children[nchildren].p.set_inheritance (true); @@ -1126,13 +1167,13 @@ checkstate (waitq *parent_w) { 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 * given in w->next. */ int res; - for (int i = 0; i < nprocs; i++) - if ((res = stopped_or_terminated (parent_w, procs[i]))) + for (int i = 0; i < chld_procs.count (); i++) + if ((res = stopped_or_terminated (parent_w, chld_procs[i]))) { remove_proc (i); potential_match = 1; @@ -1140,40 +1181,40 @@ checkstate (waitq *parent_w) } sigproc_printf ("no matching terminated children found"); - potential_match = -!!nprocs; + potential_match = -!!chld_procs.count (); out: sigproc_printf ("returning %d", 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. */ static int remove_proc (int ci) { if (have_execed) { - if (_my_tls._ctinfo != procs[ci].wait_thread) - procs[ci].wait_thread->terminate_thread (); + if (_my_tls._ctinfo != chld_procs[ci].wait_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; - sigproc_printf ("removing procs[%d], pid %d, nprocs %d", ci, procs[ci]->pid, - nprocs); - if (procs[ci] != myself) - procs[ci].release (); - if (ci < --nprocs) + sigproc_printf ("removing chld_procs[%d], pid %d, child_procs count %d", + ci, chld_procs[ci]->pid, chld_procs.count ()); + if (chld_procs[ci] != myself) + chld_procs[ci].release (); + if (ci < chld_procs.del_one ()) { /* 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 the proc_waiter thread has already done this by the time we get here. */ if (!have_execed && !exit_state) - while (!procs[nprocs].waiter_ready) + while (!chld_procs[chld_procs.count ()].waiter_ready) yield (); - procs[ci] = procs[nprocs]; + chld_procs[ci] = chld_procs[chld_procs.count ()]; } return 0; }