Fix bug #12829 with aborts on MS-Windows when several child processes die.
[bpt/emacs.git] / src / w32proc.c
index b0881c2..fd6a498 100644 (file)
@@ -24,6 +24,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
+#include <ctype.h>
 #include <io.h>
 #include <fcntl.h>
 #include <signal.h>
@@ -51,6 +52,7 @@ extern BOOL WINAPI IsValidLocale (LCID, DWORD);
 
 #include "lisp.h"
 #include "w32.h"
+#include "w32common.h"
 #include "w32heap.h"
 #include "systime.h"
 #include "syswait.h"
@@ -67,22 +69,6 @@ extern BOOL WINAPI IsValidLocale (LCID, DWORD);
 
 Lisp_Object Qhigh, Qlow;
 
-#ifdef EMACSDEBUG
-void
-_DebPrint (const char *fmt, ...)
-{
-  char buf[1024];
-  va_list args;
-
-  va_start (args, fmt);
-  vsprintf (buf, fmt, args);
-  va_end (args);
-  OutputDebugString (buf);
-}
-#endif
-
-typedef void (_CALLBACK_ *signal_handler) (int);
-
 /* Signal handlers...SIG_DFL == 0 so this is initialized correctly.  */
 static signal_handler sig_handlers[NSIG];
 
@@ -244,12 +230,30 @@ sigismember (const sigset_t *set, int signo)
   return (*set & (1U << signo)) != 0;
 }
 
+pid_t
+getpgrp (void)
+{
+  return getpid ();
+}
+
+pid_t
+tcgetpgrp (int fd)
+{
+  return getpid ();
+}
+
 int
-setpgrp (int pid, int gid)
+setpgid (pid_t pid, pid_t pgid)
 {
   return 0;
 }
 
+pid_t
+setsid (void)
+{
+  return getpid ();
+}
+
 /* Emulations of interval timers.
 
    Limitations: only ITIMER_REAL and ITIMER_PROF are supported.
@@ -258,28 +262,92 @@ setpgrp (int pid, int gid)
    the thread calls the appropriate signal handler when the timer
    expires, after stopping the thread which installed the timer.  */
 
-/* FIXME: clock_t counts overflow after 49 days, need to handle the
-   wrap-around.  */
 struct itimer_data {
-  clock_t expire;
-  clock_t reload;
-  int terminate;
+  volatile ULONGLONG expire;
+  volatile ULONGLONG reload;
+  volatile int terminate;
   int type;
   HANDLE caller_thread;
   HANDLE timer_thread;
 };
 
-static clock_t ticks_now;
+static ULONGLONG ticks_now;
 static struct itimer_data real_itimer, prof_itimer;
-static clock_t clocks_min;
+static ULONGLONG clocks_min;
 /* If non-zero, itimers are disabled.  Used during shutdown, when we
    delete the critical sections used by the timer threads.  */
 static int disable_itimers;
 
 static CRITICAL_SECTION crit_real, crit_prof;
 
