public inbox for cygwin-cvs@sourceware.org
help / color / mirror / Atom feed
* [newlib-cygwin] Cygwin: sigproc: Allow more child processes per process
@ 2020-08-28 13:24 Corinna Vinschen
  0 siblings, 0 replies; only message in thread
From: Corinna Vinschen @ 2020-08-28 13:24 UTC (permalink / raw)
  To: cygwin-cvs

https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=7c963c7ba030b9e110eefd6412eff4d6189f29e7

commit 7c963c7ba030b9e110eefd6412eff4d6189f29e7
Author: Corinna Vinschen <corinna@vinschen.de>
Date:   Fri Aug 28 15:22:58 2020 +0200

    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>

Diff:
---
 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;
 }


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2020-08-28 13:24 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-08-28 13:24 [newlib-cygwin] Cygwin: sigproc: Allow more child processes per process Corinna Vinschen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).