diff options
Diffstat (limited to 'winsup/cygwin/cygserver_process.cc')
-rwxr-xr-x | winsup/cygwin/cygserver_process.cc | 663 |
1 files changed, 353 insertions, 310 deletions
diff --git a/winsup/cygwin/cygserver_process.cc b/winsup/cygwin/cygserver_process.cc index a5173afd3..7118bbcd9 100755 --- a/winsup/cygwin/cygserver_process.cc +++ b/winsup/cygwin/cygserver_process.cc @@ -1,389 +1,432 @@ /* cygserver_process.cc - Copyright 2001 Red Hat Inc. + Copyright 2001, 2002 Red Hat Inc. Written by Robert Collins <rbtcollins@hotmail.com> - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ +#include "woutsup.h" + +#include <sys/types.h> + +#include <assert.h> #include <errno.h> -#include <stdio.h> -#include <unistd.h> #include <stdlib.h> -#include <windows.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netdb.h> -#include "wincap.h" -#include <pthread.h> -#include <threaded_queue.h> -#include <cygwin/cygserver_process.h> - -#define debug_printf if (DEBUG) printf -#define DEBUG 1 - -/* the cache structures and classes are designed for one cache per server process. - * To make multiple process caches, a redesign will be needed - */ -/* process cache */ -process_cache::process_cache (unsigned int num_initial_workers): -head (NULL) +#include "cygerrno.h" + +#include "cygwin/cygserver_process.h" + +/*****************************************************************************/ + +#define elements(ARRAY) (sizeof (ARRAY) / sizeof (*ARRAY)) + +/*****************************************************************************/ + +process_cleanup::~process_cleanup () { - /* there can only be one */ - InitializeCriticalSection (&cache_write_access); - if ((cache_add_trigger = CreateEvent (NULL, FALSE, FALSE, NULL)) == NULL) - { - printf ("Failed to create cache add trigger (%lu), terminating\n", - GetLastError ()); - exit (1); - } - initial_workers = num_initial_workers; + safe_delete (_process); } -process_cache::~process_cache () +void +process_cleanup::process () { + _process->cleanup (); } -class process * -process_cache::process (long pid) +/*****************************************************************************/ + +/* cleanup_routine */ +cleanup_routine::~cleanup_routine () { - class process *entry = head; - /* TODO: make this more granular, so a search doesn't involve the write lock */ - EnterCriticalSection (&cache_write_access); - if (!entry) +} + +/*****************************************************************************/ + +process::process (const pid_t cygpid, const DWORD winpid) + : _cygpid (cygpid), + _winpid (winpid), + _hProcess (NULL), + _cleaning_up (false), + _exit_status (STILL_ACTIVE), + _routines_head (NULL), + _next (NULL) +{ + _hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, winpid); + if (!_hProcess) { - entry = new class process (pid); - entry->next = - (class process *) InterlockedExchangePointer (&head, entry); - PulseEvent (cache_add_trigger); + system_printf ("unable to obtain handle for new cache process %d(%lu)", + _cygpid, _winpid); + _hProcess = INVALID_HANDLE_VALUE; + _exit_status = 0; } else - { - while (entry->winpid != pid && entry->next) - entry = entry->next; - if (entry->winpid != pid) - { - class process *new_entry = new class process (pid); - new_entry->next = - (class process *) InterlockedExchangePointer (&entry->next, - new_entry); - entry = new_entry; - PulseEvent (cache_add_trigger); - } - } - LeaveCriticalSection (&cache_write_access); - return entry; + debug_printf ("got handle %p for new cache process %d(%lu)", + _hProcess, _cygpid, _winpid); + InitializeCriticalSection (&_access); } -static DWORD WINAPI -request_loop (LPVOID LpParam) +process::~process () { - class process_process_param *params = (process_process_param *) LpParam; - return params->request_loop (); + DeleteCriticalSection (&_access); + (void) CloseHandle (_hProcess); } -void -process_cache::process_requests () +/* No need to be thread-safe as this is only ever called by + * process_cache::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 () { - class process_process_param *params = new process_process_param; - threaded_queue::process_requests (params, request_loop); + if (_hProcess && _hProcess != INVALID_HANDLE_VALUE + && _exit_status == STILL_ACTIVE + && !GetExitCodeProcess (_hProcess, &_exit_status)) + { + system_printf ("failed to retrieve exit code for %d(%lu), error = %lu", + _cygpid, _winpid, GetLastError ()); + _hProcess = INVALID_HANDLE_VALUE; + } + return _exit_status; } -void -process_cache::add_task (class process * theprocess) +bool +process::add (cleanup_routine *const entry) { - /* safe to not "Try" because workers don't hog this, they wait on the event - */ - /* every derived ::add must enter the section! */ - EnterCriticalSection (&queuelock); - queue_request *listrequest = new process_cleanup (theprocess); - threaded_queue::add (listrequest); - LeaveCriticalSection (&queuelock); -} + assert (entry); -/* NOT fully MT SAFE: must be called by only one thread in a program */ -void -process_cache::remove_process (class process *theprocess) -{ - class process *entry = head; - /* unlink */ - EnterCriticalSection (&cache_write_access); - if (entry == theprocess) + bool res = false; + EnterCriticalSection (&_access); + + if (!_cleaning_up) { - entry = (class process *) InterlockedExchangePointer (&head, theprocess->next); - if (entry != theprocess) - { - printf ("Bug encountered, process cache corrupted\n"); - exit (1); - } + entry->_next = _routines_head; + _routines_head = entry; + res = true; } - else + + LeaveCriticalSection (&_access); + return res; +} + +bool +process::remove (const cleanup_routine *const entry) +{ + assert (entry); + + bool res = false; + EnterCriticalSection (&_access); + + if (!_cleaning_up) { - while (entry->next && entry->next != theprocess) - entry = entry->next; - class process *temp = (class process *) InterlockedExchangePointer (&entry->next, theprocess->next); - if (temp != theprocess) + cleanup_routine *previous = NULL; + + for (cleanup_routine *ptr = _routines_head; + ptr; + previous = ptr, ptr = ptr->_next) { - printf ("Bug encountered, process cache corrupted\n"); - exit (1); + if (*ptr == *entry) + { + if (previous) + previous->_next = ptr->_next; + else + _routines_head = ptr->_next; + + safe_delete (ptr); + res = true; + break; + } } } - LeaveCriticalSection (&cache_write_access); - /* Process any cleanup tasks */ - add_task (theprocess); + + LeaveCriticalSection (&_access); + return res; } - - -/* copy <= max_copy HANDLEs to dest[], starting at an offset into _our list_ of - * begin_at. (Ie begin_at = 5, the first copied handle is still written to dest[0] - * NOTE: Thread safe, but not thread guaranteed - a newly added process may be missed. - * Who cares - It'll get caught the next time. + +/* 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. */ -int -process_cache::handle_snapshot (HANDLE * hdest, class process ** edest, - ssize_t max_copy, int begin_at) +void +process::cleanup () { - /* TODO:? grab a delete-lock, to prevent deletes during this process ? */ - class process *entry = head; - int count = begin_at; - /* skip begin_at entries */ - while (entry && count) - { - if (entry->exit_code () == STILL_ACTIVE) - count--; - entry = entry->next; - } - /* hit the end of the list within begin_at entries */ - if (count) - return 0; - HANDLE *hto = hdest; - class process **eto = edest; - while (entry && count < max_copy) + EnterCriticalSection (&_access); + assert (!is_active ()); + assert (!_cleaning_up); + InterlockedExchange (&_cleaning_up, true); + cleanup_routine *entry = _routines_head; + _routines_head = NULL; + LeaveCriticalSection (&_access); + + while (entry) { - /* hack */ - if (entry->exit_code () == STILL_ACTIVE) - { - *hto = entry->handle (); - *eto = entry; - count++; - hto++; - eto++; - } - entry = entry->next; + cleanup_routine *const ptr = entry; + entry = entry->_next; + ptr->cleanup (this); + safe_delete (ptr); } - return count; } -/* process's */ -/* global process crit section */ -static CRITICAL_SECTION process_access; -static pthread_once_t process_init; +/*****************************************************************************/ void -do_process_init (void) +process_cache::submission_loop::request_loop () { - InitializeCriticalSection (&process_access); - /* we don't have a cache shutdown capability today */ + assert (this); + assert (_cache); + assert (_interrupt_event); + + while (_running) + _cache->wait_for_processes (_interrupt_event); } -process::process (long pid): -winpid (pid), next (NULL), cleaning_up (0), head (NULL), _exit_status (STILL_ACTIVE) +/*****************************************************************************/ + +process_cache::process_cache (const unsigned int initial_workers) + : _queue (initial_workers), + _submitter (this, &_queue), // true == interruptible + _processes_count (0), + _processes_head (NULL), + _cache_add_trigger (NULL) { - pthread_once (&process_init, do_process_init); - EnterCriticalSection (&process_access); - thehandle = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); - if (!thehandle) + /* there can only be one */ + InitializeCriticalSection (&_cache_write_access); + + _cache_add_trigger = CreateEvent (NULL, // SECURITY_ATTRIBUTES + FALSE, // Auto-reset + FALSE, // Initially non-signalled + NULL); // Anonymous + + if (!_cache_add_trigger) { - printf ("unable to obtain handle for new cache process %ld\n", pid); - thehandle = INVALID_HANDLE_VALUE; + system_printf ("failed to create cache add trigger, error = %lu", + GetLastError ()); + abort (); } - debug_printf ("Got handle %p for new cache process %ld\n", thehandle, pid); - InitializeCriticalSection (&access); - LeaveCriticalSection (&process_access); -} -process::~process () -{ - DeleteCriticalSection (&access); + _queue.add_submission_loop (&_submitter); } -HANDLE -process::handle () +process_cache::~process_cache () { -// DWORD exitstate = exit_code (); -// if (exitstate == STILL_ACTIVE) - return thehandle; - - /* FIXME: call the cleanup list ? */ - -// CloseHandle (thehandle); -// debug_printf ("Process id %ld has terminated, attempting to open a new handle\n", -// winpid); -// thehandle = OpenProcess (PROCESS_ALL_ACCESS, FALSE, winpid); -// debug_printf ("Got handle %p when refreshing cache process %ld\n", thehandle, winpid); -// /* FIXME: what if OpenProcess fails ? */ -// if (thehandle) -// { -// _exit_status = STILL_ACTIVE; -// exit_code (); -// } -// else -// thehandle = INVALID_HANDLE_VALUE; -// return thehandle; + (void) CloseHandle (_cache_add_trigger); + DeleteCriticalSection (&_cache_write_access); } -DWORD process::exit_code () +/* 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) { - if (_exit_status != STILL_ACTIVE) - return _exit_status; - bool - err = GetExitCodeProcess (thehandle, &_exit_status); - if (!err) + /* 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) { - debug_printf ("Failed to retrieve exit code (%ld)\n", GetLastError ()); - thehandle = INVALID_HANDLE_VALUE; - return _exit_status; + if (_processes_count + SPECIALS_COUNT >= MAXIMUM_WAIT_OBJECTS) + { + LeaveCriticalSection (&_cache_write_access); + system_printf (("process limit (%d processes) reached; " + "new connection refused for %d(%lu)"), + MAXIMUM_WAIT_OBJECTS - SPECIALS_COUNT, + cygpid, winpid); + set_errno (EAGAIN); + return NULL; + } + + entry = safe_new (class process, cygpid, winpid); + if (!entry->is_active ()) + { + LeaveCriticalSection (&_cache_write_access); + safe_delete (entry); + set_errno (ESRCH); + 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); } - else if (_exit_status == STILL_ACTIVE) - return _exit_status; - /* add new cleanup task etc etc ? */ - return _exit_status; + + EnterCriticalSection (&entry->_access); // To be released by the caller. + LeaveCriticalSection (&_cache_write_access); + assert (entry); + assert (entry->_winpid == winpid); + return entry; } -/* 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 () +process_cache::wait_for_processes (const HANDLE interrupt_event) { - /* Serialize this */ - EnterCriticalSection (&access); - InterlockedIncrement (&(long)cleaning_up); - class cleanup_routine *entry = head; - while (entry) + // Update `_wait_array' with handles of all current processes. + const size_t count = sync_wait_array (interrupt_event); + + debug_printf ("waiting on %u objects in total (%u processes)", + count, _processes_count); + + const DWORD rc = WaitForMultipleObjects (count, _wait_array, + FALSE, INFINITE); + + if (rc == WAIT_FAILED) + { + system_printf ("could not wait on the process handles, error = %lu", + GetLastError ()); + abort (); + } + + const size_t start = rc - WAIT_OBJECT_0; + + if (rc < WAIT_OBJECT_0 || start > count) { - class cleanup_routine *temp; - entry->cleanup (winpid); - temp = entry->next; - delete entry; - entry = temp; + system_printf (("unexpected return code %rc " + "from WaitForMultipleObjects: " + "expected [%u .. %u)"), + rc, WAIT_OBJECT_0, WAIT_OBJECT_0 + count); + abort (); } - LeaveCriticalSection (&access); + + // Tell all the processes, from the signalled point up, the bad news. + for (size_t index = start; index != count; index++) + if (_process_array[index]) + check_and_remove_process (index); } -bool -process::add_cleanup_routine (class cleanup_routine *new_cleanup) +/* + * 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) { - if (cleaning_up) - return false; - EnterCriticalSection (&access); - /* check that we didn't block with ::cleanup () - * This rigmarole is to get around win9x's glaring missing TryEnterCriticalSection call - * which would be a whole lot easier - */ - if (cleaning_up) + assert (this); + assert (_cache_add_trigger && _cache_add_trigger != INVALID_HANDLE_VALUE); + assert (interrupt_event && interrupt_event != INVALID_HANDLE_VALUE); + + EnterCriticalSection (&_cache_write_access); + + assert (_processes_count + SPECIALS_COUNT <= elements (_wait_array)); + + size_t index = 0; + + for (class process *ptr = _processes_head; ptr; ptr = ptr->_next) { - LeaveCriticalSection (&access); - return false; + assert (ptr->_hProcess && ptr->_hProcess != INVALID_HANDLE_VALUE); + assert (ptr->is_active ()); + + _wait_array[index] = ptr->handle (); + _process_array[index++] = ptr; + + assert (index <= elements (_wait_array)); } - new_cleanup->next = head; - head = new_cleanup; - LeaveCriticalSection (&access); - return true; + + /* Sorry for shouting, but THESE MUST BE ADDED AT THE END! */ + /* Well, not strictly `must', but it's more efficient if they are :-) */ + + _wait_array[index] = interrupt_event; + _process_array[index++] = NULL; + + _wait_array[index] = _cache_add_trigger; + _process_array[index++] = NULL; + + /* Phew, back to normal volume now. */ + + assert (index <= elements (_wait_array)); + + LeaveCriticalSection (&_cache_write_access); + + return index; } -/* process_cleanup */ void -process_cleanup::process () +process_cache::check_and_remove_process (const size_t index) { - theprocess->cleanup (); - delete theprocess; + 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(%lu) has left the building ($? = %lu)", + 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 (safe_new (process_cleanup, process)); } -/* process_process_param */ -DWORD -process_process_param::request_loop () +class process * +process_cache::find (const DWORD winpid, class process **previous) { - process_cache *cache = (process_cache *) queue; - /* always malloc one, so there is no special case in the loop */ - ssize_t HandlesSize = 2; - HANDLE *Handles = (HANDLE *) malloc (sizeof (HANDLE) * HandlesSize); - process **Entries = (process **) malloc (sizeof (LPVOID) * HandlesSize); - /* TODO: put [1] at the end as it will also get done if a process dies? */ - Handles[0] = interrupt; - Handles[1] = cache->cache_add_trigger; - while (cache->active && !shutdown) - { - int copied; - copied = -1; - int offset; - offset = 1; - int count; - count = 2; - while ((copied == HandlesSize - 2 - offset) || copied < 0) - { - /* we need more storage to cope with all the HANDLES */ - if (copied == HandlesSize - 2 - offset) - { - HANDLE *temp = (HANDLE *) realloc (Handles, - sizeof (HANDLE) * - HandlesSize + 10); - if (!temp) - { - printf - ("cannot allocate more storage for the handle array!\n"); - exit (1); - } - Handles = temp; - process **ptemp = (process **) realloc (Entries, - sizeof (LPVOID) * - HandlesSize + 10); - if (!ptemp) - { - printf - ("cannot allocate more storage for the handle array!\n"); - exit (1); - } - Entries = ptemp; - HandlesSize += 10; - } - offset += copied; - copied = - cache->handle_snapshot (&Handles[2], &Entries[2], - HandlesSize - 2 - offset, offset); - count += copied; - } - debug_printf ("waiting on %u objects\n", count); - DWORD rc = WaitForMultipleObjects (count, Handles, FALSE, INFINITE); - if (rc == WAIT_FAILED) - { - printf ("Could not wait on the process handles (%ld)!\n", - GetLastError ()); - exit (1); - } - int objindex = rc - WAIT_OBJECT_0; - if (objindex > 1 && objindex < count) - { - debug_printf ("Process %ld has left the building\n", - Entries[objindex]->winpid); - /* fire off the termination routines */ - cache->remove_process (Entries[objindex]); - } - else if (objindex >= 0 && objindex < 2) - { - /* 0 is shutdown - do nothing */ - /* 1 is a cache add event - just rebuild the object list */ - } - else - { - printf - ("unexpected return code from WaitForMultiple objects in process_process_param::request_loop\n"); - } - } - running = false; - return 0; + 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; } + +/*****************************************************************************/ |