-#define MAX_SINGLE_SLEEP 30
+/* GetThreadTimes is not available on Windows 9X and possibly also on 2K.  */
+typedef BOOL (WINAPI *GetThreadTimes_Proc) (
+  HANDLE hThread,
+  LPFILETIME lpCreationTime,
+  LPFILETIME lpExitTime,
+  LPFILETIME lpKernelTime,
+  LPFILETIME lpUserTime);
+
+static GetThreadTimes_Proc s_pfn_Get_Thread_Times;
+
+#define MAX_SINGLE_SLEEP    30
+#define TIMER_TICKS_PER_SEC 1000
+
+/* Return a suitable time value, in 1-ms units, for THREAD, a handle
+   to a thread.  If THREAD is NULL or an invalid handle, return the
+   current wall-clock time since January 1, 1601 (UTC).  Otherwise,
+   return the sum of kernel and user times used by THREAD since it was
+   created, plus its creation time.  */
+static ULONGLONG
+w32_get_timer_time (HANDLE thread)
+{
+  ULONGLONG retval;
+  int use_system_time = 1;
+  /* The functions below return times in 100-ns units.  */
+  const int tscale = 10 * TIMER_TICKS_PER_SEC;
+
+  if (thread && thread != INVALID_HANDLE_VALUE
+      && s_pfn_Get_Thread_Times != NULL)
+    {
+      FILETIME creation_ftime, exit_ftime, kernel_ftime, user_ftime;
+      ULARGE_INTEGER temp_creation, temp_kernel, temp_user;
+
+      if (s_pfn_Get_Thread_Times (thread, &creation_ftime, &exit_ftime,
+                                 &kernel_ftime, &user_ftime))
+       {
+         use_system_time = 0;
+         temp_creation.LowPart = creation_ftime.dwLowDateTime;
+         temp_creation.HighPart = creation_ftime.dwHighDateTime;
+         temp_kernel.LowPart = kernel_ftime.dwLowDateTime;
+         temp_kernel.HighPart = kernel_ftime.dwHighDateTime;
+         temp_user.LowPart = user_ftime.dwLowDateTime;
+         temp_user.HighPart = user_ftime.dwHighDateTime;
+         retval =
+           temp_creation.QuadPart / tscale + temp_kernel.QuadPart / tscale
+           + temp_user.QuadPart / tscale;
+       }
+      else
+       DebPrint (("GetThreadTimes failed with error code %lu\n",
+                  GetLastError ()));
+    }
+
+  if (use_system_time)
+    {
+      FILETIME current_ftime;
+      ULARGE_INTEGER temp;
 
+      GetSystemTimeAsFileTime (&current_ftime);
+
+      temp.LowPart = current_ftime.dwLowDateTime;
+      temp.HighPart = current_ftime.dwHighDateTime;
+
+      retval = temp.QuadPart / tscale;
+    }
+
+  return retval;
+}
+
+/* Thread function for a timer thread.  */
 static DWORD WINAPI
 timer_loop (LPVOID arg)
 {
@@ -287,14 +355,14 @@ timer_loop (LPVOID arg)
   int which = itimer->type;
   int sig = (which == ITIMER_REAL) ? SIGALRM : SIGPROF;
   CRITICAL_SECTION *crit = (which == ITIMER_REAL) ? &crit_real : &crit_prof;
-  const DWORD max_sleep = MAX_SINGLE_SLEEP * 1000 / CLOCKS_PER_SEC;
-  int new_count = 0;
+  const DWORD max_sleep = MAX_SINGLE_SLEEP * 1000 / TIMER_TICKS_PER_SEC;
+  HANDLE hth = (which == ITIMER_REAL) ? NULL : itimer->caller_thread;
 
   while (1)
     {
       DWORD sleep_time;
       signal_handler handler;
-      clock_t now, expire, reload;
+      ULONGLONG now, expire, reload;
 
       /* Load new values if requested by setitimer.  */
       EnterCriticalSection (crit);
@@ -304,15 +372,14 @@ timer_loop (LPVOID arg)
       if (itimer->terminate)
        return 0;
 
-      if (itimer->expire == 0)
+      if (expire == 0)
        {
          /* We are idle.  */
          Sleep (max_sleep);
          continue;
        }
 
-      expire = itimer->expire;
-      if (expire > (now = clock ()))
+      if (expire > (now = w32_get_timer_time (hth)))
        sleep_time = expire - now;
       else
        sleep_time = 0;
@@ -323,24 +390,30 @@ timer_loop (LPVOID arg)
          if (itimer->terminate)
            return 0;
          Sleep (max_sleep);
+         EnterCriticalSection (crit);
          expire = itimer->expire;
-         sleep_time = (expire > (now = clock ())) ? expire - now : 0;
+         LeaveCriticalSection (crit);
+         sleep_time =
+           (expire > (now = w32_get_timer_time (hth))) ? expire - now : 0;
        }
       if (itimer->terminate)
        return 0;
       if (sleep_time > 0)
        {
-         Sleep (sleep_time * 1000 / CLOCKS_PER_SEC);
+         Sleep (sleep_time * 1000 / TIMER_TICKS_PER_SEC);
          /* Always sleep past the expiration time, to make sure we
             never call the handler _before_ the expiration time,
-            always slightly after it.  Sleep(0) relinquishes the rest
-            of the scheduled slot, so that we let other threads
-            work.  */
-         while (clock () < expire)
-           Sleep (0);
+            always slightly after it.  Sleep(5) makes sure we don't
+            hog the CPU by calling 'w32_get_timer_time' with high
+            frequency, and also let other threads work.  */
+         while (w32_get_timer_time (hth) < expire)
+           Sleep (5);
        }
 
-      if (itimer->expire == 0)
+      EnterCriticalSection (crit);
+      expire = itimer->expire;
+      LeaveCriticalSection (crit);
+      if (expire == 0)
        continue;
 
       /* Time's up.  */
@@ -358,30 +431,31 @@ timer_loop (LPVOID arg)
          /* Simulate a signal delivered to the thread which installed
             the timer, by suspending that thread while the handler
             runs.  */
-         DWORD result = SuspendThread (itimer->caller_thread);
+         HANDLE th = itimer->caller_thread;
+         DWORD result = SuspendThread (th);
 
          if (result == (DWORD)-1)
-           {
-             DebPrint (("Thread %d exiting with status 2\n", which));
-             return 2;
-           }
+           return 2;
+
          handler (sig);
-         ResumeThread (itimer->caller_thread);
+         ResumeThread (th);
        }
 
