500 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			500 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
| /* process.cc
 | |
| 
 | |
|    Copyright 2001, 2002, 2003, 2004, 2005, 2014 Red Hat Inc.
 | |
| 
 | |
|    Written by Robert Collins <rbtcollins@hotmail.com>
 | |
| 
 | |
| This file is part of Cygwin.
 | |
| 
 | |
| This software is a copyrighted work licensed under the terms of the
 | |
| Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
 | |
| details. */
 | |
| 
 | |
| #ifdef __OUTSIDE_CYGWIN__
 | |
| #include "woutsup.h"
 | |
| 
 | |
| #include <sys/types.h>
 | |
| 
 | |
| #include <assert.h>
 | |
| #include <stdlib.h>
 | |
| 
 | |
| #include "process.h"
 | |
| 
 | |
| /*****************************************************************************/
 | |
| 
 | |
| #define elements(ARRAY) (sizeof (ARRAY) / sizeof (*ARRAY))
 | |
| 
 | |
| /*****************************************************************************/
 | |
| 
 | |
| process_cleanup::~process_cleanup ()
 | |
| {
 | |
|   delete _process;
 | |
| }
 | |
| 
 | |
| void
 | |
| process_cleanup::process ()
 | |
| {
 | |
|   _process->cleanup ();
 | |
| }
 | |
| 
 | |
| /*****************************************************************************/
 | |
| 
 | |
| process::process (const pid_t cygpid, const DWORD winpid, HANDLE signal_arrived)
 | |
|   : _cygpid (cygpid),
 | |
|     _winpid (winpid),
 | |
|     _hProcess (NULL),
 | |
|     _signal_arrived (INVALID_HANDLE_VALUE),
 | |
|     _cleaning_up (false),
 | |
|     _exit_status (STILL_ACTIVE),
 | |
|     _routines_head (NULL),
 | |
|     _next (NULL)
 | |
| {
 | |
|   _hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, winpid);
 | |
|   if (!_hProcess)
 | |
|     {
 | |
|       system_printf ("unable to obtain handle for new cache process %d(%u)",
 | |
| 		     _cygpid, _winpid);
 | |
|       _hProcess = INVALID_HANDLE_VALUE;
 | |
|       _exit_status = 0;
 | |
|     }
 | |
|   else
 | |
|     debug_printf ("got handle %p for new cache process %d(%u)",
 | |
| 		  _hProcess, _cygpid, _winpid);
 | |
|   if (!signal_arrived)
 | |
|     debug_printf ("signal_arrived NULL for process %d(%u)", _cygpid, _winpid);
 | |
|   else if (signal_arrived != INVALID_HANDLE_VALUE)
 | |
|     {
 | |
|       if (!DuplicateHandle (_hProcess, signal_arrived,
 | |
| 			    GetCurrentProcess (), &_signal_arrived,
 | |
| 			    0, FALSE, DUPLICATE_SAME_ACCESS))
 | |
| 	{
 | |
| 	  system_printf ("error getting signal_arrived to server (%u)",
 | |
| 			 GetLastError ());
 | |
| 	  _signal_arrived = INVALID_HANDLE_VALUE;
 | |
| 	}
 | |
|     }
 | |
|   InitializeCriticalSection (&_access);
 | |
|   debug ("initialized (%u)", _cygpid);
 | |
| }
 | |
| 
 | |
| process::~process ()
 | |
| {
 | |
|   debug ("deleting (%u)", _cygpid);
 | |
|   DeleteCriticalSection (&_access);
 | |
|   if (_signal_arrived && _signal_arrived != INVALID_HANDLE_VALUE)
 | |
|     CloseHandle (_signal_arrived);
 | |
|   CloseHandle (_hProcess);
 | |
| }
 | |
| 
 | |
| /* No need to be thread-safe as this is only ever called by
 | |
|  * process_cache::check_and_remove_process ().  If it has to be made
 | |
|  * thread-safe later on, it should not use the `access' critical section as
 | |
|  * that is held by the client request handlers for an arbitrary length of time,
 | |
|  * i.e. while they do whatever processing is required for a client request.
 | |
|  */
 | |
