/**********************************************************************/ /** Microsoft Windows NT **/ /** Copyright(c) Microsoft Corp., 1993 **/ /**********************************************************************/ /* aap.cxx This module contains implementation of the Asynchronous Accept Pool package. Functions exported by this module: AapInitialize AapTerminate AapAcquire AapRelease AapAccept AapAbort FILE HISTORY: KeithMo 01-Mar-1995 Created. */ #include "ftpdp.hxx" // // Private constants. // #define MAX_IDLE_THREADS 10 #define AapLockLists() EnterCriticalSection( &p_AapListLock ) #define AapUnlockLists() LeaveCriticalSection( &p_AapListLock ) // // Private types. // typedef enum _AAP_STATE { AapStateFirst = -1, // Must be first aap state! AapStateIdle, // Idle. AapStateActive, // Waiting for incoming connection. AapStateAbort, // Aborting, return to idle. AapStateShutdown, // Shutting down. AapStateLast // Must be last aap state! } AAP_STATE; #define IS_VALID_AAP_STATE(x) (((x) > AapStateFirst) && ((x) < AapStateLast)) typedef struct _AAP_CONTEXT { // // Structure signature, for safety's sake. // DEBUG_SIGNATURE // // Links onto a list of idle/active contexts. // LIST_ENTRY ContextList; // // Current state. // AAP_STATE State; // // The listening socket. // SOCKET ListeningSocket; // // An event object for synchronizing with the worker thread. // HANDLE EventHandle; // // A handle to the worker thread for synchronizing shutdown. // HANDLE ThreadHandle; // // The callback associated with this context. // LPAAP_CALLBACK AapCallback; // // A user-definable context. // LPVOID UserContext; } AAP_CONTEXT, * LPAAP_CONTEXT; #if DBG #define AAP_SIGNATURE (DWORD)'cPaA' #define AAP_SIGNATURE_X (DWORD)'paaX' #define INIT_AAP_SIG(p) ((p)->Signature = AAP_SIGNATURE) #define KILL_AAP_SIG(p) ((p)->Signature = AAP_SIGNATURE_X) #define IS_VALID_AAP_CONTEXT(p) (((p) != NULL) && ((p)->Signature == AAP_SIGNATURE)) #else // !DBG #define INIT_AAP_SIG(p) ((void)(p)) #define KILL_AAP_SIG(p) ((void)(p)) #define IS_VALID_AAP_CONTEXT(p) (((void)(p)), TRUE) #endif // DBG // // Private globals. // LIST_ENTRY p_AapIdleList; LIST_ENTRY p_AapActiveList; CRITICAL_SECTION p_AapListLock; DWORD p_AapIdleCount; // // Private prototypes. // LPAAP_CONTEXT AappCreateContext( VOID ); VOID AappFreeResources( LPAAP_CONTEXT AapContext ); DWORD AappWorkerThread( LPVOID Param ); // // Public functions. // /******************************************************************* NAME: AapInitialize SYNOPSIS: Initializes the AAP package. EXIT: APIERR - 0 if successful, !0 if not. HISTORY: KeithMo 01-Mar-1995 Created. ********************************************************************/ APIERR AapInitialize( VOID ) { IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "in AapInitialize\n" )); } // // Initialize the critical section. // INITIALIZE_CRITICAL_SECTION( &p_AapListLock ); // // Initialize the worker lists. // InitializeListHead( &p_AapIdleList ); InitializeListHead( &p_AapActiveList ); p_AapIdleCount = 0; // // Success! // return 0; } // AapInitialize /******************************************************************* NAME: AapTerminate SYNOPSIS: Terminates the AAP package. HISTORY: KeithMo 01-Mar-1995 Created. ********************************************************************/ VOID AapTerminate( VOID ) { PLIST_ENTRY Entry; IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "in AapTerminate\n" )); } // // Lock the lists. // AapLockLists(); // // Scan the idle list and blow them away. // Entry = p_AapIdleList.Flink; while( Entry != &p_AapIdleList ) { LPAAP_CONTEXT AapContext; AapContext = CONTAINING_RECORD( Entry, AAP_CONTEXT, ContextList ); Entry = Entry->Flink; DBG_ASSERT( IS_VALID_AAP_CONTEXT( AapContext ) ); DBG_ASSERT( AapContext->State == AapStateIdle ); IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AapTerminate: killing idle context @ %08lX\n", AapContext )); } // // Set the state to closing so the thread will know to exit. // AapContext->State = AapStateShutdown; // // Signal the event to wake up the thread. // DBG_ASSERT( AapContext->EventHandle != NULL ); TCP_REQUIRE( SetEvent( AapContext->EventHandle ) ); } // // Scan the active list and blow them away. // Entry = p_AapActiveList.Flink; while( Entry != &p_AapActiveList ) { LPAAP_CONTEXT AapContext; SOCKET Socket; AapContext = CONTAINING_RECORD( Entry, AAP_CONTEXT, ContextList ); Entry = Entry->Flink; DBG_ASSERT( IS_VALID_AAP_CONTEXT( AapContext ) ); DBG_ASSERT( AapContext->State == AapStateActive ); IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AapTerminate: killing active context @ %08lX\n", AapContext )); } // // Set the state to closing so the thread will know to exit. // AapContext->State = AapStateShutdown; // // Close the listening socket to wake up the thread. // Socket = AapContext->ListeningSocket; DBG_ASSERT( Socket != INVALID_SOCKET ); AapContext->ListeningSocket = INVALID_SOCKET; TCP_REQUIRE( closesocket( Socket ) == 0 ); DBG_ASSERT( AapContext->EventHandle != NULL ); TCP_REQUIRE( SetEvent( AapContext->EventHandle ) ); } // // Wait for the worker threads to exit. Note that the list // lock is currently held. // for( ; ; ) { LPAAP_CONTEXT AapContext; // // Find a thread to wait on. If both lists are empty, // then we're done. // if( IsListEmpty( &p_AapIdleList ) ) { if( IsListEmpty( &p_AapActiveList ) ) { break; } AapContext = CONTAINING_RECORD( p_AapActiveList.Flink, AAP_CONTEXT, ContextList ); } else { AapContext = CONTAINING_RECORD( p_AapIdleList.Flink, AAP_CONTEXT, ContextList ); } DBG_ASSERT( IS_VALID_AAP_CONTEXT( AapContext ) ); DBG_ASSERT( AapContext->State == AapStateShutdown ); // // Unlock the lists, wait for the thread to exit, then // relock the lists. // AapUnlockLists(); WaitForSingleObject( AapContext->ThreadHandle, INFINITE ); AapLockLists(); // // Note that worker threads will neither close their thread // handles nor free their AAP_CONTEXT structures during shutdown. // This is our responsibility. // AappFreeResources( AapContext ); } DBG_ASSERT( p_AapIdleCount == 0 ); // // Unlock the lists. // AapUnlockLists(); } // AapTerminate /******************************************************************* NAME: AapAcquire SYNOPSIS: Acquires an AAP socket/thread pair for a future asynchronous accept request. ENTRY: AapCallback - Pointer to a callback function to be invoked when the accept() completes. UserContext - An uninterpreted context value passed into the callback. EXIT: AAP_HANDLE - A valid AAP handle if !NULL, NULL if an error occurred. HISTORY: KeithMo 01-Mar-1995 Created. ********************************************************************/ AAP_HANDLE AapAcquire( LPAAP_CALLBACK AapCallback, LPVOID UserContext ) { PLIST_ENTRY Entry; LPAAP_CONTEXT AapContext; // // Sanity check. // DBG_ASSERT( AapCallback != NULL ); IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AapAcquire: callback @ %08lX, context = %08lX\n", AapCallback, UserContext )); } // // See if there's something available on the idle list. // AapLockLists(); if( !IsListEmpty( &p_AapIdleList ) ) { Entry = RemoveHeadList( &p_AapIdleList ); AapContext = CONTAINING_RECORD( Entry, AAP_CONTEXT, ContextList ); DBG_ASSERT( IS_VALID_AAP_CONTEXT( AapContext ) ); DBG_ASSERT( AapContext->State == AapStateIdle ); DBG_ASSERT( p_AapIdleCount > 0 ); p_AapIdleCount--; IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AapAcquire: got idle context @ %08lX\n", AapContext )); } } else { // // Create a new one. // AapContext = AappCreateContext(); } if( AapContext != NULL ) { // // Initialize it. // AapContext->AapCallback = AapCallback; AapContext->UserContext = UserContext; AapContext->State = AapStateActive; // // Put it on the active list. // InsertHeadList( &p_AapActiveList, &AapContext->ContextList ); } // // Unlock the lists & return the (potentially NULL) context. // AapUnlockLists(); IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AapAcquire: returning context @ %08lX\n", AapContext )); } return AapContext; } // AapAcquire /******************************************************************* NAME: AapRelease SYNOPSIS: Releases an open AAP handle and makes the associated socket/thread pair available for use. ENTRY: AapHandle - A valid AAP handle returned by AapAcquire(). After this API completes, the handle is no longer valid. HISTORY: KeithMo 01-Mar-1995 Created. ********************************************************************/ VOID AapRelease( AAP_HANDLE AapHandle ) { // // Sanity check. // DBG_ASSERT( IS_VALID_AAP_CONTEXT( AapHandle ) ); DBG_ASSERT( AapHandle->State == AapStateActive ); IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AapRelease: releasing context @ %08lX\n", AapHandle )); } // // Lock the lists. // AapLockLists(); // // let it decide how to handle this. // AapHandle->State = AapStateAbort; DBG_ASSERT( AapHandle->EventHandle != NULL ); TCP_REQUIRE( SetEvent( AapHandle->EventHandle ) ); // // Unlock the lists. // AapUnlockLists(); } // AapRelease /******************************************************************* NAME: AapAccept SYNOPSIS: Initiates an accept(). The callback associated with the AAP handle will be invoked when the accept() completes. ENTRY: AapHandle - A valid AAP handle returned by AapAcquire(). EXIT: APIERR - 0 if successful, !0 if not. HISTORY: KeithMo 01-Mar-1995 Created. ********************************************************************/ APIERR AapAccept( AAP_HANDLE AapHandle ) { // // Sanity check. // DBG_ASSERT( IS_VALID_AAP_CONTEXT( AapHandle ) ); DBG_ASSERT( AapHandle->State == AapStateActive ); IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AapAccept: context @ %08lX\n", AapHandle )); } // // Release the worker thread. // TCP_REQUIRE( SetEvent( AapHandle->EventHandle ) ); return 0; } // AapAccept /******************************************************************* NAME: AapAbort SYNOPSIS: Aborts a pending accept(). ENTRY: AapHandle - A valid AAP handle returned by AapAcquire(). EXIT: APIERR - 0 if successful, !0 if not. HISTORY: KeithMo 01-Mar-1995 Created. ********************************************************************/ APIERR AapAbort( AAP_HANDLE AapHandle ) { // // Sanity check. // DBG_ASSERT( IS_VALID_AAP_CONTEXT( AapHandle ) ); DBG_ASSERT( AapHandle->State == AapStateActive ); IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AapAbort: context @ %08lX\n", AapHandle )); } // // Just zap the listening handle. The worker thread will clean // up after itself. // TCP_REQUIRE( closesocket( AapHandle->ListeningSocket ) == 0 ); return 0; } // AapAbort // // Private functions. // /******************************************************************* NAME: AappCreateContext SYNOPSIS: Creates a new AAP context, including the associated thread, socket, and synchronization objects. RETURNS: LPAAP_CONTEXT - The newly created context if successful, NULL otherwise. HISTORY: KeithMo 01-Mar-1995 Created. ********************************************************************/ LPAAP_CONTEXT AappCreateContext( VOID ) { LPAAP_CONTEXT AapContext; SOCKADDR_IN LocalAddress; DWORD ThreadId; // // Create the context structure. // AapContext = (LPAAP_CONTEXT)TCP_ALLOC( sizeof(AAP_CONTEXT) ); if( AapContext == NULL ) { DBGERROR(( DBG_CONTEXT, "AappCreateContext: allocation failure\n" )); goto FatalExit0; } RtlZeroMemory( AapContext, sizeof(AAP_CONTEXT) ); INIT_AAP_SIG( AapContext ); // // Create the listening socket. // AapContext->ListeningSocket = socket( AF_INET, SOCK_STREAM, 0 ); if( AapContext->ListeningSocket == INVALID_SOCKET) { DBGERROR(( DBG_CONTEXT, "AappCreateContext: socket() failure %d\n", WSAGetLastError() )); goto FatalExit1; } LocalAddress.sin_family = AF_INET; LocalAddress.sin_port = 0; LocalAddress.sin_addr.s_addr = 0; if( bind( AapContext->ListeningSocket, (LPSOCKADDR)&LocalAddress, sizeof(LocalAddress) ) != 0 ) { DBGERROR(( DBG_CONTEXT, "AappCreateContext: bind() failure %d\n", WSAGetLastError() )); goto FatalExit2; } if( listen( AapContext->ListeningSocket, 2 ) != 0 ) { DBGERROR(( DBG_CONTEXT, "AappCreateContext: listen() failure %d\n", WSAGetLastError() )); goto FatalExit2; } // // Create the event object. // AapContext->EventHandle = CreateEvent( NULL, // lpEventAttributes FALSE, // bManualReset FALSE, // bInitialState NULL ); // lpName if( AapContext->EventHandle == NULL ) { DBGWARN(( DBG_CONTEXT, "AappCreateContext: CreateEvent() failure %d\n", GetLastError() )); goto FatalExit2; } // // Create the worker thread. // AapContext->ThreadHandle = CreateThread( NULL, // lpsa 0, // cbStack AappWorkerThread, // lpStartAddr AapContext, // lpvThreadParm 0, // fdwCreate &ThreadId ); // lpIDThread if( AapContext->ThreadHandle == NULL ) { DBGPRINTF(( DBG_CONTEXT, "AappCreateContext: CreateThread() failure %d\n", GetLastError() )); goto FatalExit3; } // // Success! // return AapContext; // // Cleanup from various levels of fatal error. // FatalExit3: CloseHandle( AapContext->EventHandle ); FatalExit2: closesocket( AapContext->ListeningSocket ); FatalExit1: TCP_FREE( AapContext ); FatalExit0: return NULL; } // AappCreateContext /******************************************************************* NAME: AappFreeResources SYNOPSIS: Frees the system resources associated with a given AAP_CONTEXT structure, then frees the structure itself. NOTE: This routine MUST be called with the list lock held! ENTRY: AapContext - The AAP_CONTEXT structure to free. HISTORY: KeithMo 01-Mar-1995 Created. ********************************************************************/ VOID AappFreeResources( LPAAP_CONTEXT AapContext ) { // // Sanity check. // DBG_ASSERT( IS_VALID_AAP_CONTEXT( AapContext ) ); IF_DEBUG( AAP ) { DBGWARN(( DBG_CONTEXT, "AappFreeResource: context @ %08lX\n", AapContext )); } // // Close the thread handle if open. // if( AapContext->ThreadHandle != NULL ) { CloseHandle( AapContext->ThreadHandle ); } // // Remove this structure from the list. // RemoveEntryList( &AapContext->ContextList ); // // Free the structure. // KILL_AAP_SIG( AapContext ); TCP_FREE( AapContext ); } // AappFreeResources /******************************************************************* NAME: AappWorkerThread SYNOPSIS: Worker thread for accept()ing incoming connections. ENTRY: Param - Actually the LPAAP_CONTEXT structure for this thread. RETURNS: DWORD - Thread exit code (ignored). HISTORY: KeithMo 01-Mar-1995 Created. ********************************************************************/ DWORD AappWorkerThread( LPVOID Param ) { LPAAP_CONTEXT AapContext; AAP_STATE AapState; LPAAP_CALLBACK AapCallback; LPVOID UserContext; DWORD WaitResult; BOOL CallbackResult; SOCKET ListeningSocket; SOCKET AcceptedSocket; SOCKERR SocketStatus; INT RemoteAddressLength; SOCKADDR_IN RemoteAddress; // // Grab the context structure. // AapContext = (LPAAP_CONTEXT)Param; DBG_ASSERT( IS_VALID_AAP_CONTEXT( AapContext ) ); IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AappWorkerThread: starting, context @ %08lX\n", AapContext )); } // // Capture some fields from the structure. // AapCallback = AapContext->AapCallback; UserContext = AapContext->UserContext; ListeningSocket = AapContext->ListeningSocket; // // Loop forever, or at least until we're told to shutdown. // for( ; ; ) { // // Wait for a request. // WaitResult = WaitForSingleObject( AapContext->EventHandle, INFINITE ); if( WaitResult != WAIT_OBJECT_0 ) { DBGWARN(( DBG_CONTEXT, "AappWorkerThread: wait failure %lu : %d\n", WaitResult, GetLastError() )); break; } // // Check our state. // AapLockLists(); AapState = AapContext->State; AapUnlockLists(); if( AapState == AapStateShutdown ) { IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AappWorkerThread: context @ %08lX state == %d, exiting\n", AapContext, AapState )); } break; } for( ; ; ) { // // Wait for an incoming connection. // RemoteAddressLength = sizeof(RemoteAddress); AcceptedSocket = accept( ListeningSocket, (LPSOCKADDR)&RemoteAddress, &RemoteAddressLength ); SocketStatus = ( AcceptedSocket == INVALID_SOCKET ) ? WSAGetLastError() : 0; IF_DEBUG( AAP ) { if( SocketStatus != 0 ) { DBGERROR(( DBG_CONTEXT, "AappWorkerThread: accept() failure %d\n", SocketStatus )); } } // // Invoke the callback. // IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AappWorkerThread: context @ %08lX calling %08lX[%08lX]\n", AapContext, AapCallback, UserContext )); } CallbackResult = AapCallback( UserContext, SocketStatus, AcceptedSocket, (LPSOCKADDR)&RemoteAddress, RemoteAddressLength ); // // If the callback returned FALSE (indicating that it wants no // further callbacks) OR if the accept() failed for any reason, // then exit the accept() loop. // if( !CallbackResult || ( SocketStatus != 0 ) ) { break; } } // // If we hit a socket error, then bail. // if( SocketStatus != 0 ) { IF_DEBUG( AAP ) { DBGWARN(( DBG_CONTEXT, "AappWorkerThread: context @ %08lX, exiting\n", AapContext )); } break; } // // If we haven't exhausted the idle thread quota, then add // ourselves to the idle list. Otherwise, exit the main // processing loop & terminate this thread. // AapLockLists(); if( ( AapContext->State == AapStateShutdown ) || ( p_AapIdleCount >= MAX_IDLE_THREADS ) ) { AapUnlockLists(); break; } RemoveEntryList( &AapContext->ContextList ); AapContext->State = AapStateIdle; InsertHeadList( &p_AapIdleList, &AapContext->ContextList ); p_AapIdleCount++; IF_DEBUG( AAP ) { DBGPRINTF(( DBG_CONTEXT, "AappWorkerThread: context @ %08lX put on idle list\n", AapContext )); } AapUnlockLists(); } // // We only make it this far if it's time to die. // AapLockLists(); if( AapContext->State != AapStateShutdown ) { TCP_REQUIRE( CloseHandle( AapContext->EventHandle ) ); TCP_REQUIRE( CloseHandle( AapContext->ThreadHandle ) ); TCP_REQUIRE( closesocket( AapContext->ListeningSocket ) == 0 ); AappFreeResources( AapContext ); } AapUnlockLists(); return 0; } // AappWorkerThread