-      if (itimer->expire == 0)
-       continue;
-
       /* Update expiration time and loop.  */
       EnterCriticalSection (crit);
       expire = itimer->expire;
+      if (expire == 0)
+       {
+         LeaveCriticalSection (crit);
+         continue;
+       }
       reload = itimer->reload;
       if (reload > 0)
        {
-         now = clock ();
+         now = w32_get_timer_time (hth);
          if (expire <= now)
            {
-             clock_t lag = now - expire;
+             ULONGLONG lag = now - expire;
 
              /* If we missed some opportunities (presumably while
                 sleeping or while the signal handler ran), skip
@@ -406,8 +480,8 @@ stop_timer_thread (int which)
   struct itimer_data *itimer =
     (which == ITIMER_REAL) ? &real_itimer : &prof_itimer;
   int i;
-  DWORD exit_code = 255;
-  BOOL status, err;
+  DWORD err, exit_code = 255;
+  BOOL status;
 
   /* Signal the thread that it should terminate.  */
   itimer->terminate = 1;
@@ -464,6 +538,15 @@ term_timers (void)
 void
 init_timers (void)
 {
+  /* GetThreadTimes is not available on all versions of Windows, so
+     need to probe for its availability dynamically, and call it
+     through a pointer.  */
+  s_pfn_Get_Thread_Times = NULL; /* in case dumped Emacs comes with a value */
+  if (os_subtype != OS_9X)
+    s_pfn_Get_Thread_Times =
+      (GetThreadTimes_Proc)GetProcAddress (GetModuleHandle ("kernel32.dll"),
+                                          "GetThreadTimes");
+
   /* Make sure we start with zeroed out itimer structures, since
      dumping may have left there traces of threads long dead.  */
   memset (&real_itimer, 0, sizeof real_itimer);
@@ -480,6 +563,7 @@ static int
 start_timer_thread (int which)
 {
   DWORD exit_code;
+  HANDLE th;
   struct itimer_data *itimer =
     (which == ITIMER_REAL) ? &real_itimer : &prof_itimer;
 
@@ -488,17 +572,29 @@ start_timer_thread (int which)
       && exit_code == STILL_ACTIVE)
     return 0;
 
+  /* Clean up after possibly exited thread.  */
+  if (itimer->timer_thread)
+    {
+      CloseHandle (itimer->timer_thread);
+      itimer->timer_thread = NULL;
+    }
+  if (itimer->caller_thread)
+    {
+      CloseHandle (itimer->caller_thread);
+      itimer->caller_thread = NULL;
+    }
+
   /* Start a new thread.  */
   if (!DuplicateHandle (GetCurrentProcess (), GetCurrentThread (),
-                       GetCurrentProcess (), &itimer->caller_thread, 0,
-                       FALSE, DUPLICATE_SAME_ACCESS))
+                       GetCurrentProcess (), &th, 0, FALSE,
+                       DUPLICATE_SAME_ACCESS))
     {
       errno = ESRCH;
       return -1;
     }
-
   itimer->terminate = 0;
   itimer->type = which;
+  itimer->caller_thread = th;
   /* Request that no more than 64KB of stack be reserved for this
      thread, to avoid reserving too much memory, which would get in
      the way of threads we start to wait for subprocesses.  See also
@@ -517,7 +613,7 @@ start_timer_thread (int which)
   /* This is needed to make sure that the timer thread running for
      profiling gets CPU as soon as the Sleep call terminates. */
   if (which == ITIMER_PROF)
-    SetThreadPriority (itimer->caller_thread, THREAD_PRIORITY_TIME_CRITICAL);
+    SetThreadPriority (itimer->timer_thread, THREAD_PRIORITY_TIME_CRITICAL);
 
   return 0;
 }
