diff options
Diffstat (limited to 'mDNSResponder/mDNSWindows/Poll.c')
-rwxr-xr-x | mDNSResponder/mDNSWindows/Poll.c | 728 |
1 files changed, 728 insertions, 0 deletions
diff --git a/mDNSResponder/mDNSWindows/Poll.c b/mDNSResponder/mDNSWindows/Poll.c new file mode 100755 index 00000000..9adc632b --- /dev/null +++ b/mDNSResponder/mDNSWindows/Poll.c @@ -0,0 +1,728 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Poll.h" +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <winsock2.h> +#include <ws2tcpip.h> +#include <windows.h> +#include <process.h> +#include "GenLinkedList.h" +#include "DebugServices.h" + + +typedef struct PollSource_struct +{ + SOCKET socket; + HANDLE handle; + void *context; + + union + { + mDNSPollSocketCallback socket; + mDNSPollEventCallback event; + } callback; + + struct Worker_struct *worker; + struct PollSource_struct *next; + +} PollSource; + + +typedef struct Worker_struct +{ + HANDLE thread; // NULL for main worker + unsigned id; // 0 for main worker + + HANDLE start; // NULL for main worker + HANDLE stop; // NULL for main worker + BOOL done; // Not used for main worker + + DWORD numSources; + PollSource *sources[ MAXIMUM_WAIT_OBJECTS ]; + HANDLE handles[ MAXIMUM_WAIT_OBJECTS ]; + DWORD result; + struct Worker_struct *next; +} Worker; + + +typedef struct Poll_struct +{ + mDNSBool setup; + HANDLE wakeup; + GenLinkedList sources; + DWORD numSources; + Worker main; + GenLinkedList workers; + HANDLE workerHandles[ MAXIMUM_WAIT_OBJECTS ]; + DWORD numWorkers; + +} Poll; + + +/* + * Poll Methods + */ + +mDNSlocal mStatus PollSetup(); +mDNSlocal mStatus PollRegisterSource( PollSource *source ); +mDNSlocal void PollUnregisterSource( PollSource *source ); +mDNSlocal mStatus PollStartWorkers(); +mDNSlocal mStatus PollStopWorkers(); +mDNSlocal void PollRemoveWorker( Worker *worker ); + + +/* + * Worker Methods + */ + +mDNSlocal mStatus WorkerInit( Worker *worker ); +mDNSlocal void WorkerFree( Worker *worker ); +mDNSlocal void WorkerRegisterSource( Worker *worker, PollSource *source ); +mDNSlocal int WorkerSourceToIndex( Worker *worker, PollSource *source ); +mDNSlocal void WorkerUnregisterSource( Worker *worker, PollSource *source ); +mDNSlocal void WorkerDispatch( Worker *worker); +mDNSlocal void CALLBACK WorkerWakeupNotification( HANDLE event, void *context ); +mDNSlocal unsigned WINAPI WorkerMain( LPVOID inParam ); + + +static void +ShiftDown( void * arr, size_t arraySize, size_t itemSize, int index ) +{ + memmove( ( ( unsigned char* ) arr ) + ( ( index - 1 ) * itemSize ), ( ( unsigned char* ) arr ) + ( index * itemSize ), ( arraySize - index ) * itemSize ); +} + + +#define DEBUG_NAME "[mDNSWin32] " +#define gMDNSRecord mDNSStorage +mDNSlocal Poll gPoll = { mDNSfalse, NULL }; + +#define LogErr( err, FUNC ) LogMsg( "%s:%d - %s failed: %d\n", __FUNCTION__, __LINE__, FUNC, err ); + + +mStatus +mDNSPollRegisterSocket( SOCKET socket, int networkEvents, mDNSPollSocketCallback callback, void *context ) +{ + PollSource *source = NULL; + HANDLE event = INVALID_HANDLE_VALUE; + mStatus err = mStatus_NoError; + + if ( !gPoll.setup ) + { + err = PollSetup(); + require_noerr( err, exit ); + } + + source = malloc( sizeof( PollSource ) ); + require_action( source, exit, err = mStatus_NoMemoryErr ); + + event = WSACreateEvent(); + require_action( event, exit, err = mStatus_NoMemoryErr ); + + err = WSAEventSelect( socket, event, networkEvents ); + require_noerr( err, exit ); + + source->socket = socket; + source->handle = event; + source->callback.socket = callback; + source->context = context; + + err = PollRegisterSource( source ); + require_noerr( err, exit ); + +exit: + + if ( err != mStatus_NoError ) + { + if ( event != INVALID_HANDLE_VALUE ) + { + WSACloseEvent( event ); + } + + if ( source != NULL ) + { + free( source ); + } + } + + return err; +} + + +void +mDNSPollUnregisterSocket( SOCKET socket ) +{ + PollSource *source; + + for ( source = gPoll.sources.Head; source; source = source->next ) + { + if ( source->socket == socket ) + { + break; + } + } + + if ( source ) + { + WSACloseEvent( source->handle ); + PollUnregisterSource( source ); + free( source ); + } +} + + +mStatus +mDNSPollRegisterEvent( HANDLE event, mDNSPollEventCallback callback, void *context ) +{ + PollSource *source = NULL; + mStatus err = mStatus_NoError; + + if ( !gPoll.setup ) + { + err = PollSetup(); + require_noerr( err, exit ); + } + + source = malloc( sizeof( PollSource ) ); + require_action( source, exit, err = mStatus_NoMemoryErr ); + + source->socket = INVALID_SOCKET; + source->handle = event; + source->callback.event = callback; + source->context = context; + + err = PollRegisterSource( source ); + require_noerr( err, exit ); + +exit: + + if ( err != mStatus_NoError ) + { + if ( source != NULL ) + { + free( source ); + } + } + + return err; +} + + +void +mDNSPollUnregisterEvent( HANDLE event ) +{ + PollSource *source; + + for ( source = gPoll.sources.Head; source; source = source->next ) + { + if ( source->handle == event ) + { + break; + } + } + + if ( source ) + { + PollUnregisterSource( source ); + free( source ); + } +} + + +mStatus +mDNSPoll( DWORD msec ) +{ + mStatus err = mStatus_NoError; + + if ( gPoll.numWorkers > 0 ) + { + err = PollStartWorkers(); + require_noerr( err, exit ); + } + + gPoll.main.result = WaitForMultipleObjects( gPoll.main.numSources, gPoll.main.handles, FALSE, msec ); + err = translate_errno( ( gPoll.main.result != WAIT_FAILED ), ( mStatus ) GetLastError(), kUnknownErr ); + if ( err ) LogErr( err, "WaitForMultipleObjects()" ); + require_action( gPoll.main.result != WAIT_FAILED, exit, err = ( mStatus ) GetLastError() ); + + if ( gPoll.numWorkers > 0 ) + { + err = PollStopWorkers(); + require_noerr( err, exit ); + } + + WorkerDispatch( &gPoll.main ); + +exit: + + return ( err ); +} + + +mDNSlocal mStatus +PollSetup() +{ + mStatus err = mStatus_NoError; + + if ( !gPoll.setup ) + { + memset( &gPoll, 0, sizeof( gPoll ) ); + + InitLinkedList( &gPoll.sources, offsetof( PollSource, next ) ); + InitLinkedList( &gPoll.workers, offsetof( Worker, next ) ); + + gPoll.wakeup = CreateEvent( NULL, TRUE, FALSE, NULL ); + require_action( gPoll.wakeup, exit, err = mStatus_NoMemoryErr ); + + err = WorkerInit( &gPoll.main ); + require_noerr( err, exit ); + + gPoll.setup = mDNStrue; + } + +exit: + + return err; +} + + +mDNSlocal mStatus +PollRegisterSource( PollSource *source ) +{ + Worker *worker = NULL; + mStatus err = mStatus_NoError; + + AddToTail( &gPoll.sources, source ); + gPoll.numSources++; + + // First check our main worker. In most cases, we won't have to worry about threads + + if ( gPoll.main.numSources < MAXIMUM_WAIT_OBJECTS ) + { + WorkerRegisterSource( &gPoll.main, source ); + } + else + { + // Try to find a thread to use that we've already created + + for ( worker = gPoll.workers.Head; worker; worker = worker->next ) + { + if ( worker->numSources < MAXIMUM_WAIT_OBJECTS ) + { + WorkerRegisterSource( worker, source ); + break; + } + } + + // If not, then create a worker and make a thread to run it in + + if ( !worker ) + { + worker = ( Worker* ) malloc( sizeof( Worker ) ); + require_action( worker, exit, err = mStatus_NoMemoryErr ); + + memset( worker, 0, sizeof( Worker ) ); + + worker->start = CreateEvent( NULL, FALSE, FALSE, NULL ); + require_action( worker->start, exit, err = mStatus_NoMemoryErr ); + + worker->stop = CreateEvent( NULL, FALSE, FALSE, NULL ); + require_action( worker->stop, exit, err = mStatus_NoMemoryErr ); + + err = WorkerInit( worker ); + require_noerr( err, exit ); + + // Create thread with _beginthreadex() instead of CreateThread() to avoid + // memory leaks when using static run-time libraries. + // See <http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/createthread.asp>. + + worker->thread = ( HANDLE ) _beginthreadex_compat( NULL, 0, WorkerMain, worker, 0, &worker->id ); + err = translate_errno( worker->thread, ( mStatus ) GetLastError(), kUnknownErr ); + require_noerr( err, exit ); + + AddToTail( &gPoll.workers, worker ); + gPoll.workerHandles[ gPoll.numWorkers++ ] = worker->stop; + + WorkerRegisterSource( worker, source ); + } + } + +exit: + + if ( err && worker ) + { + WorkerFree( worker ); + } + + return err; +} + + +mDNSlocal void +PollUnregisterSource( PollSource *source ) +{ + RemoveFromList( &gPoll.sources, source ); + gPoll.numSources--; + + WorkerUnregisterSource( source->worker, source ); +} + + +mDNSlocal mStatus +PollStartWorkers() +{ + Worker *worker; + mStatus err = mStatus_NoError; + BOOL ok; + + dlog( kDebugLevelChatty, DEBUG_NAME "starting workers\n" ); + + worker = gPoll.workers.Head; + + while ( worker ) + { + Worker *next = worker->next; + + if ( worker->numSources == 1 ) + { + PollRemoveWorker( worker ); + } + else + { + dlog( kDebugLevelChatty, DEBUG_NAME "waking up worker\n" ); + + ok = SetEvent( worker->start ); + err = translate_errno( ok, ( mStatus ) GetLastError(), kUnknownErr ); + if ( err ) LogErr( err, "SetEvent()" ); + + if ( err ) + { + PollRemoveWorker( worker ); + } + } + + worker = next; + } + + err = mStatus_NoError; + + return err; +} + + +mDNSlocal mStatus +PollStopWorkers() +{ + DWORD result; + Worker *worker; + BOOL ok; + mStatus err = mStatus_NoError; + + dlog( kDebugLevelChatty, DEBUG_NAME "stopping workers\n" ); + + ok = SetEvent( gPoll.wakeup ); + err = translate_errno( ok, ( mStatus ) GetLastError(), kUnknownErr ); + if ( err ) LogErr( err, "SetEvent()" ); + + // Wait For 5 seconds for all the workers to wake up + + result = WaitForMultipleObjects( gPoll.numWorkers, gPoll.workerHandles, TRUE, 5000 ); + err = translate_errno( ( result != WAIT_FAILED ), ( mStatus ) GetLastError(), kUnknownErr ); + if ( err ) LogErr( err, "WaitForMultipleObjects()" ); + + ok = ResetEvent( gPoll.wakeup ); + err = translate_errno( ok, ( mStatus ) GetLastError(), kUnknownErr ); + if ( err ) LogErr( err, "ResetEvent()" ); + + for ( worker = gPoll.workers.Head; worker; worker = worker->next ) + { + WorkerDispatch( worker ); + } + + err = mStatus_NoError; + + return err; +} + + +mDNSlocal void +PollRemoveWorker( Worker *worker ) +{ + DWORD result; + mStatus err; + BOOL ok; + DWORD i; + + dlog( kDebugLevelChatty, DEBUG_NAME "removing worker %d\n", worker->id ); + + RemoveFromList( &gPoll.workers, worker ); + + // Remove handle from gPoll.workerHandles + + for ( i = 0; i < gPoll.numWorkers; i++ ) + { + if ( gPoll.workerHandles[ i ] == worker->stop ) + { + ShiftDown( gPoll.workerHandles, gPoll.numWorkers, sizeof( gPoll.workerHandles[ 0 ] ), i + 1 ); + break; + } + } + + worker->done = TRUE; + gPoll.numWorkers--; + + // Cause the thread to exit. + + ok = SetEvent( worker->start ); + err = translate_errno( ok, ( OSStatus ) GetLastError(), kUnknownErr ); + if ( err ) LogErr( err, "SetEvent()" ); + + result = WaitForSingleObject( worker->thread, 5000 ); + err = translate_errno( result != WAIT_FAILED, ( OSStatus ) GetLastError(), kUnknownErr ); + if ( err ) LogErr( err, "WaitForSingleObject()" ); + + if ( ( result == WAIT_FAILED ) || ( result == WAIT_TIMEOUT ) ) + { + ok = TerminateThread( worker->thread, 0 ); + err = translate_errno( ok, ( OSStatus ) GetLastError(), kUnknownErr ); + if ( err ) LogErr( err, "TerminateThread()" ); + } + + CloseHandle( worker->thread ); + worker->thread = NULL; + + WorkerFree( worker ); +} + + +mDNSlocal void +WorkerRegisterSource( Worker *worker, PollSource *source ) +{ + source->worker = worker; + worker->sources[ worker->numSources ] = source; + worker->handles[ worker->numSources ] = source->handle; + worker->numSources++; +} + + +mDNSlocal int +WorkerSourceToIndex( Worker *worker, PollSource *source ) +{ + int index; + + for ( index = 0; index < ( int ) worker->numSources; index++ ) + { + if ( worker->sources[ index ] == source ) + { + break; + } + } + + if ( index == ( int ) worker->numSources ) + { + index = -1; + } + + return index; +} + + +mDNSlocal void +WorkerUnregisterSource( Worker *worker, PollSource *source ) +{ + int sourceIndex = WorkerSourceToIndex( worker, source ); + DWORD delta; + + if ( sourceIndex == -1 ) + { + LogMsg( "WorkerUnregisterSource: source not found in list" ); + goto exit; + } + + delta = ( worker->numSources - sourceIndex - 1 ); + + // If this source is not at the end of the list, then move memory + + if ( delta > 0 ) + { + ShiftDown( worker->sources, worker->numSources, sizeof( worker->sources[ 0 ] ), sourceIndex + 1 ); + ShiftDown( worker->handles, worker->numSources, sizeof( worker->handles[ 0 ] ), sourceIndex + 1 ); + } + + worker->numSources--; + +exit: + + return; +} + + +mDNSlocal void CALLBACK +WorkerWakeupNotification( HANDLE event, void *context ) +{ + DEBUG_UNUSED( event ); + DEBUG_UNUSED( context ); + + dlog( kDebugLevelChatty, DEBUG_NAME "Worker thread wakeup\n" ); +} + + +mDNSlocal void +WorkerDispatch( Worker *worker ) +{ + if ( worker->result == WAIT_FAILED ) + { + /* What should we do here? */ + } + else if ( worker->result == WAIT_TIMEOUT ) + { + dlog( kDebugLevelChatty, DEBUG_NAME "timeout\n" ); + } + else + { + DWORD waitItemIndex = ( DWORD )( ( ( int ) worker->result ) - WAIT_OBJECT_0 ); + PollSource *source = NULL; + + // Sanity check + + if ( waitItemIndex >= worker->numSources ) + { + LogMsg( "WorkerDispatch: waitItemIndex (%d) is >= numSources (%d)", waitItemIndex, worker->numSources ); + goto exit; + } + + source = worker->sources[ waitItemIndex ]; + + if ( source->socket != INVALID_SOCKET ) + { + WSANETWORKEVENTS event; + + if ( WSAEnumNetworkEvents( source->socket, source->handle, &event ) == 0 ) + { + source->callback.socket( source->socket, &event, source->context ); + } + else + { + source->callback.socket( source->socket, NULL, source->context ); + } + } + else + { + source->callback.event( source->handle, source->context ); + } + } + +exit: + + return; +} + + +mDNSlocal mStatus +WorkerInit( Worker *worker ) +{ + PollSource *source = NULL; + mStatus err = mStatus_NoError; + + require_action( worker, exit, err = mStatus_BadParamErr ); + + source = malloc( sizeof( PollSource ) ); + require_action( source, exit, err = mStatus_NoMemoryErr ); + + source->socket = INVALID_SOCKET; + source->handle = gPoll.wakeup; + source->callback.event = WorkerWakeupNotification; + source->context = NULL; + + WorkerRegisterSource( worker, source ); + +exit: + + return err; +} + + +mDNSlocal void +WorkerFree( Worker *worker ) +{ + if ( worker->start ) + { + CloseHandle( worker->start ); + worker->start = NULL; + } + + if ( worker->stop ) + { + CloseHandle( worker->stop ); + worker->stop = NULL; + } + + free( worker ); +} + + +mDNSlocal unsigned WINAPI +WorkerMain( LPVOID inParam ) +{ + Worker *worker = ( Worker* ) inParam; + mStatus err = mStatus_NoError; + + require_action( worker, exit, err = mStatus_BadParamErr ); + + dlog( kDebugLevelVerbose, DEBUG_NAME, "entering WorkerMain()\n" ); + + while ( TRUE ) + { + DWORD result; + BOOL ok; + + dlog( kDebugLevelChatty, DEBUG_NAME, "worker thread %d will wait on main loop\n", worker->id ); + + result = WaitForSingleObject( worker->start, INFINITE ); + err = translate_errno( ( result != WAIT_FAILED ), ( mStatus ) GetLastError(), kUnknownErr ); + if ( err ) { LogErr( err, "WaitForSingleObject()" ); break; } + if ( worker->done ) break; + + dlog( kDebugLevelChatty, DEBUG_NAME "worker thread %d will wait on sockets\n", worker->id ); + + worker->result = WaitForMultipleObjects( worker->numSources, worker->handles, FALSE, INFINITE ); + err = translate_errno( ( worker->result != WAIT_FAILED ), ( mStatus ) GetLastError(), kUnknownErr ); + if ( err ) { LogErr( err, "WaitForMultipleObjects()" ); break; } + + dlog( kDebugLevelChatty, DEBUG_NAME "worker thread %d did wait on sockets: %d\n", worker->id, worker->result ); + + ok = SetEvent( gPoll.wakeup ); + err = translate_errno( ok, ( mStatus ) GetLastError(), kUnknownErr ); + if ( err ) { LogErr( err, "SetEvent()" ); break; } + + dlog( kDebugLevelChatty, DEBUG_NAME, "worker thread %d preparing to sleep\n", worker->id ); + + ok = SetEvent( worker->stop ); + err = translate_errno( ok, ( mStatus ) GetLastError(), kUnknownErr ); + if ( err ) { LogErr( err, "SetEvent()" ); break; } + } + + dlog( kDebugLevelVerbose, DEBUG_NAME "exiting WorkerMain()\n" ); + +exit: + + return 0; +} |