| DWORD
 | |
| process::check_exit_code ()
 | |
| {
 | |
|   if (_hProcess && _hProcess != INVALID_HANDLE_VALUE
 | |
|       && _exit_status == STILL_ACTIVE
 | |
|       && !GetExitCodeProcess (_hProcess, &_exit_status))
 | |
|     {
 | |
|       system_printf ("failed to retrieve exit code for %d(%u), error = %u",
 | |
| 		     _cygpid, _winpid, GetLastError ());
 | |
|       _hProcess = INVALID_HANDLE_VALUE;
 | |
|     }
 | |
|   return _exit_status;
 | |
| }
 | |
| 
 | |
| bool
 | |
| process::add (cleanup_routine *const entry)
 | |
| {
 | |
|   assert (entry);
 | |
| 
 | |
|   bool res = false;
 | |
|   hold ();
 | |
| 
 | |
|   if (!_cleaning_up)
 | |
|     {
 | |
|       entry->_next = _routines_head;
 | |
|       _routines_head = entry;
 | |
|       res = true;
 | |
|     }
 | |
| 
 | |
|   release ();
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| bool
 | |
| process::remove (const cleanup_routine *const entry)
 | |
| {
 | |
|   assert (entry);
 | |
| 
 | |
|   bool res = false;
 | |
|   hold ();
 | |
| 
 | |
|   if (!_cleaning_up)
 | |
|     {
 | |
|       cleanup_routine *previous = NULL;
 | |
| 
 | |
|       for (cleanup_routine *ptr = _routines_head;
 | |
| 	   ptr;
 | |
| 	   previous = ptr, ptr = ptr->_next)
 | |
| 	{
 | |
| 	  if (*ptr == *entry)
 | |
| 	    {
 | |
| 	      if (previous)
 | |
| 		previous->_next = ptr->_next;
 | |
| 	      else
 | |
| 		_routines_head = ptr->_next;
 | |
| 
 | |
| 	      delete ptr;
 | |
| 	      res = true;
 | |
| 	      break;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   release ();
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| /* This is single threaded. It's called after the process is removed
 | |
|  * from the cache, but inserts may be attemped by worker threads that
 | |
|  * have a pointer to it.
 | |
|  */
 | |
| void
 | |
| process::cleanup ()
 | |
| {
 | |
|   hold ();
 | |
|   assert (!is_active ());
 | |
|   assert (!_cleaning_up);
 | |
|   InterlockedExchange (&_cleaning_up, true);
 | |
|   cleanup_routine *entry = _routines_head;
 | |
|   _routines_head = NULL;
 | |
|   release ();
 | |
| 
 | |
|   while (entry)
 | |
|     {
 | |
|       cleanup_routine *const ptr = entry;
 | |
|       entry = entry->_next;
 | |
|       ptr->cleanup (this);
 | |
|       delete ptr;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*****************************************************************************/
 | |
| 
 | |
| void
 | |
| process_cache::submission_loop::request_loop ()
 | |
| {
 | |
|   assert (this);
 | |
|   assert (_cache);
 | |
|   assert (_interrupt_event);
 | |
| 
 | |
|   while (_running)
 | |
|     _cache->wait_for_processes (_interrupt_event);
 | |
| }
 | |
| 
 | |
| /*****************************************************************************/
 | |
| 
 | |
| process_cache::process_cache (const size_t max_procs,
 | |
| 			      const unsigned int initial_workers)
 | |
|   : _queue (initial_workers),
 | |
|     _submitter (this, &_queue),	// true == interruptible
 | |
|     _processes_count (0),
 | |
|     _max_process_count (max_procs),
 | |
|     _processes_head (NULL),
 | |
|     _cache_add_trigger (NULL)
 | |
| {
 | |
|   /* there can only be one */
 | |
|   InitializeCriticalSection (&_cache_write_access);
 | |
| 
 | |
|   _cache_add_trigger = CreateEvent (NULL,  // SECURITY_ATTRIBUTES
 | |
| 				    TRUE,  // Manual-reset
 | |
| 				    FALSE, // Initially non-signalled
 | |
| 				    NULL); // Anonymous
 | |
| 
 | |
|   if (!_cache_add_trigger)
 | |
|     {
 | |
|       system_printf ("failed to create cache add trigger, error = %u",
 | |
| 		     GetLastError ());
 | |
|       abort ();
 | |
|     }
 | |
| 
 | |
|   _queue.add_submission_loop (&_submitter);
 | |
| }
 | |
| 
 | |
| process_cache::~process_cache ()
 | |
| {
 | |
|   (void) CloseHandle (_cache_add_trigger);
 | |
|   DeleteCriticalSection (&_cache_write_access);
 | |
| }
 | |
| 
 | |
| /* This returns the process object to the caller already locked, that
 | |
|  * is, with the object's `access' critical region entered.  Thus the
 | |
|  * caller must unlock the object when it's finished with it (via
 | |
|  * process::release ()).  It must then not try to access the object
 | |
|  * afterwards, except by going through this routine again, as it may
 | |
|  * have been deleted once it has been unlocked.
 | |
|  */
 | |
| class process *
 | |
| process_cache::process (const pid_t cygpid, const DWORD winpid,
 | |
| 			HANDLE signal_arrived)
 | |
| {
 | |
|   /* TODO: make this more granular, so a search doesn't involve the
 | |
|    * write lock.
 | |
|    */
 | |
|   EnterCriticalSection (&_cache_write_access);
 | |
|   class process *previous = NULL;
 | |
|   class process *entry = find (winpid, &previous);
 | |
| 
 | |
|   if (!entry)
 | |
|     {
 | |
|       if (_processes_count >= _max_process_count)
 | |
| 	{
 | |
| 	  LeaveCriticalSection (&_cache_write_access);
 | |
| 	  system_printf (("process limit (%d processes) reached; "
 | |
| 			  "new connection refused for %d(%u)"),
 | |
| 			 _max_process_count, cygpid, winpid);
 | |
| 	  return NULL;
 | |
| 	}
 | |
| 
 | |
|       entry = new class process (cygpid, winpid, signal_arrived);
 | |
|       if (!entry->is_active ())
 | |
| 	{
 | |
| 	  LeaveCriticalSection (&_cache_write_access);
 | |
| 	  delete entry;
 | |
| 	  return NULL;
 | |
| 	}
 | |
| 
 | |
|       if (previous)
 | |
| 	{
 | |
| 	  entry->_next = previous->_next;
 | |
| 	  previous->_next = entry;
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  entry->_next = _processes_head;
 | |
| 	  _processes_head = entry;
 | |
| 	}
 | |
| 
 | |
|       _processes_count += 1;
 | |
|       SetEvent (_cache_add_trigger);
 | |
|     }
 | |
| 
 | |
|   entry->hold (); // To be released by the caller.
 | |
|   LeaveCriticalSection (&_cache_write_access);
 | |
|   assert (entry);
 | |
|   assert (entry->_winpid == winpid);
 | |
|   return entry;
 | |
| }
 | |
| 
 | |
| struct pcache_wait_t
 | |
| {
 | |
|   size_t index;
 | |
|   size_t count;
 | |
|   HANDLE *hdls;
 | |
| };
 | |
| 
 | |
| static DWORD WINAPI
 | |
| pcache_wait_thread (const LPVOID param)
 | |
| {
 | |
|   pcache_wait_t *p = (pcache_wait_t *) param;
 | |
| 
 | |
|   DWORD rc = WaitForMultipleObjects (p->count, p->hdls, FALSE, INFINITE);
 | |
|   ExitThread (rc == WAIT_FAILED ? rc : rc + p->index);
 | |
| }
 | |
| 
 | |
| void
 | |
| process_cache::wait_for_processes (const HANDLE interrupt_event)
 | |
| {
 | |
|   // Update `_wait_array' with handles of all current processes.
 | |
|   size_t idx;
 | |
|   const size_t count = sync_wait_array (interrupt_event);
 | |
| 
 | |
|   debug_printf ("waiting on %u objects in total (%u processes)",
 | |
| 		count, _processes_count);
 | |
| 
 | |
|   DWORD rc = WAIT_FAILED;
 | |
| 
 | |
|   if (count <= 64)
 | |
|     {
 | |
|       /* If count <= 64, a single WaitForMultipleObjects is sufficient and
 | |
|          we can simply wait in the main thread. */
 | |
|       rc = WaitForMultipleObjects (count, _wait_array, FALSE, INFINITE);
 | |
|       if (rc == WAIT_FAILED)
 | |
| 	{
 | |
| 	  system_printf ("could not wait on the process handles, error = %u",
 | |
| 			 GetLastError ());
 | |
| 	  abort ();
 | |
| 	}
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       /* If count > 64 we have to create sub-threads which wait for the
 | |
|          actual wait objects and the main thread waits for the termination
 | |
| 	 of one of the threads. */
 | |
|       HANDLE main_wait_array[5] = { NULL };
 | |
|       DWORD mcount = 0;
 | |
| 
 | |
|       for (idx = 0; idx < count; idx += 64)
 | |
| 	{
 | |
| 	  pcache_wait_t p = { idx, min (count - idx, 64), _wait_array + idx };
 | |
| 	  main_wait_array[mcount++] = CreateThread (NULL, 0, pcache_wait_thread,
 | |
| 						    &p, 0, NULL);
 | |
| 	}
 | |
| 
 | |
|       rc = WaitForMultipleObjects (mcount, main_wait_array, FALSE, INFINITE);
 | |
|       if (rc == WAIT_FAILED)
 | |
| 	{
 | |
| 	  system_printf ("could not wait on the process handles, error = %u",
 | |
| 			 GetLastError ());
 | |
| 	  abort ();
 | |
| 	}
 | |
| 
 | |
|       /* Check for error condition on signalled sub-thread. */
 | |
|       GetExitCodeThread (main_wait_array[rc], &rc);
 | |
|       if (rc == WAIT_FAILED)
 | |
| 	{
 | |
| 	  system_printf ("could not wait on the process handles, error = %u",
 | |
| 			 GetLastError ());
 | |
| 	  abort ();
 | |
| 	}
 | |
| 
 | |
|       /* Wake up all waiting threads.  _cache_add_trigger gets reset
 | |
|          in sync_wait_array again. */
 | |
|       SetEvent (_cache_add_trigger);
 | |
|       WaitForMultipleObjects (mcount, main_wait_array, TRUE, INFINITE);
 | |
|       for (idx = 0; idx < mcount; idx++)
 | |
| 	CloseHandle (main_wait_array[idx]);
 | |
|     }
 | |
| 
 | |
|   /* Tell all processes the bad news.  This one formerly only checked
 | |
|      processes beginning with the index of the signalled process, but
 | |
|      this can result in processes which are signalled but never removed
 | |
|      under heavy load conditions. */
 | |
|   for (idx = 0; idx < count; idx++)
 | |
|     if (_process_array[idx])
 | |
|       check_and_remove_process (idx);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * process_cache::sync_wait_array ()
 | |
|  *
 | |
|  * Fill-in the wait array with the handles that the cache needs to wait on.
 | |
|  * These handles are:
 | |
|  *  - the process_process_param's interrupt event
 | |
|  *  - the process_cache's cache_add_trigger event
 | |
|  *  - the handle for each live process in the cache.
 | |
|  *
 | |
|  * Return value: the number of live handles in the array.
 | |
|  */
 | |
| 
 | |
| size_t
 | |
| process_cache::sync_wait_array (const HANDLE interrupt_event)
 | |
| {
 | |
|   assert (this);
 | |
|   assert (interrupt_event && interrupt_event != INVALID_HANDLE_VALUE);
 | |
| 
 | |
|   /* Always reset _cache_add_trigger before filling up the array again. */
 | |
|   ResetEvent (_cache_add_trigger);
 | |
| 
 | |
|   EnterCriticalSection (&_cache_write_access);
 | |
| 
 | |
|   size_t index = 0;
 | |
| 
 | |
|   for (class process *ptr = _processes_head; ptr; ptr = ptr->_next)
 | |
|     {
 | |
|       assert (ptr->_hProcess && ptr->_hProcess != INVALID_HANDLE_VALUE);
 | |
|       assert (ptr->is_active ());
 | |
| 
 | |
|       _wait_array[index] = ptr->handle ();
 | |
|       _process_array[index++] = ptr;
 | |
| 
 | |
|       if (!ptr->_next || index % 64 == 62)
 | |
| 	{
 | |
| 	  /* Added at the end of each thread's array part for efficiency. */
 | |
| 	  _wait_array[index] = interrupt_event;
 | |
| 	  _process_array[index++] = NULL;
 | |
| 	  _wait_array[index] = _cache_add_trigger;
 | |
| 	  _process_array[index++] = NULL;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   if (!index)
 | |
|     {
 | |
|       /* To get at least *something* to wait for. */
 | |
|       _wait_array[index] = interrupt_event;
 | |
|       _process_array[index++] = NULL;
 | |
|       _wait_array[index] = _cache_add_trigger;
 | |
|       _process_array[index++] = NULL;
 | |
|     }
 | |
| 
 | |
|   assert (index <= elements (_wait_array));
 | |
| 
 | |
|   LeaveCriticalSection (&_cache_write_access);
 | |
| 
 | |
|   return index;
 | |
| }
 | |
| 
 | |
| void
 | |
| process_cache::check_and_remove_process (const size_t index)
 | |
| {
 | |
|   assert (this);
 | |
|   assert (index < elements (_wait_array) - SPECIALS_COUNT);
 | |
| 
 | |
|   class process *const process = _process_array[index];
 | |
| 
 | |
|   assert (process);
 | |
|   assert (process->handle () == _wait_array[index]);
 | |
| 
 | |
|   if (process->check_exit_code () == STILL_ACTIVE)
 | |
|     return;
 | |
| 
 | |
|   debug_printf ("process %d(%u) has left the building ($? = %u)",
 | |
| 		process->_cygpid, process->_winpid, process->_exit_status);
 | |
| 
 | |
|   /* Unlink the process object from the process list. */
 | |
| 
 | |
|   EnterCriticalSection (&_cache_write_access);
 | |
| 
 | |
|   class process *previous = NULL;
 | |
| 
 | |
|   const class process *const tmp = find (process->_winpid, &previous);
 | |
| 
 | |
|   assert (tmp == process);
 | |
|   assert (previous ? previous->_next == process : _processes_head == process);
 | |
| 
 | |
|   if (previous)
 | |
|     previous->_next = process->_next;
 | |
|   else
 | |
|     _processes_head = process->_next;
 | |
| 
 | |
|   _processes_count -= 1;
 | |
|   LeaveCriticalSection (&_cache_write_access);
 | |
| 
 | |
|   /* Schedule any cleanup tasks for this process. */
 | |
|   _queue.add (new process_cleanup (process));
 | |
| }
 | |
| 
 | |
| class process *
 | |
| process_cache::find (const DWORD winpid, class process **previous)
 | |
| {
 | |
|   if (previous)
 | |
|     *previous = NULL;
 | |
| 
 | |
|   for (class process *ptr = _processes_head; ptr; ptr = ptr->_next)
 | |
|     if (ptr->_winpid == winpid)
 | |
|       return ptr;
 | |
|     else if (ptr->_winpid > winpid) // The list is sorted by winpid.
 | |
|       return NULL;
 | |
|     else if (previous)
 | |
|       *previous = ptr;
 | |
| 
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| /*****************************************************************************/
 | |
| #endif /* __OUTSIDE_CYGWIN__ */
 |