@@ -528,17 +624,16 @@ start_timer_thread (int which)
 int
 getitimer (int which, struct itimerval *value)
 {
-  volatile clock_t *t_expire;
-  volatile clock_t *t_reload;
-  clock_t expire, reload;
+  volatile ULONGLONG *t_expire;
+  volatile ULONGLONG *t_reload;
+  ULONGLONG expire, reload;
   __int64 usecs;
   CRITICAL_SECTION *crit;
+  struct itimer_data *itimer;
 
   if (disable_itimers)
     return -1;
 
-  ticks_now = clock ();
-
   if (!value)
     {
       errno = EFAULT;
@@ -551,8 +646,14 @@ getitimer (int which, struct itimerval *value)
       return -1;
     }
 
-  t_expire = (which == ITIMER_REAL) ? &real_itimer.expire: &prof_itimer.expire;
-  t_reload = (which == ITIMER_REAL) ? &real_itimer.reload: &prof_itimer.reload;
+  itimer = (which == ITIMER_REAL) ? &real_itimer : &prof_itimer;
+
+  ticks_now = w32_get_timer_time ((which == ITIMER_REAL)
+                                 ? NULL
+                                 : GetCurrentThread ());
+
+  t_expire = &itimer->expire;
+  t_reload = &itimer->reload;
   crit = (which == ITIMER_REAL) ? &crit_real : &crit_prof;
 
   EnterCriticalSection (crit);
@@ -563,11 +664,13 @@ getitimer (int which, struct itimerval *value)
   if (expire)
     expire -= ticks_now;
 
-  value->it_value.tv_sec    = expire / CLOCKS_PER_SEC;
-  usecs = (expire % CLOCKS_PER_SEC) * (__int64)1000000 / CLOCKS_PER_SEC;
+  value->it_value.tv_sec    = expire / TIMER_TICKS_PER_SEC;
+  usecs =
+    (expire % TIMER_TICKS_PER_SEC) * (__int64)1000000 / TIMER_TICKS_PER_SEC;
   value->it_value.tv_usec   = usecs;
-  value->it_interval.tv_sec = reload / CLOCKS_PER_SEC;
-  usecs = (reload % CLOCKS_PER_SEC) * (__int64)1000000 / CLOCKS_PER_SEC;
+  value->it_interval.tv_sec = reload / TIMER_TICKS_PER_SEC;
+  usecs =
+    (reload % TIMER_TICKS_PER_SEC) * (__int64)1000000 / TIMER_TICKS_PER_SEC;
   value->it_interval.tv_usec= usecs;
 
   return 0;
@@ -576,10 +679,11 @@ getitimer (int which, struct itimerval *value)
 int
 setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
 {
-  volatile clock_t *t_expire, *t_reload;
-  clock_t expire, reload, expire_old, reload_old;
+  volatile ULONGLONG *t_expire, *t_reload;
+  ULONGLONG expire, reload, expire_old, reload_old;
   __int64 usecs;
   CRITICAL_SECTION *crit;
+  struct itimerval tem, *ptem;
 
   if (disable_itimers)
     return -1;
@@ -589,26 +693,21 @@ setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
      time we are called, measure the clock tick resolution.  */
   if (!clocks_min)
     {
-      clock_t t1, t2;
+      ULONGLONG t1, t2;
 
-      for (t1 = clock (); (t2 = clock ()) == t1; )
+      for (t1 = w32_get_timer_time (NULL);
+          (t2 = w32_get_timer_time (NULL)) == t1; )
        ;
       clocks_min = t2 - t1;
     }
 
   if (ovalue)
-    {
-      if (getitimer (which, ovalue)) /* also sets ticks_now */
-       return -1;                   /* errno already set */
-    }
+    ptem = ovalue;
   else
-    ticks_now = clock ();
+    ptem = &tem;
 
-  if (which != ITIMER_REAL && which != ITIMER_PROF)
-    {
-      errno = EINVAL;
-      return -1;
-    }
+  if (getitimer (which, ptem)) /* also sets ticks_now */
+    return -1;                /* errno already set */
 
   t_expire =
     (which == ITIMER_REAL) ? &real_itimer.expire : &prof_itimer.expire;
@@ -628,26 +727,26 @@ setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
       return 0;
     }
 
-  reload = value->it_interval.tv_sec * CLOCKS_PER_SEC;
+  reload = value->it_interval.tv_sec * TIMER_TICKS_PER_SEC;
 
   usecs = value->it_interval.tv_usec;
   if (value->it_interval.tv_sec == 0
-      && usecs && usecs * CLOCKS_PER_SEC < clocks_min * 1000000)
+      && usecs && usecs * TIMER_TICKS_PER_SEC < clocks_min * 1000000)
     reload = clocks_min;
   else
     {
-      usecs *= CLOCKS_PER_SEC;
+      usecs *= TIMER_TICKS_PER_SEC;
       reload += usecs / 1000000;
     }
 
