Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2863 lines
69 KiB

/**********************************************************************/
/** Microsoft Windows NT **/
/** Copyright(c) Microsoft Corp., 1994 **/
/**********************************************************************/
/*
atqwin.c
This module contains async thread queue (atq) for async IO and thread
pool sharing.
BUGBUG This is not final code , it only allows one completion port per app and hard codes
link to dispatching window. We will not have window based dispatching in final version
anyway due to performance problem.
Best implementation would be to encapsulate io completion port fully, so when kernel starts
supporting them it will be minimal change to this file.
FILE HISTORY:
VladS 05-Nov-1995 Created.
Functions Exported:
BOOL AtqInitialize();
BOOL AtqTerminate();
DWORD AtqSetInfo();
DWORD AtqSetContextInfo();
BOOL AtqAddAsyncHandle();
VOID AtqFreeContext();
BOOL AtqReadFile();
BOOL AtqWriteFile();
BOOL AtqTransmitFile();
BOOL AtqPostCompletionStatus();
BOOL AtqGetStatistics();
*/
//#define ATQ_NO_PROTOTYPES 1
# include "tcpdllp.hxx"
# include "atqbw.h"
# include "atqwin.h"
# include "inetreg.h"
# include "tcpproc.h"
# include "dbgutil.h"
#define ATQ_ASSERT DBG_ASSERT
#define ATQ_REQUIRE DBG_REQUIRE
#if DBG
#define ATQ_DBGPRINT(str) OutputDebugString(str)
#define ATQ_PRINTF( x ) { char buff[256]; wsprintf x; OutputDebugString( buff ); }
//#define ATQ_PRINTF( x )
#else
#define ATQ_DBGPRINT(str)
#define ATQ_PRINTF( x )
#endif
/************************************************************
* Constants
************************************************************/
//
// The maximum number of threads we will allow to be created
//
#define ATQ_MAX_THREADS 10
//
// The amount of time (in ms) a worker thread will be idle before
// terminating itself
//
#define ATQ_THREAD_TIMEOUT (2*60*1000) // 2 minutes
DWORD g_msThreadTimeout = ATQ_THREAD_TIMEOUT;
//
//
// Time to wait for the Timeout thread to die
//
#define ATQ_WAIT_FOR_THREAD_DEATH (10 * 1000) // in milliseconds
//
// The interval (in seconds) the timeout thread sleeps between checking
// for timed out async requests
//
#define ATQ_TIMEOUT_INTERVAL (30)
//
// Rounds the time out value up to the next time out interval
//
#define ATQ_ROUNDUP_TIMEOUT( timeout ) \
(((timeout + ATQ_TIMEOUT_INTERVAL) / ATQ_TIMEOUT_INTERVAL) \
* ATQ_TIMEOUT_INTERVAL)
/*++
The following array specifies the status of operations for different
operations in different zones of the measured bandwidth.
We split the range of bandwidth into three zones as specified in
the type ZoneLevel. Depending upon the zone of operation, differet
operations are enabled/disabled. Priority is given to minimize the amount
of CPU work that needs to be redone when an operation is to be rejected.
We block operations on which considerable amount of CPU is spent earlier.
--*/
typedef enum {
ZoneLevelLow = 0, // if MeasuredBw < Bandwidth specified
ZoneLevelMedium, // if MeasuredBw approxEquals Bandwidth specified
ZoneLevelHigh, // if MeasuredBw > Bandwidth specified
ZoneLevelMax // just a boundary element
} ZoneLevel;
static OPERATION_STATUS g_rgStatus[ZoneLevelMax][AtqIoMaxOp] = {
// For ZoneLevelLow: Allow All operations
{ StatusRejectOperation,
StatusAllowOperation,
StatusAllowOperation,
StatusAllowOperation
},
// For ZoneLevelMedium:
{ StatusRejectOperation,
StatusBlockOperation, // Block Read
StatusAllowOperation,
StatusAllowOperation
},
// For ZoneLevelHigh
{ StatusRejectOperation,
StatusRejectOperation, // Reject Read
StatusAllowOperation, // Allow Writes
StatusBlockOperation // Block TransmitFile
}
};
OPERATION_STATUS * g_pStatus = &g_rgStatus[ZoneLevelLow][0];
/************************************************************
* Private Globals
************************************************************/
HWND g_hwndAtqDispatch = NULL; // Dispatching window handle
DWORD g_cThreads = 0; // number of thread in the pool
DWORD g_cAvailableThreads = 0; // # of threads waiting on the port.
DWORD g_cMaxThreads = ATQ_MAX_THREADS;
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
HANDLE g_hPendingCompletionSemaphore = NULL;
HINSTANCE g_hProcessInstance = NULL;
CHAR g_szAtqWindowClassName[] = "WNDTarantulaClass";
//
// g_AtqClientHead - The list of all IO handles added by the various clients
// g_AtqFreeList - The list of all ATQ contexts freed earlier.
//
LIST_ENTRY g_AtqClientHead ;
LIST_ENTRY g_AtqCompletedIoRequests ;
LIST_ENTRY g_AtqFreeList;
CRITICAL_SECTION g_AtqGlobalCriticalSection; // global sync variable.
DWORD AtqPoolThread( LPDWORD param );
BOOL AtqCheckThreadStatus( VOID );
BOOL
TsLogInformationA(
IN OUT DWORD hInetLog,
IN const VOID * pInetLogInfo
);
//
// Allocation Cache Functions
//
BOOL AtqInitAllocCachedContexts( VOID);
BOOL AtqFreeAllocCachedContexts( VOID);
PATQ_CONT AtqAllocContextFromCache( VOID);
VOID AtqFreeContextToCache( IN PATQ_CONT pAtqContext);
BOOL FindATQContextFromSocket( IN SOCKET sc, OUT PATQ_CONT *ppReturnContext);
LRESULT CALLBACK AtqDispatchWinProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL WinAtqOnSelect(SOCKET sc,UINT uiFunction);
BOOL
AtqStartAsyncOperation(
PATQ_CONT pContext
);
BOOL
AtqAddAsyncHandleEx(
PATQ_CONT * ppatqContext,
PVOID ClientContext,
ATQ_COMPLETION pfnOnConnect,
ATQ_COMPLETION pfnCompletion,
DWORD TimeOut,
HANDLE hAsyncIO,
BOOL fAddToPort,
HANDLE hListenSocket,
PVOID * pvBuff,
DWORD cbBuff,
PATQ_CONT pReuseableAtq,
ACCEPTEX_LISTEN_INFO * pListenInfo
);
#define AtqLockGlobals() EnterCriticalSection( &g_AtqGlobalCriticalSection )
#define AtqUnlockGlobals() LeaveCriticalSection( &g_AtqGlobalCriticalSection )
//
// Public functions.
//
VOID
AtqWinSetProcessInstance(
HINSTANCE hInstance
)
{
g_hProcessInstance = hInstance;
}
BOOL
AtqInitialize(
IN LPCTSTR pszRegKey
)
/*++
Routine Description:
Initializes the ATQ package
Arguments:
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
--*/
{
HKEY hkey = NULL;
BOOL bSuccess = FALSE;
// Initialize lists
InitializeListHead( &g_AtqClientHead );
InitializeListHead( &g_AtqCompletedIoRequests );
//
// Create the shutdown event
//
g_hShutdownEvent = CreateEvent( NULL, // lpsa (Security Attributes)
TRUE, // Manual reset
FALSE, // Not signalled
NULL ); // Name for event.
g_hPendingCompletionSemaphore =
CreateSemaphore(NULL,// Security Attributes
0, // Initial count
0x7fffffff, // Maximum count
NULL); // Name
//
// Create the dispatch window
//
bSuccess = WinCreateIoCompletionPort(0);
if ( !g_hShutdownEvent || !g_hwndAtqDispatch )
{
if ( g_hShutdownEvent != NULL ) CloseHandle( g_hShutdownEvent );
if ( g_hwndAtqDispatch != NULL ) DestroyWindow( g_hwndAtqDispatch );
return FALSE;
}
InitializeCriticalSection( &g_AtqGlobalCriticalSection );
// Ensure all other initializations also
g_cThreads = 0;
g_fShutdown = FALSE;
g_cAvailableThreads = 0;
return TRUE;
} // AtqInitialize()
BOOL
AtqTerminate(
VOID
)
/*++
Routine Description:
Cleans up the ATQ package. Should only be called after all of the
clients of ATQ have been shutdown.
History:
--*/
{
DWORD currentThreadCount;
if ( (g_hShutdownEvent == NULL) ||
(g_hwndAtqDispatch== NULL) ||
(g_fShutdown == TRUE)
)
{
//
// We have not been intialized or have already terminated.
//
SetLastError( ERROR_NOT_READY );
return FALSE;
}
AtqLockGlobals();
//
// All clients should have cleaned themselves up before calling us.
//
//ATQ_ASSERT( IsListEmpty( &g_AtqClientHead ) );
if ( !IsListEmpty(&g_AtqClientHead)) {
AtqUnlockGlobals();
return FALSE;
}
AtqUnlockGlobals();
//
// Note that we are shutting down and prevent any more handles from
// being added to the completion port.
//
g_fShutdown = TRUE;
currentThreadCount = g_cThreads;
// Now destroy our dispatch window
DestroyWindow(g_hwndAtqDispatch);
g_hwndAtqDispatch = NULL;
if (currentThreadCount > 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; i<currentThreadCount; i++) {
fRes = WinPostQueuedCompletionStatus(0,
0,
&overlapped );
ATQ_ASSERT( (fRes == TRUE) ||
( (fRes == FALSE) &&
(GetLastError() == ERROR_IO_PENDING) )
);
}
}
//
// Now wait for the pool threads to shutdown.
//
WaitForSingleObject( g_hShutdownEvent,
ATQ_WAIT_FOR_THREAD_DEATH);
//
// At this point, no other threads should be left running.
//
ATQ_ASSERT( !g_cThreads );
ATQ_REQUIRE( CloseHandle( g_hShutdownEvent ) );
ATQ_REQUIRE( CloseHandle( g_hPendingCompletionSemaphore ) );
g_hShutdownEvent = NULL;
g_hPendingCompletionSemaphore = NULL;
//
// Free all the elements in the Allocation caching list
//
ATQ_REQUIRE( AtqFreeAllocCachedContexts());
//
// Cleanup our synchronization resources
//
DeleteCriticalSection( &g_AtqGlobalCriticalSection );
return TRUE;
} // AtqTerminate()
DWORD
AtqSetInfo(
IN enum ATQ_INFO atqInfo,
IN DWORD Data
)
/*++
Routine Description:
Sets various bits of information for the ATQ module
Arguments:
atqInfo - Data item to set
data - New value for item
Return Value:
The old value of the parameter
--*/
{
DWORD dwOldVal = 0;
switch ( atqInfo ) {
case AtqMaxPoolThreads:
// the value is per processor values
// internally we maintain value for all processors
dwOldVal = g_cMaxThreads;
g_cMaxThreads = Data;
break;
//
// Increment or decrement the max thread count. In this instance, we
// do not scale by the number of CPUs
//
case AtqIncMaxPoolThreads:
InterlockedIncrement( (LONG *) &g_cMaxThreads );
dwOldVal = TRUE;
break;
case AtqDecMaxPoolThreads:
InterlockedDecrement( (LONG *) &g_cMaxThreads );
dwOldVal = TRUE;
break;
case AtqThreadTimeout:
dwOldVal = g_msThreadTimeout/1000; // convert back to seconds
g_msThreadTimeout = Data * 1000; // convert value to millisecs
break;
default:
//ATQ_ASSERT( FALSE )
;
}
return dwOldVal;
} // AtqSetInfo()
DWORD
AtqGetInfo(
IN enum ATQ_INFO atqInfo
)
/*++
Routine Description:
Gets various bits of information for the ATQ module
Arguments:
atqInfo - Data item to set
Return Value:
The old value of the parameter
--*/
{
DWORD dwVal = 0;
switch ( atqInfo ) {
case AtqMaxPoolThreads:
dwVal = g_cMaxThreads;
break;
case AtqThreadTimeout:
dwVal = g_msThreadTimeout/1000; // convert back to seconds
break;
default:
//ATQ_ASSERT( FALSE )
;
}
return dwVal;
} // AtqGetInfo()
BOOL
AtqGetStatistics(IN OUT ATQ_STATISTICS * pAtqStats)
{
if ( pAtqStats != NULL) {
// Since all these counters are updated using InterlockedIncrement()
// we dont take locks in obtaining the current value.
pAtqStats->cAllowedRequests = 0;
pAtqStats->cBlockedRequests = 0;
pAtqStats->cRejectedRequests = 0;
pAtqStats->MeasuredBandwidth = 0;
pAtqStats->cCurrentBlockedRequests = 0;
} else {
SetLastError( ERROR_INVALID_PARAMETER);
return (FALSE);
}
return (TRUE);
} // AtqGetStatistics()
BOOL
AtqClearStatistics( VOID)
{
return (0);
} // 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;
if ( Data != INFINITE )
pContext->TimeOut = ATQ_ROUNDUP_TIMEOUT( Data );
else
pContext->TimeOut = INFINITE;
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;
}
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
pListenInfo - The acceptex listen information for this context
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
--*/
{
return AtqAddAsyncHandleEx( (PATQ_CONT *) ppatqContext,
ClientContext,
NULL,
pfnCompletion,
TimeOut,
hAsyncIO,
TRUE,
NULL,
NULL,
0,
NULL,
NULL );
}
BOOL
AtqAddAsyncHandleEx(
PATQ_CONT * ppatqContext,
PVOID ClientContext,
ATQ_COMPLETION pfnOnConnect,
ATQ_COMPLETION pfnCompletion,
DWORD TimeOut,
HANDLE hAsyncIO,
BOOL fAddToPort,
HANDLE hListenSocket,
PVOID * pvBuff,
DWORD cbBuff,
PATQ_CONT pReuseableAtq,
ACCEPTEX_LISTEN_INFO * pListenInfo
)
/*++
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
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
pfnOnConnect - Connection completion routine if AcceptEx is being used
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
fAddToPort - TRUE if the socket should be added to the completion port
hListenSocket - Socket handle of adapter requests are arriving on
pvBuff - Initial receive buffer if this is a listen type request. If this
is non-NULL and the context is NULL, then the context is set to the
atq context.
cbBuff - size of pvBuff
pReusableAtq - Pointer to ATQ context to reuse or
NULL to allocate a new one
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
--*/
{
BOOL fReturn = TRUE;
DWORD dwError = NO_ERROR;
PATQ_CONT pContext;
if ( ppatqContext )
{
ATQ_CONTEXT atqContext; // a stack variable for initializations
//
// Round TimeOut up to be an even interval of ATQ_TIMEOUT_INTERVAL
//
TimeOut = ATQ_ROUNDUP_TIMEOUT( TimeOut );
//
// Fill out the context. We set TimeTillTimeOut to INFINITE
// so the timeout thread will ignore this entry until an IO
// request is made unless this is an AcceptEx socket, that means
// we're about to submit the IO.
//
atqContext.Signature = ATQ_SIGNATURE;
atqContext.ClientContext = ClientContext;
atqContext.pfnCompletion = pfnCompletion;
atqContext.dwAtqCtxtFlags = 0L;
atqContext.TimeOut = TimeOut;
atqContext.TimeTillTimeOut = INFINITE;
atqContext.hAsyncIO = hAsyncIO;
memset( &atqContext.Overlapped, 0, sizeof( atqContext.Overlapped ));
//
// These data members are used if we're doing AcceptEx processing
//
// Note that if we're not using AcceptEx, then we consider the client
// to have been notified externally (thus fConnectionIndicated is
// set to TRUE).
//
atqContext.pvBuff = pvBuff;
atqContext.cbBuff = cbBuff;
atqContext.fConnectionIndicated = FALSE;
atqContext.pfnConnComp = pfnOnConnect;
atqContext.SockState = ATQ_SOCK_CONNECTED;
atqContext.pListenInfo = pListenInfo;
#if DBG
atqContext.ListEntry.Flink = atqContext.ListEntry.Blink = NULL;
#endif
// Following added for bandwidth throttling purposes
atqContext.fBlocked = FALSE;
atqContext.arInfo.atqOp = AtqIoNone;
atqContext.arInfo.lpOverlapped = NULL;
// bandwidth throttling initialization ends here.
if (g_fShutdown == TRUE) {
pContext = NULL;
dwError = ERROR_NOT_READY;
} else {
if ( pReuseableAtq ) {
pContext = pReuseableAtq;
}
else {
//
// Note we take and release the lock here as we're
// optimizing for the reuseable context case
//
AtqLockGlobals();
pContext = AtqAllocContextFromCache();
AtqUnlockGlobals();
}
if ( pContext != NULL) {
// set the context values properly
RtlCopyMemory( pContext, &atqContext, sizeof(ATQ_CONTEXT));
DBG_CODE(
ATQ_ASSERT(atqContext.Signature == pContext->Signature);
ATQ_ASSERT(atqContext.ClientContext == pContext->ClientContext);
ATQ_ASSERT(atqContext.TimeOut == pContext->TimeOut);
);
//
// If our timeout thread is in long sleep, then we need to
// alert it after adding an entry to the list.
//
AtqLockGlobals();
ATQ_ASSERT( pContext->ListEntry.Flink == NULL &&
pContext->ListEntry.Blink == NULL );
InsertTailList( &g_AtqClientHead, &pContext->ListEntry );
AtqUnlockGlobals();
*ppatqContext = pContext;
} else {
dwError = ERROR_NOT_ENOUGH_MEMORY;
}
}
} else {
dwError = ERROR_INVALID_PARAMETER ;
}
if ( dwError != NO_ERROR) {
SetLastError( dwError);
return (FALSE);
}
//
// Make sure there are pool threads to service the request
//
if ( (fReturn = AtqCheckThreadStatus()) ) {
/*
ATQ_ASSERT( g_hCompPort );
if ( fAddToPort )
{
fReturn = (
CreateIoCompletionPort( hAsyncIO,
g_hCompPort,
(DWORD) pContext,
g_cConcurrency)
!= NULL
);
}
*/
} // if ( AtqCheckThreadStatus())
return (fReturn);
} // AtqAddAsyncHandle()
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;
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
//
if ( pContext->hAsyncIO == NULL )
{
ATQ_DBGPRINT("[AtqCloseSocket] Warning - NULL socket on close\n");
}
if(pContext->arInfo.atqOp == AtqIoXmitFile) {
ATQ_PRINTF((buff,
"[AtqCloseSocket(%lu)] Warning - transmission in progress socket = %x\n",
GetCurrentThreadId(),pContext->hAsyncIO ));
//ATQ_ASSERT( TRUE );
}
if ( fShutdown )
{
//
// 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) pContext->hAsyncIO, 1 );
}
WSAAsyncSelect((int) pContext->hAsyncIO,
g_hwndAtqDispatch,
0,
0);
if ( closesocket( (int) pContext->hAsyncIO ) )
{
ATQ_PRINTF((buff,
"[AtqCloseSocket(%lu)] Warning - closesocket failed, error %d, socket = %x\n",
GetCurrentThreadId(),
GetLastError(),
pContext->hAsyncIO ));
}
pContext->hAsyncIO = NULL;
break;
}
return TRUE;
}
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
VOID
AtqFreeContext(
PATQ_CONTEXT patqContext,
BOOL fReuseContext
)
/*++
Routine Description:
Frees the context created in AtqAddAsyncHandle
Arguments:
patqContext - Context to free
--*/
{
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
ATQ_ASSERT( pContext != NULL );
if ( pContext )
{
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE );
//
// Remove the entry from the timeout list and move it to
// allocation cache
//
AtqLockGlobals();
RemoveEntryList( &pContext->ListEntry );
#if DBG
pContext->ListEntry.Flink = pContext->ListEntry.Blink = NULL;
#endif
//
// It is possible that there are events in the queue of completed
// results for this context. We need to remove those
//
if ( !IsListEmpty(&g_AtqCompletedIoRequests)) {
LIST_ENTRY * pentry;
LIST_ENTRY * pentryNext;
PATQWIN_COMPLETION_MSG pAtqWinMsg = NULL;
for ( pentry = g_AtqCompletedIoRequests.Flink;
pentry != &g_AtqCompletedIoRequests;
pentry = pentryNext )
{
pentryNext = pentry->Flink;
pAtqWinMsg = CONTAINING_RECORD( pentry,
ATQWIN_COMPLETION_MSG,
ListEntry );
if (pAtqWinMsg->pAtqContext == patqContext) {
RemoveEntryList(pentry);
}
}
}
AtqUnlockGlobals();
AtqLockGlobals();
AtqFreeContextToCache( pContext);
AtqUnlockGlobals();
}
return;
} // AtqFreeContext()
DWORD
AtqPoolThread(
LPDWORD param
)
/*++
Routine Description:
This is the pool thread wait and dispatch routine
Arguments:
param : unused.
Return Value:
Thread return value (ignored)
--*/
{
PATQ_CONT pContext;
BOOL fRet;
LPOVERLAPPED lpo;
DWORD cbWritten;
DWORD returnValue;
UNREFERENCED_PARAMETER(param);
for(;;)
{
InterlockedIncrement( &g_cAvailableThreads );
fRet = WinGetQueuedCompletionStatus(&cbWritten,
(LPDWORD)&pContext,
&lpo,
g_msThreadTimeout );
InterlockedDecrement( &g_cAvailableThreads );
//
// Successfuly unblocked, but list is empty - wait again
//
if(!lpo) {
continue;
}
if ( fRet || lpo) {
if ( pContext == NULL && g_fShutdown ) {
//
// This is our signal to exit.
//
returnValue = NO_ERROR;
break;
}
ATQ_ASSERT( lpo );
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE );
// BUGBUG If context is signalled, which is already freed
// We should find the reason for this
if( pContext->Signature != ATQ_SIGNATURE) {
continue;
}
//
// We need to make sure the timeout thread doesn't time this
// request out so reset the timeout value
//
InterlockedExchange( (LPLONG )&pContext->TimeTillTimeOut,
(LONG ) INFINITE);
if ( pContext->pfnCompletion )
{
//
// If an error occurred on a TransmitFile (or other IO),
// set the state to connected so the socket will get
// closed on cleanup
//
if ( !fRet && pContext->SockState == ATQ_SOCK_UNCONNECTED )
{
pContext->SockState = ATQ_SOCK_CONNECTED;
}
pContext->pfnCompletion( pContext->ClientContext,
cbWritten,
//fRet ? NO_ERROR : pContext->dwLastError,
pContext->dwLastError,
lpo );
}
}
else
{
//
// An error occurred. Either the thread timed out, the handle
// is going away or something bad happened. Let the thread exit.
//
// BUGBUG Do not exit if thread count is 1 VLADS . Otherwise if there is
// only one thread and waiting contexts in queue they will not dequed
if ((g_cThreads == 1) && !g_fShutdown)
continue;
returnValue = GetLastError();
break;
}
//
// After x requests, recheck thread usage to see if more should be
// created. NYI
//
//
// THREAD TUNING CODE GOES HERE
//
} // for
if ( InterlockedDecrement( &g_cThreads ) == 0 ) {
//
// Wake up ATQTerminate()
//
SetEvent( g_hShutdownEvent );
}
return returnValue;
} // AtqPoolThread()
BOOL
AtqCheckThreadStatus(
VOID
)
/*++
Routine Description:
This routine makes sure there is at least one thread in
the thread pool. We're fast and loose so a couple of extra
threads may be created.
Arguments:
Return Value:
TRUE if successful, FALSE on error (call GetLastError)
--*/
{
BOOL fRet = TRUE;
//
// If no threads are available, kick a new one off up to the limit
//
// WE NEED TO CHANGE THE CONDITIONS FOR STARTING ANOTHER THREAD
// IT SHOULD NOT BE VERY EASY TO START A THREAD ....
//
if ( g_cThreads < g_cMaxThreads &&
g_cAvailableThreads == 0 )
{
HANDLE hThread;
DWORD dwThreadID;
InterlockedIncrement( &g_cThreads );
hThread = CreateThread( NULL,
0,
(LPTHREAD_START_ROUTINE)AtqPoolThread,
NULL,
0,
&dwThreadID );
if ( hThread )
{
CloseHandle( hThread ); // Free system resources
}
else
{
InterlockedDecrement( &g_cThreads );
fRet = FALSE;
}
}
return fRet;
} // AtqCheckThreadStatus()
BOOL
AtqProcessTimeoutOfRequests(VOID)
{
BOOL fRet = TRUE;
PATQ_CONT pContext;
LIST_ENTRY * pentry;
LIST_ENTRY * pentryNext;
DWORD TimeRemaining;
//
// Scan the timeout list looking for items that have timed out
// and adjust the timeout values
//
for ( pentry = g_AtqClientHead.Flink;
pentry != &g_AtqClientHead;
pentry = pentryNext ) {
pentryNext = pentry->Flink;
pContext = CONTAINING_RECORD( pentry, ATQ_CONTEXT,
ListEntry );
if ( pContext->Signature != ATQ_SIGNATURE ) {
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE );
break;
}
//
// The client specifies the IO doesn't timeout if
// INFINITE is in the TimeOut field of the ATQ context
// If we've timed out, then notify the client.
//
TimeRemaining = pContext->TimeTillTimeOut;
if ( TimeRemaining != INFINITE &&
!(TimeRemaining -= ATQ_TIMEOUT_INTERVAL)) {
// We need to adjust the time remaining and send a timeout callback
//
// Call client after re-checking that this item
// really has timed out
//
if ( InterlockedExchange( (LPLONG ) &pContext->TimeTillTimeOut,
INFINITE ) ==
(LONG) (TimeRemaining + ATQ_TIMEOUT_INTERVAL)) {
//
// If we've already indicated this connection to the client,
// then we abort them by calling their IO completion routine
// and letting them cleanup. Otherwise we close the socket
// which will generally cause an IO aborted completion that
// we will cleanup. Note there is a window where we may
// close the socket out from under a client in their
// connection completion routine but that should be ok.
//
if ( pContext->pfnCompletion &&
pContext->fConnectionIndicated ) {
pContext->pfnCompletion(pContext->ClientContext,
0,
ERROR_SEM_TIMEOUT,
NULL );
} else {
closesocket( (SOCKET) pContext->hAsyncIO );
}
}
} else {
if ( TimeRemaining != INFINITE) {
// Store the new time remaining value in the object
InterlockedExchange( &pContext->TimeTillTimeOut,
TimeRemaining);
}
}
} // scan list
return (fRet);
} // AtqProcessRequest()
# define NUM_SAMPLES_PER_TIMEOUT_INTERVAL \
(ATQ_TIMEOUT_INTERVAL/ ATQ_SAMPLE_INTERVAL_IN_SECS)
// Calculate timeout in milliseconds from timeout in seconds.
# define TimeToWait(Timeout) (((Timeout) == INFINITE) ?INFINITE: \
(Timeout) * 1000)
DWORD
AtqTimeoutThread(
LPDWORD param
)
/*++
Routine Description:
This is the thread that checks for timeouts
Clients should not call AtqFreeContext in their timeout
processing (could deadlock on global critical section).
The thread assumes timeouts are rounded to ATQ_TIMEOUT_INTERVAL
In addition to timing out requests when necessary, the timeout thread
also performs the job of bandwidth calculation and tuning the bandwidth
throttle operation (which works on feedback mechanism).
At every sampling interval the thread is awoken and it updates
the bandwidth.
Arguments:
param - Unused
Return Value:
Thread return value (ignored)
--*/
{
/*
BOOL fLongSleepPending = FALSE;
DWORD Timeout = INFINITE; // In seconds
DWORD msTimeInterval;
DWORD cSamplesForTimeout; // number of samples before normal timeout
// we require the sampling interval to be less than the request timeout
// interval. So that we may be able to use feedback mechanism effectively.
ATQ_ASSERT( ATQ_SAMPLE_INTERVAL_IN_SECS < ATQ_TIMEOUT_INTERVAL);
cSamplesForTimeout = NUM_SAMPLES_PER_TIMEOUT_INTERVAL;
msTimeInterval = GetTickCount();
for(;;)
{
DWORD dwErr = WaitForSingleObject(g_hTimeoutEvent,TimeToWait(Timeout));
switch (dwErr) {
//
// Somebody wants us to wake up and start working
//
case WAIT_OBJECT_0:
AtqLockGlobals();
if (g_fShutdown) {
AtqUnlockGlobals();
return 0;
}
g_fLongSleep = FALSE;
AtqUnlockGlobals();
if ( g_fBandwidthThrottle) {
msTimeInterval = GetTickCount();
Timeout = ATQ_SAMPLE_INTERVAL_IN_SECS;
cSamplesForTimeout = NUM_SAMPLES_PER_TIMEOUT_INTERVAL;
} else {
Timeout = ATQ_TIMEOUT_INTERVAL;
cSamplesForTimeout = 1; // only timeout matters
}
continue;
//
// Somebody must have closed the event, time to leave
//
default:
return 0;
//
// When there is work to do, we wakeup every x seconds to look at
// the list. That's what we need to do now.
//
case WAIT_TIMEOUT:
ATQ_ASSERT( cSamplesForTimeout >= 1);
--cSamplesForTimeout;
if ( g_fBandwidthThrottle) {
msTimeInterval = GetTickCount() - msTimeInterval;
// Perform a sampling to update measured bandwidth +
// apply feedback policy
//AbwUpdateBandwidth( msTimeInterval);
DBG_CODE(
DBGPRINTF(( DBG_CONTEXT, "Sample @%u: Bandwidth = %u\n",
GetTickCount(), g_dwMeasuredBw));
);
if ( cSamplesForTimeout != 0) {
// We have not reached timeout yet. Proceed to wait
continue;
}
}
// We had reached the timeout interval for requests.
// Examine and release requests.
ATQ_ASSERT( cSamplesForTimeout == 0);
// reset the count of samples before proceeding.
cSamplesForTimeout = ((g_fBandwidthThrottle)
? NUM_SAMPLES_PER_TIMEOUT_INTERVAL
: 1);
// We are at a Timeout Interval. Examine and timeout requests.
AtqLockGlobals(); // Prevents adding/removing items
//
// If the list is empty, then turn off timeout processing
// We actually wait for one complete ATQ_INTERVAL before
// entering the sleep mode.
//
if ( IsListEmpty( &g_AtqClientHead )) {
DBGPRINTF(( DBG_CONTEXT, "Atq List Empty\n"));
if ( fLongSleepPending) {
// We ought to enter long sleep mode.
g_fLongSleep = TRUE;
AtqUnlockGlobals();
Timeout = INFINITE;
fLongSleepPending = FALSE;
DBG_CODE( DBGPRINTF((DBG_CONTEXT, "Sleeping Long...\n")););
} else {
fLongSleepPending = TRUE;
AtqUnlockGlobals();
}
continue;
}
// reset that a long sleep is not pending
fLongSleepPending = FALSE;
ATQ_REQUIRE( AtqProcessTimeoutOfRequests());
AtqUnlockGlobals();
continue;
// case WAIT_TIMEOUT: ends
} // switch
} // for(ever)
*/
return 0;
} // AtqTimeoutThread()
DWORD
AtqSetContextInfo(
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;
if ( Data != INFINITE )
pContext->TimeOut = ATQ_ROUNDUP_TIMEOUT( Data );
else
pContext->TimeOut = INFINITE;
break;
case ATQ_INFO_COMPLETION:
dwOldVal = (DWORD) pContext->pfnCompletion;
pContext->pfnCompletion = (ATQ_COMPLETION) Data;
break;
case ATQ_INFO_COMPLETION_CONTEXT:
dwOldVal = (DWORD) pContext->ClientContext;
pContext->ClientContext = (void *) Data;
break;
default:
ATQ_ASSERT( FALSE );
}
}
return dwOldVal;
}
/*++
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)
//DWORD dwErr;
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
pContext->TimeTillTimeOut = pContext->TimeOut;
pContext->dwLastError = NO_ERROR;
if ( !lpo )
{
lpo = &pContext->Overlapped;
}
//ATQ_ASSERT( g_pStatus != NULL);
ATQ_PRINTF((buff,
"[AtqReadFileRequest(%lu)] Socket = %x pContext=%x\n",
GetCurrentThreadId(),
pContext->hAsyncIO,pContext));
switch ( g_pStatus[AtqIoRead]) {
case StatusAllowOperation:
//INC_ATQ_COUNTER( g_cTotalAllowedRequests);
// store data for retrieving later in WSM_SELECT method
pContext->arInfo.atqOp = AtqIoRead;
pContext->arInfo.lpOverlapped = lpo;
pContext->arInfo.uop.opReadWrite.lpBuffer = lpBuffer;
pContext->arInfo.uop.opReadWrite.cbBuffer = BytesToRead;
fRes = AtqStartAsyncOperation(pContext);
/*
dwErr = WSAAsyncSelect((SOCKET)pContext->hAsyncIO,
g_hwndAtqDispatch,
WSM_SELECT,
FD_READ | FD_CLOSE
);
fRes = (dwErr != SOCKET_ERROR);
*/
/*
fRes = ( ReadFile( pContext->hAsyncIO,
lpBuffer,
BytesToRead,
&cbRead,
lpo ) ||
GetLastError() == ERROR_IO_PENDING);
*/
//
// Queue
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)
//DWORD dwErr;
PATQ_CONT pContext = (PATQ_CONT ) patqContext;
pContext->dwLastError = NO_ERROR;
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
pContext->TimeTillTimeOut = pContext->TimeOut;
if ( !lpo )
{
lpo = &pContext->Overlapped;
}
ATQ_PRINTF((buff,
"[AtqWriteFileRequest(%lu)] Socket = %x pContext=%x\n",
GetCurrentThreadId(),
pContext->hAsyncIO,pContext));
ATQ_ASSERT( g_pStatus != NULL);
switch ( g_pStatus[AtqIoWrite]) {
case StatusAllowOperation:
//INC_ATQ_COUNTER( g_cTotalAllowedRequests);
pContext->arInfo.atqOp = AtqIoWrite;
pContext->arInfo.lpOverlapped = lpo;
pContext->arInfo.uop.opReadWrite.lpBuffer = (LPVOID) lpBuffer;
pContext->arInfo.uop.opReadWrite.cbBuffer = BytesToWrite;
fRes = AtqStartAsyncOperation(pContext);
/*
dwErr = WSAAsyncSelect((SOCKET)pContext->hAsyncIO,
g_hwndAtqDispatch,
WSM_SELECT,
FD_WRITE | FD_CLOSE
);
fRes = (dwErr != SOCKET_ERROR);
*/
/*
fRes = ( WriteFile( pContext->hAsyncIO,
lpBuffer,
BytesToWrite,
&cbWritten,
lpo ) ||
GetLastError() == ERROR_IO_PENDING);
*/
// store data for completion code
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;
DWORD Timeout;
//DWORD dwErr;
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE );
ATQ_ASSERT( pContext->arInfo.atqOp == AtqIoNone);
//
// For large file sends, the client's default timeout may not be
// adequte for slow links. Scale based on bytes being sent
//
// We plan for worst case of 1k per second
//
//
// BUGBUG - Subst. LowPart for QuadPart due to compile warnings
//
Timeout = ATQ_ROUNDUP_TIMEOUT( liBytesInFile.LowPart / 1000 );
pContext->TimeTillTimeOut = max( pContext->TimeOut, Timeout);
pContext->dwLastError = NO_ERROR;
ATQ_ASSERT( g_pStatus != NULL);
switch ( g_pStatus[AtqIoXmitFile]) {
case StatusAllowOperation:
//INC_ATQ_COUNTER( g_cTotalAllowedRequests);
dwFlags = 0;
//
// If the socket is getting disconnected, mark it appropriately
//
if ( dwFlags & TF_DISCONNECT )
{
if ( dwFlags & TF_REUSE_SOCKET )
{
pContext->SockState = ATQ_SOCK_UNCONNECTED;
}
else
{
pContext->SockState = ATQ_SOCK_CLOSED;
}
}
#ifndef CHICAGO
fRes = ( TransmitFile( (SOCKET ) pContext->hAsyncIO,
hFile,
0, // send entire file.
0,
&pContext->Overlapped,
lpTransmitBuffers,
dwFlags ) ||
GetLastError() == ERROR_IO_PENDING);
#else
//
// Store data
//
pContext->pvBuff = NULL;
pContext->cbBuff = 0;
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;
memcpy(&pContext->arInfo.uop.opXmit.TransmitBuffers,
lpTransmitBuffers,
sizeof(TRANSMIT_FILE_BUFFERS));
pContext->arInfo.uop.opXmit.dwFlags = dwFlags;
pContext->arInfo.uop.opXmit.CurrentState = ATQ_XMIT_START;
pContext->arInfo.uop.opXmit.fRetry = FALSE;
ATQ_PRINTF((buff,
"[AtqTransmitFile(%lu)] Request to transmit Socket = %x pContext=%x hFile= %x \n",
GetCurrentThreadId(),
pContext->hAsyncIO,pContext,hFile ));
fRes = AtqStartAsyncOperation(pContext);
/*
dwErr = WSAAsyncSelect((SOCKET)pContext->hAsyncIO,
g_hwndAtqDispatch,
WSM_SELECT,
FD_WRITE | FD_CLOSE
);
fRes = (dwErr != SOCKET_ERROR);
*/
#endif
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;
memcpy(&pContext->arInfo.uop.opXmit.TransmitBuffers,
lpTransmitBuffers,
sizeof(TRANSMIT_FILE_BUFFERS));
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;
}
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) {
ATQ_PRINTF((buff,
"[AtqPostCompletionEvent(%lu)] Queuing completion event Context %x \n",
GetCurrentThreadId(),
patqContext));
fRes = ( WinPostQueuedCompletionStatus( //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;
}
/**************************************************
* Allocation Caching of ATQ contexts
* Author: Murali R. Krishnan (MuraliK) 5-July-1995
**************************************************/
BOOL AtqInitAllocCachedContexts( VOID)
/*++
This function initializes allocation caching of Atq contexts to be
used for holding free atq contexts for reuse.
The main purpose of allocation cache is to avoid calls to the Heap allocator
whenever a new ATQ context needs to be allocated. This reduces the
contention at the heap's critical section.
Presently this initializes a single list head to hold free list of contexts
Since this function is called at initialization of ATQ module,
there is no or less contention. Hence, we can allocate a certain fixed
number of initial contexts to avoid slow starts. Defered for now.
Arguments:
NONE
Returns:
TRUE on success and FALSE on failure.
--*/
{
InitializeListHead( &g_AtqFreeList);
return ( TRUE);
} // AtqInitAllocCachedContexts()
BOOL
AtqFreeAllocCachedContexts( VOID)
/*++
This function frees all the Atq contexts that were alloc cached.
It walks through the list of alloc cached contexts and frees them.
This function should be called when ATQ module is terminated and when
no other thread can interfere in processing a shared object.
Arguments:
NONE
Returns:
TRUE on success and FALSE on failure.
--*/
{
#ifndef CHICAGO
register PLIST_ENTRY pEntry;
register PLIST_ENTRY pEntryNext;
register PATQ_CONT pAtqContext;
for( pEntry = g_AtqFreeList.Flink;
pEntry != &g_AtqFreeList; ) {
pEntryNext = pEntry->Flink;
pAtqContext = CONTAINING_RECORD( pEntry, ATQ_CONTEXT, ListEntry );
ATQ_ASSERT( pAtqContext->Signature == ATQ_FREE_SIGNATURE);
RemoveEntryList( pEntry ); // Remove this context from list
#if DBG
pEntry->Flink = pEntry->Blink = NULL;
#endif
LocalFree( pAtqContext); // free the context
pEntry = pEntryNext;
} // for
ATQ_ASSERT( g_AtqFreeList.Flink == &g_AtqFreeList);
#endif
return (TRUE);
} // AtqFreeAllocCachedContexts()
PATQ_CONT
AtqAllocContextFromCache( VOID)
/*++
This function attempts to allocate an ATQ context from the allocation cache
using the free list of atq contexts available.
If none is available, then a new context is allocated using LocalAlloc() and
returned to caller. Eventually the context will enter free list and will
be available for free use.
Arguments:
None
Returns:
On success a valid pointer to ATQ_CONT. Otherwise NULL.
Issues:
This function should be called while holding global lock.
AtqLockGlobals() can be used for the same.
Should we use a separate lock for allocation cache?? Maybe. NYI
--*/
{
PATQ_CONT pAtqContext;
pAtqContext = LocalAlloc( LPTR, sizeof(ATQ_CONTEXT));
memset( pAtqContext, 0, sizeof( ATQ_CONTEXT ));
return (pAtqContext);
} // AtqAllocContextFromCache()
VOID
AtqFreeContextToCache( IN PATQ_CONT pAtqContext)
/*++
This function releases the given context to the allocation cache.
It adds the given context object to list of context objects on free list.
Arguments:
pAtqContext pointer to the ATQ_CONTEXT that is being freed.
Returns:
None
Issues:
This function should be called after holding the global critical section
for the ATQ module. Should we have a separate critical section??
Should we limit the number of items that can be on free list and
to release the remaining to global pool? NYI (depends on # CPUs)
--*/
{
ATQ_ASSERT( pAtqContext->Signature == ATQ_SIGNATURE );
//
// Delete the socket handle if it's not already deleted
//
if ( pAtqContext->hAsyncIO != NULL )
{
if ( closesocket( (SOCKET) pAtqContext->hAsyncIO ) == SOCKET_ERROR )
{
ATQ_PRINTF((buff,
"[AtqFreeContextToCache(%lu)] Warning - closesocket failed, error %d, socket = %x\n",
GetCurrentThreadId(),
GetLastError(),
pAtqContext->hAsyncIO ));
}
pAtqContext->hAsyncIO = NULL;
}
if ( pAtqContext->pvBuff )
{
LocalFree( pAtqContext->pvBuff );
pAtqContext->pvBuff = NULL;
}
LocalFree( pAtqContext); // free the context
return;
} // AtqFreeContextToCache()
/*
*
* Following is temporary support for window based io completion dispatching
*
*/
BOOL
AtqMasterLoop(
VOID
)
{
MSG msg;
while(GetMessage(&msg,NULL,0,0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return TRUE;
}
HWND
AtqGetDispatchWindow(
VOID
)
{
return g_hwndAtqDispatch;
}
BOOL
WinCreateIoCompletionPort(
IN UINT uiConcurrency
)
{
WNDCLASSEX wc;
HWND hwnd;
memset(&wc,0,sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = AtqDispatchWinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = g_hProcessInstance;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.lpszClassName = g_szAtqWindowClassName;
if (!RegisterClassEx(&wc))
return FALSE;
hwnd = CreateWindowEx(0, // Style bits
g_szAtqWindowClassName, // Class name
"", // Title
WS_OVERLAPPEDWINDOW , // Window style bits
CW_USEDEFAULT, // x
CW_USEDEFAULT, // y
CW_USEDEFAULT, // h
CW_USEDEFAULT, // w
NULL, // Parent
NULL, // Menu
g_hProcessInstance, // Module instance
NULL); // Options
if(hwnd) {
// Register custom message
g_hwndAtqDispatch = hwnd;
return TRUE;
}
return FALSE;
}
LRESULT CALLBACK
AtqDispatchWinProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
BOOL fReadOp = TRUE;
PATQ_CONT pContext = NULL;
switch(uMsg) {
case WM_CREATE:
// Create timer for timeout processing
break;
case WM_DESTROY:
// Destroy timer
PostQuitMessage(0);
break;
case WM_TIMER:
// Do timeout processing
break;
case WSM_SELECT:
//
// Block messages for this socket
//
WSAAsyncSelect((SOCKET)wParam,
g_hwndAtqDispatch,
0,
0);
WinAtqOnSelect((SOCKET)wParam,WSAGETSELECTEVENT(lParam));
break;
case WSM_IO_REQUEST:
// We need to initiate I/O
pContext = (PATQ_CONT)lParam;
// Is socket just closed ?
if (pContext->dwAtqCtxtFlags & ATF_SOCKET_CLOSED) {
// Then no need to call select, we can just fail this I/O
// BUGBUG For transmit file do we need to keep running transmission count ?
pContext->dwLastError = WSAECONNABORTED;
// Queue context as completed
WinPostQueuedCompletionStatus(0,
(DWORD)pContext,
pContext->arInfo.lpOverlapped
);
}
else {
if (pContext->arInfo.atqOp != AtqIoRead) {
fReadOp = FALSE;
}
// Indicate that Select has been called on this socket
pContext->dwAtqCtxtFlags |= ATF_IO_SELECTED;
WSAAsyncSelect((SOCKET)wParam,
hwnd,
WSM_SELECT,
(fReadOp ? FD_READ : FD_WRITE) | FD_CLOSE);
}
break;
case WSM_CLOSE_SOCKET:
{
int err = NO_ERROR;
err = closesocket((SOCKET)wParam);
if ( WSAEWOULDBLOCK == err ) {
WSAAsyncSelect((SOCKET)wParam,
hwnd,
WSM_SELECT,
FD_CLOSE);
}
}
break;
default:
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
}
#define TRANSMIT_FILE_BUFFER 10000
BOOL
WinAtqOnSelect(
SOCKET sc,
UINT uiFunction
)
{
UINT cbBytesTransferred;
BOOL fRes = FALSE;
DWORD dwErr = NOERROR;
PATQ_CONT pContext = NULL;
switch(uiFunction) {
case FD_CLOSE:
// Find Atq context for this socket
if (!FindATQContextFromSocket(sc,&pContext)) {
// Socket got orphaned , possibly closed
// We need to post completion message with closed notification
ATQ_PRINTF((buff,
"[AtqSelect(%lu)] Failed to find context for socket %x\n",
GetCurrentThreadId(),
sc ));
return FALSE;
}
ATQ_PRINTF((buff,
"[AtqSelect(%lu)] Received FD_CLOSE for socket %x\n",
GetCurrentThreadId(),
sc ));
// If there is I/O in progress and select has been performed, we need
// to post failure completion event
pContext->dwAtqCtxtFlags |= ATF_SOCKET_CLOSED;
if (pContext->dwAtqCtxtFlags & ATF_IO_SELECTED ) {
pContext->dwLastError = WSAECONNABORTED;
cbBytesTransferred = 0;
// Queue context as completed
WinPostQueuedCompletionStatus(cbBytesTransferred,
(DWORD)pContext,
pContext->arInfo.lpOverlapped
);
}
fRes = TRUE;
break;
case FD_ACCEPT:
// Call onConnection routine (conn.cxx)
break;
case FD_READ:
case FD_WRITE:
// Async IO completed
// Find Atq context for this socket
if (!FindATQContextFromSocket(sc,&pContext)) {
ATQ_PRINTF((buff,
"[AtqSelect(%lu)] Failed to find context for socket %x\n",
GetCurrentThreadId(),
sc ));
// Socket got orphaned , possibly closed
// We need to post completion message with closed notification
return FALSE;
}
// Indicate that select notification received
pContext->dwAtqCtxtFlags &= ~ATF_IO_SELECTED;
// If socket has been closed, bail out immideately
if (pContext->dwAtqCtxtFlags & ATF_SOCKET_CLOSED ) {
// Queue context as completed
pContext->dwLastError = WSAECONNABORTED;
WinPostQueuedCompletionStatus(0,
(DWORD)pContext,
pContext->arInfo.lpOverlapped
);
fRes = TRUE;
break;
}
// Make appropriate socket call into buffers from Atqcontext
switch(pContext->arInfo.atqOp ) {
case AtqIoRead:
dwErr = recv(sc,
pContext->arInfo.uop.opReadWrite.lpBuffer,
pContext->arInfo.uop.opReadWrite.cbBuffer,
0);
break;
case AtqIoWrite:
dwErr = send(sc,
pContext->arInfo.uop.opReadWrite.lpBuffer,
pContext->arInfo.uop.opReadWrite.cbBuffer,
0);
break;
case AtqIoXmitFile:
//
// Read next buffer and send it .
// If something left - issue WSASelect
//
{
LPVOID lpBufferSend;
DWORD cbWrite = 0;
int err = NO_ERROR;
//
// If we are retrying - skip all logic
//
if(pContext->arInfo.uop.opXmit.fRetry) {
lpBufferSend = pContext->arInfo.uop.opXmit.pvLastSent;
cbWrite = pContext->arInfo.uop.opXmit.dwLastSentBytes;
}
else {
if (pContext->arInfo.uop.opXmit.CurrentState == ATQ_XMIT_START) {
//
// We are just starting transmission, check if there is any
// header part to send
//
cbWrite = pContext->arInfo.uop.opXmit.TransmitBuffers.HeadLength;
pContext->arInfo.uop.opXmit.CurrentState = ATQ_XMIT_HEADR_SENT;
lpBufferSend = pContext->arInfo.uop.opXmit.TransmitBuffers.Head;
if (cbWrite && lpBufferSend) {
goto AtqXmitSendData;
}
}
if (pContext->arInfo.uop.opXmit.CurrentState == ATQ_XMIT_HEADR_SENT) {
//
// We are reading / sending file itself
// Read next portion
if (!pContext->pvBuff) {
pContext->pvBuff = LocalAlloc(LPTR, TRANSMIT_FILE_BUFFER);
}
if (pContext->pvBuff) {
//
// Read file
//
cbWrite = 0;
dwErr = ReadFile(pContext->arInfo.uop.opXmit.hFile,
pContext->pvBuff,
TRANSMIT_FILE_BUFFER,
&cbWrite,
NULL);
if (dwErr && cbWrite) {
lpBufferSend = pContext->pvBuff;
goto AtqXmitSendData;
}
}
if (pContext->pvBuff) {
LocalFree(pContext->pvBuff);
pContext->pvBuff = NULL;
}
// We are finished with this file
pContext->arInfo.uop.opXmit.CurrentState = ATQ_XMIT_FILE_DONE;
}
if (pContext->arInfo.uop.opXmit.CurrentState == ATQ_XMIT_FILE_DONE) {
//
// Check if there is any tail part to send
//
cbWrite = pContext->arInfo.uop.opXmit.TransmitBuffers.TailLength;
pContext->arInfo.uop.opXmit.CurrentState = ATQ_XMIT_TAIL_SENT;
lpBufferSend = pContext->arInfo.uop.opXmit.TransmitBuffers.Tail;
}
// THe last check
if (pContext->arInfo.uop.opXmit.CurrentState == ATQ_XMIT_FILE_DONE) {
ATQ_PRINTF((buff,
"[ ATQXMitting (%lu)]: Confusion, file has been already sent : Socket=%x Context=%x \n\r",
GetCurrentThreadId(),
sc,pContext));
return FALSE;
}
}
AtqXmitSendData:
dwErr = NO_ERROR;
if (cbWrite && lpBufferSend) {
dwErr = send(sc,
lpBufferSend,
cbWrite,
0);
err = WSAGetLastError();
ATQ_PRINTF((buff,
"[ ATQXMitting (%lu)]: Send result: Socket=%x Error=%d LastErr=%d \n\r",
GetCurrentThreadId(),
sc, dwErr,err));
//
// Check for the error
//
pContext->arInfo.uop.opXmit.fRetry = FALSE;
if (dwErr == SOCKET_ERROR) {
if (err == WSAEWOULDBLOCK) {
ATQ_DBGPRINT("ATQXmit: Retrying.... \n");
pContext->arInfo.uop.opXmit.fRetry = TRUE;
pContext->arInfo.uop.opXmit.pvLastSent = lpBufferSend;
pContext->arInfo.uop.opXmit.dwLastSentBytes = cbWrite;
}
else {
ATQ_PRINTF((buff,
" ATQXMitting : Send failed result: Socket=%x Error=%d LastErr=%d \n\r",
sc, dwErr,err));
}
}
}
if ( SOCKET_ERROR == dwErr ||
pContext->arInfo.uop.opXmit.CurrentState != ATQ_XMIT_TAIL_SENT) {
//
// Reenable async notifications and return
//
fRes = AtqStartAsyncOperation(pContext);
return TRUE;
}
// No error and it was last phase - send notification
ATQ_PRINTF((buff,
"[ ATQXMit (%lu)]: Finished transmitting file Socket=%x HFile=%x LastErr=%d \n\r",
GetCurrentThreadId(),
sc, pContext->arInfo.uop.opXmit.hFile,pContext->dwLastError));
// Indicate no transmission in progress
pContext->arInfo.uop.opXmit.CurrentState = ATQ_XMIT_NONE;
}
break;
default:
;
}
// Set the last error code in context
if (SOCKET_ERROR == dwErr) {
pContext->dwLastError = WSAGetLastError();
cbBytesTransferred = 0;
}
else {
pContext->dwLastError = NOERROR;
cbBytesTransferred = dwErr;
}
// Queue context as completed
WinPostQueuedCompletionStatus(cbBytesTransferred,
(DWORD)pContext,
pContext->arInfo.lpOverlapped
);
fRes = TRUE;
break;
default:;
}
return fRes;
}
BOOL
AtqStartAsyncOperation(
PATQ_CONT pContext
)
{
//
// Mark I/O operation in progress
//
pContext->dwAtqCtxtFlags |= ATF_IO_IN_PROCESS;
PostMessage(g_hwndAtqDispatch,
WSM_IO_REQUEST,
(WPARAM)pContext->hAsyncIO,
(LPARAM)pContext);
return TRUE;
}
BOOL
WinGetQueuedCompletionStatus(
LPDWORD pcbWritten,
LPDWORD pContext,
LPOVERLAPPED *plpo,
DWORD msThreadTimeout
)
{
DWORD dwErr = NOERROR;
BOOL fRes = FALSE;
PATQWIN_COMPLETION_MSG pAtqWinMsg = NULL;
LIST_ENTRY* pEntry;
// Wait on completed queue semaphore
dwErr = WaitForSingleObject( g_hPendingCompletionSemaphore,msThreadTimeout);
switch (dwErr) {
case WAIT_OBJECT_0:
// We have something in the queue
AtqLockGlobals();
// Remove first event from queue
if ( !IsListEmpty(&g_AtqCompletedIoRequests)) {
pEntry = g_AtqCompletedIoRequests.Flink;
pAtqWinMsg = CONTAINING_RECORD( pEntry,
ATQWIN_COMPLETION_MSG,
ListEntry );
RemoveEntryList(pEntry);
// Get atq context and overlapped buffer pointer from completion message
*plpo = pAtqWinMsg->lpOverlapped;
*pContext = (DWORD) pAtqWinMsg->pAtqContext;
*pcbWritten = pAtqWinMsg->cbWritten;
// Operation completed - reset IO command in ATQ context
if(pAtqWinMsg->pAtqContext) {
PATQ_CONT pAtqContextFull = (PATQ_CONT)(pAtqWinMsg->pAtqContext);
pAtqContextFull->arInfo.atqOp = AtqIoNone; // reset since operation done.
// No I/O in progress
pAtqContextFull->dwAtqCtxtFlags &= ~ATF_IO_IN_PROCESS;
}
ATQ_PRINTF((buff,
"[AtqGetQueueCompletionEvent(%lu)] Got event Context %x \n",
GetCurrentThreadId(),
*pContext));
}
else {
// Completion event was not dequeued - indicate to the caller
// That is error , if semaphore is signalled queue should contain at list one event
*plpo = NULL;
}
AtqUnlockGlobals();
// Free message
if(pAtqWinMsg) {
LocalFree(pAtqWinMsg);
}
fRes = TRUE;
break;
case WAIT_TIMEOUT:
default:
// Completion event was not dequeued - indicate to the caller
*plpo = NULL;
fRes = FALSE;
} // switch
return fRes;
}
BOOL
WinPostQueuedCompletionStatus(
DWORD dwBytesTransferred,
DWORD dwCompletionkey,
LPOVERLAPPED lpo
)
{
PATQWIN_COMPLETION_MSG pAtqWinMsg = NULL;
// allocate new message block
pAtqWinMsg = (PATQWIN_COMPLETION_MSG)LocalAlloc(LPTR,sizeof(ATQWIN_COMPLETION_MSG));
if(!pAtqWinMsg) {
//SetLastError(ERROR_OUT_OF_MEMORY);
return FALSE;
}
// Fill content
pAtqWinMsg->pAtqContext = (PATQ_CONTEXT) dwCompletionkey;
pAtqWinMsg->cbWritten = dwBytesTransferred;
pAtqWinMsg->lpOverlapped = lpo;
pAtqWinMsg->hAsyncIO = NULL;
AtqLockGlobals();
ATQ_PRINTF((buff,
"[WinAtqPostQueued(%lu)] Queuing Context %x \n",
GetCurrentThreadId(),
dwCompletionkey));
InsertTailList( &g_AtqCompletedIoRequests, &pAtqWinMsg->ListEntry );
// Wake up pool threads if they are waiting on the completion queue
ReleaseSemaphore(g_hPendingCompletionSemaphore,
1,
NULL);
AtqUnlockGlobals();
}
BOOL
FindATQContextFromSocket(
SOCKET sc,
PATQ_CONT *ppReturnContext
)
{
LIST_ENTRY * pentry;
LIST_ENTRY * pentryNext;
PATQ_CONT pContext;
*ppReturnContext = NULL;
AtqLockGlobals();
for ( pentry = g_AtqClientHead.Flink;
pentry != &g_AtqClientHead;
pentry = pentryNext )
{
pentryNext = pentry->Flink;
pContext = CONTAINING_RECORD( pentry, ATQ_CONTEXT,
ListEntry );
if ( pContext->Signature != ATQ_SIGNATURE ) {
ATQ_ASSERT( pContext->Signature == ATQ_SIGNATURE );
break;
}
if ((SOCKET) pContext->hAsyncIO == sc) {
// We found our context
*ppReturnContext = pContext;
break;
}
}
AtqUnlockGlobals();
return (*ppReturnContext != NULL);
}
BOOL
TsLogInformationA(
IN OUT DWORD hInetLog,
IN const VOID * pInetLogInfo
)
{
BOOL fReturn = FALSE;
return TRUE;
}
VOID
AtqGetAcceptExAddrs(
IN PATQ_CONTEXT patqContext,
OUT SOCKET * pSock,
OUT PVOID * ppvBuff,
OUT SOCKADDR * * ppsockaddrLocal,
OUT SOCKADDR * * ppsockaddrRemote
)
{
return;
}
/************************ End of File **************************/