blob: 9adc632b717a4548cd6f36ea5f2f4990b46f0969 [file] [log] [blame]
/* -*- 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;
}