-  expire = value->it_value.tv_sec * CLOCKS_PER_SEC;
+  expire = value->it_value.tv_sec * TIMER_TICKS_PER_SEC;
   usecs = value->it_value.tv_usec;
   if (value->it_value.tv_sec == 0
-      && usecs * CLOCKS_PER_SEC < clocks_min * 1000000)
+      && usecs * TIMER_TICKS_PER_SEC < clocks_min * 1000000)
     expire = clocks_min;
   else
     {
-      usecs *= CLOCKS_PER_SEC;
+      usecs *= TIMER_TICKS_PER_SEC;
       expire += usecs / 1000000;
     }
 
@@ -690,7 +789,6 @@ alarm (int seconds)
 /* Child process management list.  */
 int child_proc_count = 0;
 child_process child_procs[ MAX_CHILDREN ];
-child_process *dead_child = NULL;
 
 static DWORD WINAPI reader_thread (void *arg);
 
@@ -943,9 +1041,6 @@ create_child (char *exe, char *cmdline, char *env, int is_gui_app,
   if (cp->pid < 0)
     cp->pid = -cp->pid;
 
-  /* pid must fit in a Lisp_Int */
-  cp->pid = cp->pid & INTMASK;
-
   *pPid = cp->pid;
 
   return TRUE;
@@ -1021,55 +1116,110 @@ reap_subprocess (child_process *cp)
     delete_child (cp);
 }
 
-/* Wait for any of our existing child processes to die
-   When it does, close its handle
-   Return the pid and fill in the status if non-NULL.  */
+/* Wait for a child process specified by PID, or for any of our
+   existing child processes (if PID is nonpositive) to die.  When it
+   does, close its handle.  Return the pid of the process that died
+   and fill in STATUS if non-NULL.  */
 
