/**********************************************************************/ /** Microsoft Windows NT **/ /** Copyright(c) Microsoft Corp., 1994 **/ /**********************************************************************/ /* atqnew.c This module contains async thread queue (atq) for async IO and thread pool sharing. FILE HISTORY: Johnl 05-Aug-1994 Created. MuraliK 01-Nov-1994 Modified to use real TransmitFile() MuraliK 27-Mar-1995 Removed unwanted parameters from Atq*File() calls to simplify MikeMas 30-Mar-1995 Cleaned up shutdown code and made independent of any other code. MuraliK 26-May-1995 Moved Atq.c to AtqNew.c to contain bandwidth throttling code. MuraliK 19-Sept-1995 Tune thread startup; set proper concurrency for GetQueuedCompletionStatus(); Murali R. Krishnan (MuraliK) 02-Apr-1996 code clarity; path-length mods & ref count fixes Functions Exported: BOOL AtqInitialize(); BOOL AtqTerminate(); DWORD AtqSetInfo(); DWORD AtqSetContextInfo(); BOOL AtqAddAsyncHandle(); VOID AtqFreeContext(); BOOL AtqReadFile(); BOOL AtqWriteFile(); BOOL AtqTransmitFile(); BOOL AtqPostCompletionStatus(); BOOL AtqGetStatistics(); */ # include # include # include "atqtypes.hxx" # include # include "tcpproc.h" # include "dbgutil.h" # include /************************************************************ * Constants ************************************************************/ // concurrent # of threads to run per processor DWORD g_cConcurrency = ATQ_COMPLETION_PORT_CONCURRENCY; // // The amount of time (in ms) a worker thread will be idle before // terminating itself // DWORD g_msThreadTimeout = INETA_DEF_THREAD_TIMEOUT * 1000; /************************************************************ * Private Globals ************************************************************/ HANDLE g_hCompPort = NULL; // Handle for completion port DWORD g_cThreads = 0; // number of thread in the pool DWORD g_cAvailableThreads = 0; // # of threads waiting on the port. DWORD g_cCPU = 0; // number of CPUs in machine (for thread-tuning) // // Current thread limit // DWORD g_cMaxThreads = ATQ_MAX_THREADS; // // The absolute thread limit // DWORD g_cMaxThreadLimit = INETA_DEF_ATQ_THREAD_LIMIT; HANDLE g_hTimeoutThread = NULL; // handle for timeout thread. BOOL g_fShutdown = FALSE; // if set, indicates that we are shutting down // in that case, all threads should exit. HANDLE g_hShutdownEvent = NULL; // set when all running threads shutdown BOOL g_fUseAcceptEx = TRUE; // Use AcceptEx if available // // Should we use transmit files // BOOL g_fUseTransmitFile = TRUE; DWORD g_cbMinKbSec = INETA_DEF_MIN_KB_SEC; // Assumed minimum file transfer rate // // Size of buffers for fake xmits // DWORD g_cbXmitBufferSize = INETA_DEF_NONTF_BUFFER_SIZE; // // This event is used for the timer wakeup and for putting the timeout // thread to sleep when there aren't any clients. It's an auto reset event. // HANDLE g_hTimeoutEvent = NULL; // globals keeping track of status for each operation supported by ATQ. BOOL g_fBandwidthThrottle = FALSE; // are we throttling? BOOL DisableBandwidthThrottle = FALSE; // should bw be disabled // // List of free context // LIST_ENTRY AtqFreeContextList; CRITICAL_SECTION AtqFreeContextListLock; // // List of active context // ATQ_CONTEXT_LISTHEAD AtqActiveContextList[ATQ_NUM_CONTEXT_LIST]; // // List of pending connects // LIST_ENTRY AtqListenInfoList; CRITICAL_SECTION AtqListenInfoLock; // // entry points // PFN_ACCEPTEX pfnAcceptEx = NULL; PFN_GETACCEPTEXSOCKADDRS pfnGetAcceptExSockaddrs = NULL; // // Used to switch context between lists // DWORD AtqGlobalContextCount = 0; BOOL AtqInitialize( IN LPCTSTR pszRegKey ) /*++ Routine Description: Initializes the ATQ package Arguments: Return Value: TRUE if successful, FALSE on error (call GetLastError) --*/ { DWORD dwThreadID; HKEY hkey = NULL; DWORD dwError; HINSTANCE hWsock32; DWORD dwVal; DWORD i; // // Validate Product Type // AtqValidateProductType( ); // // If this is a NTW, do the right thing // if ( !TsIsNtServer() ) { DisableBandwidthThrottle = TRUE; g_cCPU = 1; g_cMaxThreads = ATQ_MAX_THREADS_PWS; g_cMaxThreadLimit = INETA_MIN_ATQ_THREAD_LIMIT; g_msThreadTimeout = INETA_DEF_THREAD_TIMEOUT_PWS * 1000; } else { SYSTEM_INFO si; MEMORYSTATUS ms; // // get the count of CPUs for Thread Tuning. // GetSystemInfo( &si ); g_cCPU = si.dwNumberOfProcessors; // // get the memory size // ms.dwLength = sizeof(MEMORYSTATUS); GlobalMemoryStatus( &ms ); // // Set the limit to be twice the total. // g_cMaxThreadLimit = ms.dwTotalPhys >> 19; if ( g_cMaxThreadLimit < INETA_MIN_ATQ_THREAD_LIMIT ) { g_cMaxThreadLimit = INETA_MIN_ATQ_THREAD_LIMIT; } else if ( g_cMaxThreadLimit > INETA_MAX_ATQ_THREAD_LIMIT ) { g_cMaxThreadLimit = INETA_MAX_ATQ_THREAD_LIMIT; } } ATQ_REQUIRE( AbwInitialize()); ATQ_REQUIRE( AtqInitAllocCachedContexts()); // // Initialize context lists and crit sects // for (i=0; i INETA_MAX_NONTF_BUFFER_SIZE ) { dwVal = INETA_MAX_NONTF_BUFFER_SIZE; } g_cbXmitBufferSize = dwVal; } if ( hkey ) { ATQ_REQUIRE( !RegCloseKey( hkey ) ); } } else { AtqSetInfo( AtqMaxConcurrency, INETA_DEF_PER_PROCESSOR_CONCURRENCY); AtqSetInfo( AtqUseAcceptEx, INETA_DEF_USE_ACCEPTEX); } // // Create the completion port // g_hCompPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, g_hCompPort, (DWORD) NULL, g_cConcurrency); if ( !g_hShutdownEvent || !g_hCompPort ) { goto cleanup; } // Ensure all other initializations also g_cThreads = 0; g_fShutdown = FALSE; g_cAvailableThreads = 0; g_fBandwidthThrottle = FALSE; // stop throttling by default if ( !I_AtqCreateTimeoutThread( NULL ) ) { goto cleanup; } // // Try and get the AcceptEx and GetAcceptExSockaddrs entry points // if ( g_fUseAcceptEx ) { hWsock32 = LoadLibrary( "wsock32.dll" ); if ( hWsock32 ) { pfnAcceptEx = (PFN_ACCEPTEX) GetProcAddress( hWsock32, "AcceptEx" ); pfnGetAcceptExSockaddrs = (PFN_GETACCEPTEXSOCKADDRS) GetProcAddress( hWsock32, "GetAcceptExSockaddrs" ); if ( !pfnAcceptEx || !pfnGetAcceptExSockaddrs ) { pfnAcceptEx = NULL; pfnGetAcceptExSockaddrs = NULL; g_fUseAcceptEx = FALSE; ATQ_PRINTF(( buff, "[AtqInitialize] Using Accept threads " "for connections\n")); } else { ATQ_PRINTF(( buff, "[AtqInitialize] Using AcceptEx " "for connections\n")); } FreeLibrary( hWsock32 ); } } else { ATQ_PRINTF(( buff, "[AtqInitialize] Using Accept threads for connections\n")); } // // Create the initial ATQ thread. // (VOID)I_AtqCheckThreadStatus( (PVOID)ATQ_INITIAL_THREAD ); // // Create a second thread if we are NTS // if ( TsIsNtServer() ) { (VOID)I_AtqCheckThreadStatus( (PVOID)ATQ_INITIAL_THREAD ); } return TRUE; cleanup: for (i=0; i 0) { DWORD i; BOOLEAN fRes; OVERLAPPED overlapped; // // Post a message to the completion port for each worker thread // telling it to exit. The indicator is a NULL context in the // completion. // RtlZeroMemory( &overlapped, sizeof(OVERLAPPED) ); for (i=0; icAllowedRequests = g_cTotalAllowedRequests; pAtqStats->cBlockedRequests = g_cTotalBlockedRequests; pAtqStats->cRejectedRequests = g_cTotalRejectedRequests; pAtqStats->MeasuredBandwidth = g_dwMeasuredBw; pAtqStats->cCurrentBlockedRequests = g_cCurrentBlockedRequests; } else { SetLastError( ERROR_INVALID_PARAMETER); return (FALSE); } return (TRUE); } // AtqGetStatistics() BOOL AtqClearStatistics( VOID) { return ( AbwClearStatistics()); } // AtqClearStatistics() DWORD AtqContextSetInfo( PATQ_CONTEXT patqContext, enum ATQ_CONTEXT_INFO atqInfo, DWORD Data ) /*++ Routine Description: Sets various bits of information for this context Arguments: patqContext - pointer to ATQ context atqInfo - Data item to set data - New value for item Return Value: The old value of the parameter --*/ { PATQ_CONT pContext = (PATQ_CONT) patqContext; DWORD dwOldVal = 0; ATQ_ASSERT( pContext ); ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE ); if ( pContext && pContext->Signature == ATQ_SIGNATURE ) { switch ( atqInfo ) { case ATQ_INFO_TIMEOUT: dwOldVal = pContext->TimeOut; CanonTimeout( &Data ); pContext->TimeOut = Data; break; case ATQ_INFO_RESUME_IO: // // set back the max timeout from pContext->TimeOut // This will ensure that timeout processing can go on // peacefully. // { DWORD currentTime = AtqGetCurrentTick( ); DWORD timeout; dwOldVal = pContext->NextTimeout; timeout = pContext->TimeOut; // // Set the new timeout // I_SetNextTimeout(pContext); // // Return the old // if ( currentTime >= dwOldVal ) { ATQ_ASSERT((dwOldVal & ATQ_INFINITE) == 0); dwOldVal = 0; } else if ( (dwOldVal & ATQ_INFINITE) == 0 ) { dwOldVal -= currentTime; } UndoCanonTimeout( &dwOldVal ); // return correct units } break; case ATQ_INFO_COMPLETION: dwOldVal = (DWORD) pContext->pfnCompletion; pContext->pfnCompletion = (ATQ_COMPLETION) Data; break; case ATQ_INFO_COMPLETION_CONTEXT: ATQ_ASSERT( Data != 0 ); // NULL context not allowed dwOldVal = (DWORD) pContext->ClientContext; pContext->ClientContext = (void *) Data; break; default: ATQ_ASSERT( FALSE ); } } return dwOldVal; } // AtqContextSetInfo BOOL AtqAddAsyncHandle( PATQ_CONTEXT * ppatqContext, PVOID ClientContext, ATQ_COMPLETION pfnCompletion, DWORD TimeOut, HANDLE hAsyncIO ) /*++ Routine Description: Adds a handle to the thread queue The client should call this after the IO handle is openned and before the first IO request is made Unfortunately you can't create a completion port without a file handle so we have to have some logic for the first thread to create the port. Even in the case of failure, client should call AtqFreeContext() and free the memory associated with this object. Arguments: ppatqContext - Receives allocated ATQ Context Context - Context to call client with pfnCompletion - Completion to call when IO completes TimeOut - Time to wait (sec) for IO completion (INFINITE is valid) hAsyncIO - Handle with pending read or write Return Value: TRUE if successful, FALSE on error (call GetLastError) --*/ { return ( I_AtqAddAsyncHandle( (PATQ_CONT *) ppatqContext, ClientContext, pfnCompletion, TimeOut, hAsyncIO) && I_AddAtqContextToPort( *((PATQ_CONT *) ppatqContext)) ); } // AtqAddAsyncHandle() VOID AtqGetAcceptExAddrs( IN PATQ_CONTEXT patqContext, OUT SOCKET * pSock, OUT PVOID * ppvBuff, OUT SOCKADDR * * ppsockaddrLocal, OUT SOCKADDR * * ppsockaddrRemote ) { PATQ_CONT pContext = (PATQ_CONT ) patqContext; INT cbsockaddrLocal; INT cbsockaddrRemote; if ( !pfnGetAcceptExSockaddrs ) return; *pSock = (SOCKET) pContext->hAsyncIO; // // The buffer not only receives the initial received data, it also // gets the sock addrs, which must be at least sockaddr_in + 16 bytes // large // pfnGetAcceptExSockaddrs( pContext->pvBuff, pContext->cbBuff - 2 * MIN_SOCKADDR_SIZE, MIN_SOCKADDR_SIZE, MIN_SOCKADDR_SIZE, ppsockaddrLocal, &cbsockaddrLocal, ppsockaddrRemote, &cbsockaddrRemote ); *ppvBuff = ( ( pContext->cbBuff - 2*MIN_SOCKADDR_SIZE == 0) ? NULL : pContext->pvBuff); return; } // AtqGetAcceptExAddrs() BOOL AtqCloseSocket( PATQ_CONTEXT patqContext, BOOL fShutdown ) /*++ Routine Description: Closes the socket in this atq structure if it wasn't closed by transmitfile Arguments: patqContext - Context to close fShutdown - If TRUE, means we call shutdown and always close the socket. Note that if TransmitFile closed the socket, it will have done the shutdown for us --*/ { PATQ_CONT pContext = (PATQ_CONT ) patqContext; HANDLE hIO; IF_DEBUG( ATQ ) { DBGPRINTF(( DBG_CONTEXT, " AtqCloseSocket(%08x, sock=%d) \n", patqContext, patqContext->hAsyncIO)); } if ( pContext ) { ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE ); // // Don't delete the socket if we don't have to // switch ( pContext->SockState ) { case ATQ_SOCK_UNCONNECTED: case ATQ_SOCK_CLOSED: // // Do nothing // break; default: case ATQ_SOCK_LISTENING: case ATQ_SOCK_CONNECTED: pContext->SockState = ATQ_SOCK_CLOSED; // // During shutdown, the socket may be closed while this thread // is doing processing, so only give a warning if any of the // following fail // hIO = (HANDLE ) InterlockedExchange((LPLONG ) &pContext->hAsyncIO, (LONG ) NULL); if ( fShutdown && hIO != NULL) { ACCEPTEX_LISTEN_INFO * pLi; pLi = pContext->pListenInfo; // // If this is an AcceptEx socket, we must first force a // user mode context update before we can call shutdown // if ( pfnAcceptEx && pLi != NULL ) { if ( setsockopt( (SOCKET) hIO, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char *) &pLi->sListenSocket, sizeof(SOCKET) ) == SOCKET_ERROR ) { ATQ_PRINTF((buff, "[AtqCloseSocket] Warning- setsockopt " "failed, error %d, socket = %x," " Context= %08x, Listen = %lx\n", GetLastError(), hIO, pContext, pLi->sListenSocket )); } } // // Note that shutdown can fail in instances where the client // aborts in the middle of a TransmitFile. This is an // acceptable failure case // shutdown( (int) hIO, 1 ); } if ( hIO == NULL || closesocket( (int) hIO ) ) { ATQ_PRINTF((buff, "[AtqCloseSocket] Warning- closesocket failed, " " Context = %08x, error %d, socket = %x\n", pContext, GetLastError(), hIO )); } break; } // switch() return TRUE; } DBGPRINTF(( DBG_CONTEXT, "[AtqCloseSocket] Warning - NULL Atq context\n")); SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } // AtqCloseSocket() VOID AtqFreeContext( PATQ_CONTEXT patqContext, BOOL fReuseContext ) /*++ Routine Description: Frees the context created in AtqAddAsyncHandle Arguments: patqContext - Context to free fReuseContext - TRUE if this can context can be reused in the context of the calling thread. Should be FALSE if the calling thread will exit soon (i.e., isn't an AtqPoolThread). --*/ { PATQ_CONT pContext = (PATQ_CONT ) patqContext; ACCEPTEX_LISTEN_INFO * pListenInfo; ATQ_ASSERT( pContext != NULL ); IF_DEBUG( ATQ ) { DBGPRINTF(( DBG_CONTEXT, " AtqFreeContext(%08x. sock=%d) \n", patqContext, patqContext->hAsyncIO)); } if ( pContext ) { ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE ); // Get this object out of the Blocked Requests List. if ( pContext->fBlocked) { ATQ_REQUIRE( AbwRemoveFromBlockedList( pContext)); ATQ_ASSERT( pContext->fBlocked == FALSE); } // // If the socket is an AcceptEx socket, redo the AcceptEx and put // it back on the in use list // pListenInfo = pContext->pListenInfo; if ( (pListenInfo != NULL) && fReuseContext ) { ATQ_ASSERT(pContext->fConnectionIndicated); pContext->ContextList->RemoveFromList( &pContext->ListEntry ); #if DBG pContext->ListEntry.Flink = pContext->ListEntry.Flink = NULL; #endif ATQ_ASSERT( pListenInfo->Signature == ACCEPTEX_LISTEN_SIGN ); // // Either there is no socket or the socket must be in the // unconnected state (meaning reused after TransmitFile) // #if 0 ATQ_ASSERT( !pContext->hAsyncIO || (pContext->hAsyncIO && pContext->SockState == ATQ_SOCK_UNCONNECTED )); #else // // BUGBUG - Debug code, don't assert until we figure out how // we got in this state // if ( !(!pContext->hAsyncIO || (pContext->hAsyncIO && pContext->SockState == ATQ_SOCK_UNCONNECTED ))) { ATQ_PRINTF(( buff, "[AtqFreeContext] Warning:" " state = %d, socket = %x (context %lx), " " was Free called w/o close?\n", pContext->SockState, pContext->hAsyncIO, pContext )); } #endif if ( !I_AtqAddAcceptExSocket(pListenInfo, pContext) ) { // // Failed to add the socket, // I_AtqAddAcceptExSocket freed the context // ATQ_PRINTF(( buff, "[AtqFreeContext] AtqAddAcceptExSocket(%08x) " " failed. Error = %d\n", pContext, GetLastError() )); } } else { I_AtqFreeContextToCache( pContext, TRUE ); } } return; } // AtqFreeContext() BOOL AtqAddAcceptExSockets( IN SOCKET sListenSocket, IN ATQ_COMPLETION pfnOnConnect, IN ATQ_COMPLETION pfnIOCompletion, IN DWORD cInitial, IN DWORD cbRecvBuf, IN DWORD csecTimeout ) /*++ Routine Description: Creates the initial set of listenning sockets if we're using the AcceptEx APIs Arguments: sListenSocket - Server socket to listen on pfnOnConnect - Completion routine of connections pfnIOCompletio - Completion routine for IOs cInitial - Initial number of listenning sockets cbRecvBuf - Initial size of receive buffer (If 0, we submit AcceptEx call with no receive Buffer) csecTimeout - Timeout between connect and the completion of receiving the first buffer Return Value: TRUE on success, FALSE on failure --*/ { PATQ_CONT patqContext = NULL; ACCEPTEX_LISTEN_INFO * pListenInfo = NULL; BOOL fReturn; // // Add the listen socket // fReturn = I_AtqAddListenSocketToPort( &patqContext, pfnOnConnect, pfnIOCompletion, sListenSocket ); if ( !fReturn) { if ( patqContext ) { I_AtqFreeContextToCache( patqContext, FALSE ); } return FALSE; } // // Add the listen socket once, then add the other sockets // pListenInfo = (PACCEPTEX_LISTEN_INFO)LocalAlloc( LPTR, sizeof(ACCEPTEX_LISTEN_INFO) ); if ( !pListenInfo ) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return FALSE; } pListenInfo->Signature = ACCEPTEX_LISTEN_SIGN; pListenInfo->cRef = 1; pListenInfo->fAccepting = TRUE; pListenInfo->sListenSocket = sListenSocket; pListenInfo->cbInitialRecvSize = cbRecvBuf; pListenInfo->csecTimeout = csecTimeout; pListenInfo->cSocketsAvail = 0; pListenInfo->pfnOnConnect = pfnOnConnect; pListenInfo->pfnIOCompletion = pfnIOCompletion; pListenInfo->cAvailDuringTimeOut = 0; if ( !TsIsNtServer( ) ) { // // Limit what a workstation can specify // cInitial = min(cInitial, ATQ_MIN_CTX_INC); pListenInfo->cNewIncrement = cInitial >> 1; } else { pListenInfo->cNewIncrement = cInitial; cInitial += (cInitial >> 1); } // // Put the listen info on the list // AcquireLock( &AtqListenInfoLock ); InsertTailList( &AtqListenInfoList, &pListenInfo->ListEntry ); ReleaseLock( &AtqListenInfoLock ); // // Now add the acceptex sockets for this ListenInfo object // return ( I_AtqPrepareAcceptExSockets(pListenInfo, cInitial ) ); } // AtqAddAcceptExSockets dllexp BOOL AtqRemoveAcceptExSockets( IN SOCKET ListenSocket ) { LIST_ENTRY * pEntry; ACCEPTEX_LISTEN_INFO * pListenInfo; PATQ_CONT pContext; DWORD i; // // Find the listen socket info // AcquireLock( &AtqListenInfoLock ); for ( pEntry = AtqListenInfoList.Flink; pEntry != &AtqListenInfoList; pEntry = pEntry->Flink ) { pListenInfo = CONTAINING_RECORD( pEntry, ACCEPTEX_LISTEN_INFO, ListEntry ); ATQ_ASSERT( pListenInfo->Signature == ACCEPTEX_LISTEN_SIGN ); if ( ListenSocket == pListenInfo->sListenSocket ) { RemoveEntryList( pEntry ); pEntry->Flink = NULL; goto Found; } } ReleaseLock( &AtqListenInfoLock ); SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; Found: // // Mark the listen info as no longer accepting connections // pListenInfo->fAccepting = FALSE; ReleaseLock( &AtqListenInfoLock ); // // Delete all of the non-connected sockets to prevent any new incoming // connections // for ( i = 0; i < ATQ_NUM_CONTEXT_LIST; i++) { PLIST_ENTRY pPendingList; AtqActiveContextList[i].Lock(); pPendingList = &AtqActiveContextList[i].PendingAcceptExListHead; for ( pEntry = pPendingList->Flink; pEntry != pPendingList; pEntry = pEntry->Flink ) { pContext = CONTAINING_RECORD( pEntry, ATQ_CONTEXT, ListEntry ); ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE ); if ( pContext->pListenInfo == pListenInfo && pContext->SockState != ATQ_SOCK_CONNECTED && pContext->hAsyncIO != NULL ) { pContext->SockState = ATQ_SOCK_CLOSED; // // This should generate an IO completion which will free this // context // if (closesocket((SOCKET)pContext->hAsyncIO) == SOCKET_ERROR) { ATQ_PRINTF((buff, "[AtqRemoveAcceptExSockets] Warning - " " Context=%08x closesocket failed," " error %d, socket = %x\n", pContext, GetLastError(), pContext->hAsyncIO )); } pContext->hAsyncIO = NULL; } } // for AtqActiveContextList[i].Unlock(); } // for // // Undo the reference for being on the listen info list. If it's the last // one, delete the object // if ( InterlockedDecrement( (PLONG)&pListenInfo->cRef ) == 0) { ATQ_ASSERT( pContext->pListenInfo->ListEntry.Flink == NULL); pListenInfo->Signature = ACCEPTEX_LISTEN_SIGN_FREE; LocalFree( pListenInfo ); } return TRUE; } // AtqRemoveAcceptExSockets /*++ Routine Description: AtqReadFile, AtqWriteFile, AtqTransmitFile The following three functions just reset the timeout value then call the corresponding function. An IO pending error code is treated as a success error code Arguments: patqContext - pointer to ATQ context Everything else as in the Win32 API NOTES: AtqTransmitFile takes an additional DWORD flags which may contain the winsock constants TF_DISCONNECT and TF_REUSE_SOCKET AtqReadFile and AtqWriteFile take an optional overlapped structure if clients want to have multiple outstanding reads or writes. If the value is NULL, then the overlapped structure from the Atq context is used. Return Value: TRUE if successful, FALSE on error (call GetLastError) sets ERROR_NETWORK_BUSY as error when the request needs to be rejected. --*/ BOOL AtqReadFile( PATQ_CONTEXT patqContext, LPVOID lpBuffer, DWORD BytesToRead, OVERLAPPED * lpo ) { BOOL fRes; DWORD cbRead; // discarded after usage ( since this is Async) PATQ_CONT pContext = (PATQ_CONT ) patqContext; ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE ); ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone); I_SetNextTimeout(pContext); pContext->BytesSent = 0; if ( !lpo ) { lpo = &pContext->Overlapped; } InterlockedExchange( &pContext->lSyncTimeout, AtqPendingIo); ATQ_ASSERT( g_pStatus != NULL); switch ( g_pStatus[AtqIoRead]) { case StatusAllowOperation: INC_ATQ_COUNTER( g_cTotalAllowedRequests); fRes = ( ReadFile( pContext->hAsyncIO, lpBuffer, BytesToRead, &cbRead, lpo ) || GetLastError() == ERROR_IO_PENDING); break; case StatusBlockOperation: // store data for restarting the operation. pContext->arInfo.atqOp = AtqIoRead; pContext->arInfo.lpOverlapped = lpo; pContext->arInfo.uop.opReadWrite.lpBuffer = lpBuffer; pContext->arInfo.uop.opReadWrite.cbBuffer = BytesToRead; INC_ATQ_COUNTER( g_cTotalBlockedRequests); // Put this request in queue of blocked requests. fRes = AbwBlockRequest( pContext); break; case StatusRejectOperation: INC_ATQ_COUNTER( g_cTotalRejectedRequests); SetLastError( ERROR_NETWORK_BUSY); fRes = FALSE; break; default: ATQ_ASSERT( FALSE); SetLastError( ERROR_INVALID_PARAMETER); fRes = FALSE; break; } // switch() return fRes; } // AtqReadFile() BOOL AtqWriteFile( PATQ_CONTEXT patqContext, LPCVOID lpBuffer, DWORD BytesToWrite, OVERLAPPED * lpo ) { BOOL fRes; DWORD cbWritten; // discarded after usage ( since this is Async) PATQ_CONT pContext = (PATQ_CONT ) patqContext; ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE ); ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone); I_SetNextTimeout(pContext); pContext->BytesSent = BytesToWrite; if ( !lpo ) { lpo = &pContext->Overlapped; } InterlockedExchange( &pContext->lSyncTimeout, AtqPendingIo); ATQ_ASSERT( g_pStatus != NULL); switch ( g_pStatus[AtqIoWrite]) { case StatusAllowOperation: INC_ATQ_COUNTER( g_cTotalAllowedRequests); fRes = ( WriteFile( pContext->hAsyncIO, lpBuffer, BytesToWrite, &cbWritten, lpo ) || GetLastError() == ERROR_IO_PENDING); break; case StatusBlockOperation: // store data for restarting the operation. pContext->arInfo.atqOp = AtqIoWrite; pContext->arInfo.lpOverlapped = lpo; pContext->arInfo.uop.opReadWrite.lpBuffer = (LPVOID) lpBuffer; pContext->arInfo.uop.opReadWrite.cbBuffer = BytesToWrite; INC_ATQ_COUNTER( g_cTotalBlockedRequests); // Put this request in queue of blocked requests. fRes = AbwBlockRequest( pContext); break; case StatusRejectOperation: INC_ATQ_COUNTER( g_cTotalRejectedRequests); SetLastError( ERROR_NETWORK_BUSY); fRes = FALSE; break; default: ATQ_ASSERT( FALSE); SetLastError( ERROR_INVALID_PARAMETER); fRes = FALSE; break; } // switch() return fRes; } // AtqWriteFile() BOOL AtqTransmitFile( IN PATQ_CONTEXT patqContext, IN HANDLE hFile, IN LARGE_INTEGER liBytesInFile, IN LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, IN DWORD dwFlags ) { BOOL fRes; PATQ_CONT pContext = (PATQ_CONT) patqContext; ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE ); ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone); // // Check if transmit file is disabled // InterlockedExchange( &pContext->lSyncTimeout, AtqPendingIo); if ( !g_fUseTransmitFile ) { return(I_DoFakeTransmitFile( pContext, hFile, liBytesInFile.LowPart, lpTransmitBuffers )); } // // For large file sends, the client's default timeout may not be // adequte for slow links. Scale based on bytes being sent // I_SetNextTimeout(pContext); pContext->BytesSent = liBytesInFile.LowPart; // // The flags are only valid in the version of wsock32 that supports // AcceptEx // if ( (dwFlags == 0) || !pfnAcceptEx ) { // // If no flags are set, then we can attempt to use the special // write-behind flag. This flag can cause the TransmitFile to // complete immediately, before the send actually completes. // This can be a significant performance improvement inside the // system. // dwFlags = TF_WRITE_BEHIND; } else if ( dwFlags & TF_DISCONNECT ) { // // If the socket is getting disconnected, mark it appropriately // if ( dwFlags & TF_REUSE_SOCKET ) { pContext->SockState = ATQ_SOCK_UNCONNECTED; } else { pContext->SockState = ATQ_SOCK_CLOSED; } } ATQ_ASSERT( g_pStatus != NULL); switch ( g_pStatus[AtqIoXmitFile]) { case StatusAllowOperation: INC_ATQ_COUNTER( g_cTotalAllowedRequests); fRes = ( TransmitFile( (SOCKET ) pContext->hAsyncIO, hFile, liBytesInFile.LowPart, // send entire file. 0, &pContext->Overlapped, lpTransmitBuffers, dwFlags ) || GetLastError() == ERROR_IO_PENDING); break; case StatusBlockOperation: // store data for restarting the operation. pContext->arInfo.atqOp = AtqIoXmitFile; pContext->arInfo.lpOverlapped = &pContext->Overlapped; pContext->arInfo.uop.opXmit.hFile = hFile; pContext->arInfo.uop.opXmit.liBytesInFile = liBytesInFile; pContext->arInfo.uop.opXmit.lpXmitBuffers = lpTransmitBuffers; pContext->arInfo.uop.opXmit.dwFlags = dwFlags; INC_ATQ_COUNTER( g_cTotalBlockedRequests); // Put this request in queue of blocked requests. fRes = AbwBlockRequest( pContext); break; case StatusRejectOperation: INC_ATQ_COUNTER( g_cTotalRejectedRequests); SetLastError( ERROR_NETWORK_BUSY); fRes = FALSE; break; default: ATQ_ASSERT( FALSE); SetLastError( ERROR_INVALID_PARAMETER); fRes = FALSE; break; } // switch() // // Restore the socket state if we failed so that the handle gets freed // if ( !fRes ) { pContext->SockState = ATQ_SOCK_CONNECTED; } return fRes; } // AtqTransmitFile() BOOL AtqTransmitFileEx( IN PATQ_CONTEXT patqContext, IN HANDLE hFile, IN LARGE_INTEGER liBytesInFile, IN LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, IN DWORD dwFlags, IN DWORD dwMBZ1, // Reserved, must be zero IN DWORD dwMBZ2 // Reserved, must be zero ) { // // Check arguments // if ( liBytesInFile.HighPart || dwMBZ1 || dwMBZ2 ) { SetLastError( ERROR_NOT_SUPPORTED ); return FALSE; } // // With recent changes to AtqTransmitFile() to support Byte ranges // and AFD TransmitFile() fast path, the functions // AtqTransmitFile() & AtqTransmitFileEx() are identical. // So let us invoke one single function! // - MuraliK 04-01-1996 (this is April Fool's Day!) // return ( AtqTransmitFile( patqContext, hFile, liBytesInFile, lpTransmitBuffers, dwFlags) ); } // AtqTransmitFileEx() BOOL AtqPostCompletionStatus( PATQ_CONTEXT patqContext, DWORD BytesTransferred ) /*++ Routine Description: Posts a completion status on the completion port queue An IO pending error code is treated as a success error code Arguments: patqContext - pointer to ATQ context Everything else as in the Win32 API NOTES: Return Value: TRUE if successful, FALSE on error (call GetLastError) --*/ { BOOL fRes; PATQ_CONT pAtqContext = (PATQ_CONT ) patqContext; ATQ_ASSERT( (pAtqContext)->Signature == ATQ_SIGNATURE ); if ( !pAtqContext->fBlocked) { fRes = ( PostQueuedCompletionStatus( g_hCompPort, BytesTransferred, (DWORD) patqContext, &pAtqContext->Overlapped ) || (GetLastError() == ERROR_IO_PENDING)); } else { // Forcibly remove the context from blocking list. fRes = AbwRemoveFromBlockedList(pAtqContext); // There is a possibility of race conditions! // If we cant remove an item from blocking list before // its IO operation is scheduled. // there wont be any call back generated for this case! } return fRes; } // AtqPostCompletionStatus