-int
-sys_wait (int *status)
+pid_t
+waitpid (pid_t pid, int *status, int options)
 {
   DWORD active, retval;
   int nh;
-  int pid;
   child_process *cp, *cps[MAX_CHILDREN];
   HANDLE wait_hnd[MAX_CHILDREN];
+  DWORD timeout_ms;
+  int dont_wait = (options & WNOHANG) != 0;
 
   nh = 0;
-  if (dead_child != NULL)
+  /* According to Posix:
+
+     PID = -1 means status is requested for any child process.
+
+     PID > 0 means status is requested for a single child process
+     whose pid is PID.
+
+     PID = 0 means status is requested for any child process whose
+     process group ID is equal to that of the calling process.  But
+     since Windows has only a limited support for process groups (only
+     for console processes and only for the purposes of passing
+     Ctrl-BREAK signal to them), and since we have no documented way
+     of determining whether a given process belongs to our group, we
+     treat 0 as -1.
+
+     PID < -1 means status is requested for any child process whose
+     process group ID is equal to the absolute value of PID.  Again,
+     since we don't support process groups, we treat that as -1.  */
+  if (pid > 0)
     {
-      /* We want to wait for a specific child */
-      wait_hnd[nh] = dead_child->procinfo.hProcess;
-      cps[nh] = dead_child;
-      if (!wait_hnd[nh]) emacs_abort ();
-      nh++;
-      active = 0;
-      goto get_result;
+      int our_child = 0;
+
+      /* We are requested to wait for a specific child.  */
+      for (cp = child_procs + (child_proc_count-1); cp >= child_procs; cp--)
+       {
+         /* Some child_procs might be sockets; ignore them.  Also
+            ignore subprocesses whose output is not yet completely
+            read.  */
+         if (CHILD_ACTIVE (cp)
+             && cp->procinfo.hProcess
+             && cp->pid == pid)
+           {
+             our_child = 1;
+             break;
+           }
+       }
+      if (our_child)
+       {
+         if (cp->fd < 0 || (fd_info[cp->fd].flags & FILE_AT_EOF) != 0)
+           {
+             wait_hnd[nh] = cp->procinfo.hProcess;
+             cps[nh] = cp;
+             nh++;
+           }
+         else if (dont_wait)
+           {
+             /* PID specifies our subprocess, but its status is not
+                yet available.  */
+             return 0;
+           }
+       }
+      if (nh == 0)
+       {
+         /* No such child process, or nothing to wait for, so fail.  */
+         errno = ECHILD;
+         return -1;
+       }
     }
   else
     {
       for (cp = child_procs + (child_proc_count-1); cp >= child_procs; cp--)
-       /* some child_procs might be sockets; ignore them */
-       if (CHILD_ACTIVE (cp) && cp->procinfo.hProcess
-           && (cp->fd < 0 || (fd_info[cp->fd].flags & FILE_AT_EOF) != 0))
-         {
-           wait_hnd[nh] = cp->procinfo.hProcess;
-           cps[nh] = cp;
-           nh++;
-         }
+       {
+         if (CHILD_ACTIVE (cp)
+             && cp->procinfo.hProcess
+             && (cp->fd < 0 || (fd_info[cp->fd].flags & FILE_AT_EOF) != 0))
+           {
+             wait_hnd[nh] = cp->procinfo.hProcess;
+             cps[nh] = cp;
+             nh++;
+           }
+       }
+      if (nh == 0)
+       {
+         /* Nothing to wait on, so fail.  */
+         errno = ECHILD;
+         return -1;
+       }
     }
 
-  if (nh == 0)
-    {
-      /* Nothing to wait on, so fail */
-      errno = ECHILD;
-      return -1;
-    }
+  if (dont_wait)
+    timeout_ms = 0;
+  else
+    timeout_ms = 1000; /* check for quit about once a second. */
 
   do
     {
-      /* Check for quit about once a second. */
       QUIT;
-      active = WaitForMultipleObjects (nh, wait_hnd, FALSE, 1000);
+      active = WaitForMultipleObjects (nh, wait_hnd, FALSE, timeout_ms);
     } while (active == WAIT_TIMEOUT);
 
   if (active == WAIT_FAILED)
@@ -1099,8 +1249,10 @@ get_result:
     }
   if (retval == STILL_ACTIVE)
     {
-      /* Should never happen */
+      /* Should never happen */
       DebPrint (("Wait.WaitForMultipleObjects returned an active process\n"));
+      if (pid > 0 && dont_wait)
+       return 0;
       errno = EINVAL;
       return -1;
     }
@@ -1114,6 +1266,8 @@ get_result:
   else
     retval <<= 8;
 
+  if (pid > 0 && active != 0)
+    emacs_abort ();
   cp = cps[active];
   pid = cp->pid;
 #ifdef FULL_DEBUG
@@ -1134,7 +1288,7 @@ get_result:
       else if (WIFSIGNALED (retval))
        {
          int code = WTERMSIG (retval);
-         char *signame;
+         const char *signame;
 
          synchronize_system_messages_locale ();
          signame = strsignal (code);
@@ -1593,7 +1747,7 @@ sys_spawnve (int mode, char *cmdname, char **argv, char **envp)
       numenv++;
     }
   /* extra env vars... */
-  sprintf (ppid_env_var_buffer, "EM_PARENT_PROCESS_ID=%d",
+  sprintf (ppid_env_var_buffer, "EM_PARENT_PROCESS_ID=%lu",
           GetCurrentProcessId ());
   arglen += strlen (ppid_env_var_buffer) + 1;
   numenv++;
@@ -1902,9 +2056,7 @@ count_children:
              DebPrint (("select calling SIGCHLD handler for pid %d\n",
                         cp->pid));
 #endif
-             dead_child = cp;
              sig_handlers[SIGCHLD] (SIGCHLD);
-             dead_child = NULL;
            }
        }
       else if (fdindex[active] == -1)