/*++ Copyright (c) 1996-1997 Microsoft Corporation Module Name: servinfo.cxx Abstract: Class implementation for global server info list Contents: INTERNET_HANDLE_OBJECT::GetServerInfo INTERNET_HANDLE_OBJECT::FindServerInfo ReleaseServerInfo INTERNET_HANDLE_OBJECT::PurgeServerInfoList CServerInfo::CServerInfo CServerInfo::~CServerInfo CServerInfo::Reference CServerInfo::Dereference CServerInfo::UpdateConnectTime CServerInfo::UpdateRTT CServerInfo::GetConnection CFsm_GetConnection::RunSM CServerInfo::GetConnection_Fsm CServerInfo::ReleaseConnection CServerInfo::RemoveWaiter (CServerInfo::FindKeepAliveConnection) (CServerInfo::KeepAliveWaiters) (CServerInfo::RunOutOfConnections) (CServerInfo::UpdateConnectionLimit) CServerInfo::PurgeKeepAlives ContainingServerInfo Author: Richard L Firth (rfirth) 07-Oct-1996 Revision History: 07-Oct-1996 rfirth Created --*/ #include #include // // private macros // //#define CHECK_CONNECTION_COUNT() \ // INET_ASSERT(!UnlimitedConnections() \ // ? (TotalAvailableConnections() <= ConnectionLimit()) : TRUE) #define CHECK_CONNECTION_COUNT() /* NOTHING */ //#define RLF_DEBUG 1 #if INET_DEBUG #ifdef RLF_DEBUG #define DPRINTF dprintf #else #define DPRINTF (void) #endif #else #define DPRINTF (void) #endif // // functions // DWORD INTERNET_HANDLE_OBJECT::GetServerInfo( IN LPSTR lpszHostName, IN DWORD dwServiceType, IN BOOL bDoResolution, OUT CServerInfo * * lplpServerInfo ) /*++ Routine Description: Finds or creates a CServerInfo entry Arguments: lpszHostName - pointer to server name to get info for dwServiceType - type of service for which CServerInfo requested bDoResolution - TRUE if we are to resolve host name lplpServerInfo - pointer to created/found CServerInfo if successful Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_NOT_ENOUGH_MEMORY Couldn't create the CServerInfo ERROR_WINHTTP_NAME_NOT_RESOLVED We were asked to resolve the name, but failed --*/ { DEBUG_ENTER((DBG_SESSION, Dword, "GetServerInfo", "%q, %s (%d), %B, %#x", lpszHostName, InternetMapService(dwServiceType), dwServiceType, bDoResolution, lplpServerInfo )); ICSTRING hostName(lpszHostName); CServerInfo * lpServerInfo = NULL; BOOL bCreated = FALSE; DWORD error = ERROR_SUCCESS; if (hostName.HaveString()) { hostName.MakeLowerCase(); LPSTR lpszHostNameLower = hostName.StringAddress(); if (!LockSerializedList(&_ServerInfoList)) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } lpServerInfo = FindServerInfo(lpszHostNameLower); if (lpServerInfo == NULL) { lpServerInfo = New CServerInfo(&_ServerInfoList, lpszHostNameLower, &error, dwServiceType, GetMaxConnectionsPerServer(WINHTTP_OPTION_MAX_CONNS_PER_SERVER) ); if (lpServerInfo != NULL) { if (error != ERROR_SUCCESS) { delete lpServerInfo; lpServerInfo = NULL; } else { bCreated = TRUE; // Reference this to keep it alive beyond the unlock/ lpServerInfo->Reference(); } } else { error = ERROR_NOT_ENOUGH_MEMORY; } } UnlockSerializedList(&_ServerInfoList); } else { // // failed to create ICSTRING // error = GetLastError(); INET_ASSERT(error != ERROR_SUCCESS); lpServerInfo = NULL; } // // if we created a new CServerInfo and we are instructed to resolve the host // name then do it now, outside of the global server info list lock. This // operation may take some time // if (bDoResolution && (lpServerInfo != NULL)) { //error = lpServerInfo->ResolveHostName(); if (error != ERROR_SUCCESS) { ReleaseServerInfo(lpServerInfo); lpServerInfo = NULL; } } quit: *lplpServerInfo = lpServerInfo; DEBUG_LEAVE(error); return error; } CServerInfo * INTERNET_HANDLE_OBJECT::FindServerInfo( IN LPSTR lpszHostName ) /*++ Routine Description: Walks the server info list looking for the requested server Arguments: lpszHostName - pointer to server name to find (IN LOWER CASE!) Return Value: CServerInfo * Success - pointer to found list entry Failure - NULL --*/ { DEBUG_ENTER((DBG_SESSION, Pointer, "FindServerInfo", "%q", lpszHostName )); DWORD hashHostName = CalculateHashValue(lpszHostName); CServerInfo * lpServerInfo = NULL; BOOL found = FALSE; if (!LockSerializedList(&_ServerInfoList)) { goto quit; } for (lpServerInfo = (CServerInfo *)HeadOfSerializedList(&_ServerInfoList); lpServerInfo != (CServerInfo *)SlSelf(&_ServerInfoList); lpServerInfo = lpServerInfo->Next()) { if (lpServerInfo->Match(hashHostName, lpszHostName)) { found = TRUE; break; } } if (!found) { lpServerInfo = NULL; } // Need to keep this alive beyond the lock. if (lpServerInfo) { lpServerInfo->Reference(); } UnlockSerializedList(&_ServerInfoList); quit: DEBUG_LEAVE(lpServerInfo); return lpServerInfo; } VOID ReleaseServerInfo( IN CServerInfo * lpServerInfo ) /*++ Routine Description: Release a CServerInfo by dereferencing it. If the reference count goes to zero, the CServerInfo will be destroyed Arguments: lpServerInfo - pointer to CServerInfo to release Return Value: None. --*/ { DEBUG_ENTER((DBG_SESSION, None, "ReleaseServerInfo", "%#x [%q]", lpServerInfo, lpServerInfo->GetHostName() )); lpServerInfo->Dereference(); DEBUG_LEAVE(0); } VOID INTERNET_HANDLE_OBJECT::PurgeServerInfoList( IN BOOL bForce ) /*++ Routine Description: Throw out any CServerInfo entries that have expired or any KEEP_ALIVE entries (for any CServerInfo) that have expired Arguments: bForce - TRUE if we forcibly remove entries which have not yet expired but which have a reference count of 1, else FALSE to remove only entries that have expired Return Value: None. --*/ { DEBUG_ENTER((DBG_SESSION, None, "PurgeServerInfoList", "%B", bForce )); if (!LockSerializedList(&_ServerInfoList)) { // Can't purge list if unable to obtain the lock. goto quit; } PLIST_ENTRY pEntry = HeadOfSerializedList(&_ServerInfoList); PLIST_ENTRY pPrevious = (PLIST_ENTRY)SlSelf(&_ServerInfoList); while (TRUE) { if (pEntry == (PLIST_ENTRY)SlSelf(&_ServerInfoList)) { break; } CServerInfo * pServerInfo; //pServerInfo = (CServerInfo *)pEntry; //pServerInfo = CONTAINING_RECORD(pEntry, CONNECTION_LIMIT, m_List); pServerInfo = ContainingServerInfo(pEntry); BOOL deleted = FALSE; if (pServerInfo->ReferenceCount() == 1) { if (bForce || pServerInfo->Expired()) { //dprintf("purging server info entry for %q\n", pServerInfo->GetHostName()); deleted = pServerInfo->Dereference(); } else { pServerInfo->PurgeKeepAlives(PKA_NO_FORCE); } } if (!deleted) { pPrevious = pEntry; } pEntry = pPrevious->Flink; } UnlockSerializedList(&_ServerInfoList); quit: DEBUG_LEAVE(0); } VOID INTERNET_HANDLE_OBJECT::PurgeKeepAlives( IN DWORD dwForce ) /*++ Routine Description: Throw out any KEEP_ALIVE entries from any CServerInfo that have expired or which have failed authentication or which are unused, depending on dwForce Arguments: dwForce - force to apply when purging. Value can be: PKA_NO_FORCE - only purge timed-out sockets or sockets in close-wait state (default) PKA_NOW - purge all sockets PKA_AUTH_FAILED - purge sockets that have been marked as failing authentication Return Value: None. --*/ { DEBUG_ENTER((DBG_SESSION, None, "PurgeKeepAlives", "%s [%d]", (dwForce == PKA_NO_FORCE) ? "NO_FORCE" : (dwForce == PKA_NOW) ? "NOW" : (dwForce == PKA_AUTH_FAILED) ? "AUTH_FAILED" : "?", dwForce )); if (!LockSerializedList(&_ServerInfoList)) { goto quit; } PLIST_ENTRY pEntry = HeadOfSerializedList(&_ServerInfoList); while (pEntry != (PLIST_ENTRY)SlSelf(&_ServerInfoList)) { CServerInfo * lpServerInfo = ContainingServerInfo(pEntry); lpServerInfo->PurgeKeepAlives(dwForce); pEntry = pEntry->Flink; } UnlockSerializedList(&_ServerInfoList); quit: DEBUG_LEAVE(0); } // // methods // CServerInfo::CServerInfo( IN SERIALIZED_LIST * ServerInfoList, IN LPSTR lpszHostName, OUT DWORD* pdwError, IN DWORD dwService, IN DWORD dwMaxConnections ) /*++ Routine Description: CServerInfo constructor Arguments: lpszHostName - server for which to create CServerInfo dwService - which service to create CServerInfo for dwMaxConnections - maximum number of simultaneous connections to this server Return Value: None. --*/ { DEBUG_ENTER((DBG_OBJECTS, None, "CServerInfo::CServerInfo", "%q, %s (%d), %d", lpszHostName, InternetMapService(dwService), dwService, dwMaxConnections )); INIT_SERVER_INFO(); m_ServerInfoList = ServerInfoList; *pdwError = ERROR_SUCCESS; InitializeListHead(&m_List); m_Expires = 0; m_Wrap = 0; m_ReferenceCount = 1; m_HostName = lpszHostName; if (!m_HostName.StringAddress()) { goto error; } m_HostName.MakeLowerCase(); m_Hash = CalculateHashValue(m_HostName.StringAddress()); m_Services.Word = 0; m_HttpSupport.Word = 0; m_Flags.Word = 0; m_ProxyLink = NULL; INET_ASSERT(dwService == INTERNET_SERVICE_HTTP); SetHTTP(); // // only initialize the keep-alive and connection limit lists if we are // creating the server info entry for a HTTP server (or CERN proxy) // // // BUGBUG - we only want to do this on demand // //if (IsHTTP()) { InitializeSerializedList(&m_KeepAliveList); SetKeepAliveListInitialized(); // // the maximum number of connections per server is initialized to the // default (registry) value unless overridden by the caller // if (dwMaxConnections == 0) { dwMaxConnections = DEFAULT_MAX_CONNECTIONS_PER_SERVER; } m_ConnectionLimit = dwMaxConnections; //} else { // m_ConnectionLimit = UNLIMITED_CONNECTIONS; //} //dprintf("*** %s: limit = %d\n", GetHostName(), m_ConnectionLimit); // // BUGBUG - only create event if limiting connections. Need method to manage // connection limit count/event creation // m_NewLimit = m_ConnectionLimit; m_ConnectionsAvailable = m_ConnectionLimit; //m_ActiveConnections = 0; m_LastActiveTime = 0; m_ConnectTime = (DWORD)-1; m_RTT = 0; m_dwError = ERROR_SUCCESS; // // add to the global list. We are assuming here that the caller has already // checked for dupes // if (!InsertAtHeadOfSerializedList(m_ServerInfoList, &m_List)) *pdwError = ERROR_NOT_ENOUGH_MEMORY; quit: DEBUG_LEAVE(0); return; error: *pdwError = ERROR_NOT_ENOUGH_MEMORY; goto quit; } CServerInfo::~CServerInfo() /*++ Routine Description: CServerInfo destructor Arguments: None. Return Value: None. --*/ { DEBUG_ENTER((DBG_OBJECTS, None, "CServerInfo::~CServerInfo", "{%q}", GetHostName() )); CHECK_SERVER_INFO(); //GlobalServerInfoDeAllocCount++; // unlink if we have a nested obj if ( m_ProxyLink ) { CServerInfo *pDerefObj = NULL; // will leak if unable to dereference if (LockSerializedList(m_ServerInfoList)) { pDerefObj = m_ProxyLink; m_ProxyLink = NULL; UnlockSerializedList(m_ServerInfoList); } if (pDerefObj) { pDerefObj->Dereference(); } } RemoveFromSerializedList(m_ServerInfoList, &m_List); INET_ASSERT(m_ReferenceCount == 0); if (IsKeepAliveListInitialized() && LockSerializedList(&m_KeepAliveList)) { while (!IsSerializedListEmpty(&m_KeepAliveList)) { //dprintf("%#x ~S-I killing K-A %#x\n", GetCurrentThreadId(), HeadOfSerializedList(&m_KeepAliveList)); LPVOID pEntry = SlDequeueHead(&m_KeepAliveList); INET_ASSERT(pEntry != NULL); if (pEntry != NULL) { ICSocket * pSocket = ContainingICSocket(pEntry); //dprintf("~CServerInfo: destroying socket %#x\n", pSocket->GetSocket()); pSocket->Destroy(); } } UnlockSerializedList(&m_KeepAliveList); TerminateSerializedList(&m_KeepAliveList); } DEBUG_LEAVE(0); } VOID CServerInfo::Reference( VOID ) /*++ Routine Description: Increments the reference count for the CServerInfo Arguments: None. Return Value: None. --*/ { DEBUG_ENTER((DBG_SESSION, None, "CServerInfo::Reference", "{%q}", GetHostName() )); CHECK_SERVER_INFO(); INET_ASSERT(m_ReferenceCount > 0); InterlockedIncrement(&m_ReferenceCount); //dprintf("CServerInfo %s - %d\n", GetHostName(), m_ReferenceCount); DEBUG_PRINT(SESSION, INFO, ("Reference count = %d\n", ReferenceCount() )); DEBUG_LEAVE(0); } BOOL CServerInfo::Dereference( VOID ) /*++ Routine Description: Dereferences the SESSION_INFO. If the reference count goes to zero then this entry is deleted. If the reference count goes to 1 then the expiry timer is started Arguments: None. Return Value: BOOL TRUE - entry was deleted FALSE - entry was not deleted --*/ { DEBUG_ENTER((DBG_SESSION, None, "CServerInfo::Dereference", "{%q}", GetHostName() )); CHECK_SERVER_INFO(); INET_ASSERT(m_ReferenceCount > 0); // // we need to grab the list - we may be removing this entry or updating // the reference count and expiry fields which must be done atomically // SERIALIZED_LIST * ServerInfoList = m_ServerInfoList; BOOL deleted = FALSE; if (!LockSerializedList(ServerInfoList)) goto quit; LONG result = InterlockedDecrement(&m_ReferenceCount); //dprintf("CServerInfo %s - %d\n", GetHostName(), m_ReferenceCount); DEBUG_PRINT(SESSION, INFO, ("Reference count = %d\n", ReferenceCount() )); if (result == 0) { delete this; deleted = TRUE; } else if (result == 1) { // // start expiration proceedings... // SetExpiryTime(); } UnlockSerializedList(ServerInfoList); quit: DEBUG_LEAVE(deleted); return deleted; } DWORD CServerInfo::SetCachedProxyServerInfo( IN CServerInfo * pProxyServer, IN DWORD dwProxyVersion, IN BOOL fUseProxy, IN INTERNET_SCHEME HostScheme, IN INTERNET_PORT HostPort, IN INTERNET_SCHEME ProxyScheme, IN INTERNET_PORT ProxyPort ) /*++ Routine Description: If the Version information match up, copies the proxy information and links this server object to the appopriate proxy server object Assumes that this is called on successful use of the proxy object. Arguments: None. Return Value: DWORD ERROR_SUCCESS ERROR_NOT_ENOUGH_MEMORY - entry was not deleted because there wasn't available memory to obtain lock --*/ { DWORD error=ERROR_SUCCESS; if (!LockSerializedList(m_ServerInfoList)) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } if ( dwProxyVersion != GlobalProxyVersionCount ) { SetProxyScriptCached(FALSE); goto cleanup; // bail, we don't accept out of date additions to the cache } if ( m_ProxyLink ) { if ( IsProxyScriptCached() && HostScheme == m_HostScheme && HostPort == m_HostPort && fUseProxy ) { if ( pProxyServer == m_ProxyLink ) { INET_ASSERT(dwProxyVersion == GlobalProxyVersionCount); m_dwProxyVersion = dwProxyVersion; // we're now up to date goto cleanup; // match, no version or host changes } INET_ASSERT(pProxyServer != m_ProxyLink ); } // // unlink, because we have a new entry to save, // and the previous entry is bad // m_ProxyLink->Dereference(); m_ProxyLink = NULL; } // // Add new cached entry // SetProxyScriptCached(TRUE); m_HostScheme = HostScheme; m_HostPort = HostPort; m_dwProxyVersion = dwProxyVersion; // we're now up to date if ( fUseProxy ) { INET_ASSERT(this != pProxyServer); m_ProxyLink = pProxyServer; m_ProxyLink->Reference(); m_ProxyLink->m_HostScheme = ProxyScheme; m_ProxyLink->m_HostPort = ProxyPort; switch (ProxyScheme) { case INTERNET_SCHEME_HTTP: m_ProxyLink->SetCernProxy(); break; case INTERNET_SCHEME_SOCKS: m_ProxyLink->SetSocksGateway(); break; } } cleanup: UnlockSerializedList(m_ServerInfoList); quit: return error; } CServerInfo * CServerInfo::GetCachedProxyServerInfo( IN INTERNET_SCHEME HostScheme, IN INTERNET_PORT HostPort, OUT BOOL *pfCachedEntry ) /*++ Routine Description: Retrieves a cached server object, that indicates a probable proxy to use On Success, the return has an additional increment on its ref count, assumition that caller derefs Arguments: None. Return Value: CServerInfo * NULL on failure --*/ { CServerInfo *pProxyServer = NULL; if (!LockSerializedList(m_ServerInfoList)) return NULL; *pfCachedEntry = FALSE; if ( IsProxyScriptCached() ) { // // Examine Version Count // if ( GlobalProxyVersionCount == m_dwProxyVersion && HostScheme == m_HostScheme && HostPort == m_HostPort ) { *pfCachedEntry = TRUE; if ( m_ProxyLink ) { // matched cached entry m_ProxyLink->Reference(); pProxyServer = m_ProxyLink; } } else { // version is expired, remove reference SetProxyScriptCached(FALSE); if ( m_ProxyLink ) { m_ProxyLink->Dereference(); m_ProxyLink = NULL; } } } UnlockSerializedList(m_ServerInfoList); return pProxyServer; } BOOL CServerInfo::CopyCachedProxyInfoToProxyMsg( IN OUT AUTO_PROXY_ASYNC_MSG *pQueryForProxyInfo ) /*++ Routine Description: Retrieves Cached Proxy info from object Arguments: None. Return Value: BOOL TRUE - sucess --*/ { BOOL fSuccess = FALSE; // really only need to lock to proctect m_HostPort && m_HostScheme if (!LockSerializedList(m_ServerInfoList)) return FALSE; pQueryForProxyInfo->SetUseProxy(FALSE); pQueryForProxyInfo->_lpszProxyHostName = m_HostName.StringAddress() ? NewString(m_HostName.StringAddress()) : NULL; if ( pQueryForProxyInfo->_lpszProxyHostName != NULL ) { // copy out cached entry to proxy message structure pQueryForProxyInfo->_nProxyHostPort = m_HostPort; pQueryForProxyInfo->_tProxyScheme = m_HostScheme; pQueryForProxyInfo->_bFreeProxyHostName = TRUE; pQueryForProxyInfo->_dwProxyHostNameLength = strlen((pQueryForProxyInfo)->_lpszProxyHostName); pQueryForProxyInfo->SetUseProxy(TRUE); fSuccess = TRUE; // success } UnlockSerializedList(m_ServerInfoList); return fSuccess; } VOID CServerInfo::UpdateConnectTime( IN DWORD dwConnectTime ) /*++ Routine Description: Calculates average connect time Arguments: dwConnectTime - current connect time Return Value: None. --*/ { DEBUG_ENTER((DBG_SESSION, None, "CServerInfo::UpdateConnectTime", "{%q} %d", GetHostName(), dwConnectTime )); DWORD connectTime = m_ConnectTime; if (connectTime == (DWORD)-1) { connectTime = dwConnectTime; } else { connectTime = (connectTime + dwConnectTime) / 2; } //dprintf("%s: connect time = %d, ave = %d\n", GetHostName(), dwConnectTime, connectTime); DEBUG_PRINT(SESSION, INFO, ("average connect time = %d mSec\n", connectTime )); InterlockedExchange((LPLONG)&m_ConnectTime, connectTime); DEBUG_LEAVE(0); } VOID CServerInfo::UpdateRTT( IN DWORD dwRTT ) /*++ Routine Description: Calculates rolling average round-trip time Arguments: dwRTT - current round-trip time Return Value: None. --*/ { DEBUG_ENTER((DBG_SESSION, None, "CServerInfo::UpdateRTT", "{%q} %d", GetHostName(), dwRTT )); DWORD RTT = m_RTT; if (RTT == 0) { RTT = dwRTT; } else { RTT = (RTT + dwRTT) / 2; } //dprintf("%s: RTT = %d, ave = %d\n", GetHostName(), dwRTT, RTT); DEBUG_PRINT(SESSION, INFO, ("average round trip time = %d mSec\n", RTT )); InterlockedExchange((LPLONG)&m_RTT, RTT); DEBUG_LEAVE(0); } DWORD CFsm_GetConnection::RunSM( IN CFsm * Fsm ) /*++ Routine Description: Runs next CFsm_GetConnection state Arguments: Fsm - FSM controlling operation Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { //dprintf("%#x: %s FSM %#x state %s\n", GetCurrentThreadId(), Fsm->MapType(), Fsm, Fsm->MapState()); DEBUG_ENTER((DBG_SESSION, Dword, "CFsm_GetConnection::RunSM", "%#x", Fsm )); CServerInfo * pServerInfo = (CServerInfo *)Fsm->GetContext(); CFsm_GetConnection * stateMachine = (CFsm_GetConnection *)Fsm; DWORD error; switch (Fsm->GetState()) { case FSM_STATE_INIT: stateMachine->StartTimer(); // // fall through // case FSM_STATE_CONTINUE: #ifdef NEW_CONNECTION_SCHEME case FSM_STATE_ERROR: #endif error = pServerInfo->GetConnection_Fsm(stateMachine); break; #ifndef NEW_CONNECTION_SCHEME case FSM_STATE_ERROR: INET_ASSERT((Fsm->GetError() == ERROR_WINHTTP_TIMEOUT) || (Fsm->GetError() == ERROR_WINHTTP_OPERATION_CANCELLED)); pServerInfo->RemoveWaiter((DWORD_PTR)Fsm); error = Fsm->GetError(); Fsm->SetDone(); //dprintf("%#x: FSM_STATE_ERROR - %d\n", GetCurrentThreadId(), error); break; #endif default: error = ERROR_WINHTTP_INTERNAL_ERROR; Fsm->SetDone(ERROR_WINHTTP_INTERNAL_ERROR); INET_ASSERT(FALSE); break; } DEBUG_LEAVE(error); return error; } DWORD CServerInfo::GetConnection_Fsm( IN CFsm_GetConnection * Fsm ) /*++ Routine Description: Tries to get a connection of requested type for caller. If no connection is available then one of the following happens: * If there are available keep-alive connections of a different type then one is closed and the caller allowed to create a new connection * If this is an async request, the FSM is blocked and the thread returns to the pool if a worker, or back to the app if an app thread * If this is a sync request, we wait on an event for a connection to be made available, or the connect timeout to elapse Arguments: Fsm - get connection FSM Return Value: DWORD Success - ERROR_SUCCESS Depending on *lplpSocket, we either returned the socket to use, or its okay to create a new connection ERROR_IO_PENDING Request will complete asynchronously Failure - ERROR_WINHTTP_TIMEOUT Failed to get connection in time allowed ERROR_WINHTTP_INTERNAL_ERROR Something unexpected happened --*/ { DEBUG_ENTER((DBG_SESSION, Dword, "CServerInfo::GetConnection_Fsm", "{%q [%d+%d/%d]} %#x(%#x, %d, %d)", GetHostName(), m_ConnectionsAvailable, ElementsOnSerializedList(&m_KeepAliveList), m_ConnectionLimit, Fsm, Fsm->m_dwSocketFlags, Fsm->m_nPort, Fsm->m_dwTimeout )); PERF_ENTER(GetConnection); BOOL bFound = FALSE; DWORD error = ERROR_SUCCESS; CFsm_GetConnection & fsm = *Fsm; ICSocket * pSocket = NULL; LPINTERNET_THREAD_INFO lpThreadInfo = fsm.GetThreadInfo(); HANDLE hEvent = NULL; BOOL bUnlockList = TRUE; BOOL bKeepAliveWaiters; INET_ASSERT(lpThreadInfo != NULL); INET_ASSERT(lpThreadInfo->hObjectMapped != NULL); INET_ASSERT(((HANDLE_OBJECT *)lpThreadInfo->hObjectMapped)-> GetHandleType() == TypeHttpRequestHandle); if ((lpThreadInfo == NULL) || (lpThreadInfo->hObjectMapped == NULL)) { error = ERROR_WINHTTP_INTERNAL_ERROR; goto quit; } BOOL bAsyncRequest; bAsyncRequest = lpThreadInfo->IsAsyncWorkerThread || ((INTERNET_HANDLE_OBJECT *)lpThreadInfo->hObjectMapped)-> IsAsyncHandle(); *fsm.m_lplpSocket = NULL; try_again: bUnlockList = TRUE; // // use m_Waiters to serialize access. N.B. - we will acquire m_KeepAliveList // from within m_Waiters // if (!m_Waiters.Acquire()) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } if (IsNewLimit()) { UpdateConnectionLimit(); } bKeepAliveWaiters = KeepAliveWaiters(); if (fsm.m_dwSocketFlags & SF_KEEP_ALIVE) { // // maintain requester order - if there are already waiters then queue // this request, else try to satisfy the requester. HOWEVER, only check // for existing requesters the FIRST time through. If we're here with // FSM_STATE_CONTINUE then we've been unblocked and we can ignore any // waiters that came after us // if ((fsm.GetState() == FSM_STATE_CONTINUE) || !bKeepAliveWaiters) { DEBUG_PRINT(SESSION, INFO, ("no current waiters for K-A connections\n" )); while (pSocket = FindKeepAliveConnection(fsm.m_dwSocketFlags, fsm.m_nPort, fsm.m_lpszSecureTunnelHost)) { if (pSocket->IsReset() || pSocket->HasExpired()) { DPRINTF("%#x: %#x: ********* socket %#x is closed already\n", GetCurrentThreadId(), Fsm, pSocket->GetSocket() ); DEBUG_PRINT(SESSION, INFO, ("K-A connection %#x [%#x/%d] is reset (%B) or expired (%B)\n", pSocket, pSocket->GetSocket(), pSocket->GetSourcePort(), pSocket->IsReset(), pSocket->HasExpired() )); pSocket->SetLinger(FALSE, 0); pSocket->Shutdown(2); //dprintf("GetConnection: destroying reset socket %#x\n", pSocket->GetSocket()); pSocket->Destroy(); pSocket = NULL; if (!UnlimitedConnections()) { ++m_ConnectionsAvailable; } CHECK_CONNECTION_COUNT(); } else { DPRINTF("%#x: %#x: *** matched %#x, %#x\n", GetCurrentThreadId(), Fsm, pSocket->GetSocket(), pSocket->GetFlags() ); break; } } if (pSocket == NULL) { DEBUG_PRINT(SESSION, INFO, ("no available K-A connections\n" )); /* // // if all connections are in use as keep-alive connections then // since we're here, we want a keep-alive connection that doesn't // match the currently available keep-alive connections. Terminate // the oldest keep-alive connection (at the head of the queue) // and generate a new connection // LockSerializedList(&m_KeepAliveList); if (ElementsOnSerializedList(&m_KeepAliveList) == m_ConnectionLimit) { pSocket = ContainingICSocket(SlDequeueHead(&m_KeepAliveList)); pSocket->SetLinger(FALSE, 0); pSocket->Shutdown(2); pSocket->Destroy(); if (!UnlimitedConnections()) { ++m_ConnectionsAvailable; } CHECK_CONNECTION_COUNT(); } UnlockSerializedList(&m_KeepAliveList); */ } } else { DEBUG_PRINT(SESSION, INFO, ("%d waiters for K-A connection to %q\n", ElementsOnSerializedList(&m_KeepAliveList), GetHostName() )); } } // // if we found a matching keep-alive connection or we are not limiting // connections then we're done // if ((pSocket != NULL) || UnlimitedConnections()) { INET_ASSERT(error == ERROR_SUCCESS); goto exit; } // // no keep-alive connections matched, or there are already waiters for // keep-alive connections // INET_ASSERT(m_ConnectionsAvailable <= m_ConnectionLimit); if (m_ConnectionsAvailable > 0) { if (fsm.m_lpszSecureTunnelHost) goto exit; // don't create a connection here for SSL tunneling // // can create a connection // DEBUG_PRINT(SESSION, INFO, ("OK to create new connection\n" )); DPRINTF("%#x: %#x: *** %s OK to create connection %d/%d\n", GetCurrentThreadId(), Fsm, GetHostName(), m_ConnectionsAvailable, m_ConnectionLimit ); --m_ConnectionsAvailable; } else if (fsm.GetElapsedTime() > fsm.m_dwTimeout) { error = ERROR_WINHTTP_TIMEOUT; } else { // // if there are keep-alive connections but no keep-alive waiters // then either we don't want a keep-alive connection, or the ones // available don't match our requirements. // If we need a connection of a different type - e.g. SSL when all // we have is non-SSL then close a connection & generate a new one. // If we need a non-keep-alive connection then its okay to return // a current keep-alive connection, the understanding being that the // caller will not add Connection: Keep-Alive header (HTTP 1.0) or // will add Connection: Close header (HTTP 1.1) // // // BUGBUG - what about waiters for non-keep-alive connections? // // scenario - limit of 1 connection: // // A. request for k-a // continue & create connection // B. request non-k-a // none available; wait // C. release k-a connection; unblock sync waiter B // D. request non-k-a // k-a available; return it; caller converts to non-k-a // E. unblocked waiter B request non-k-a // none available; wait // // If this situation continues, eventually B will time-out, whereas it // could have had the connection taken by D. Request D is younger and // therefore can afford to wait while B continues with the connection // BOOL fHaveConnection = FALSE; if (!bKeepAliveWaiters || (fsm.GetState() == FSM_STATE_CONTINUE)) { if (!LockSerializedList(&m_KeepAliveList)) { error = ERROR_NOT_ENOUGH_MEMORY; goto exit; } if (ElementsOnSerializedList(&m_KeepAliveList) != 0) { pSocket = ContainingICSocket(SlDequeueHead(&m_KeepAliveList)); fHaveConnection = TRUE; #define SOCK_FLAGS (SF_ENCRYPT | SF_DECRYPT | SF_SECURE | SF_TUNNEL) DWORD dwSocketTypeFlags = pSocket->GetFlags() & SOCK_FLAGS; DWORD dwRequestTypeFlags = fsm.m_dwSocketFlags & SOCK_FLAGS; if ((dwSocketTypeFlags ^ dwRequestTypeFlags) || (fsm.m_nPort != pSocket->GetPort())) { DEBUG_PRINT(SESSION, INFO, ("different socket types (%#x, %#x) or ports (%d, %d) requested\n", fsm.m_dwSocketFlags, pSocket->GetFlags(), fsm.m_nPort, pSocket->GetPort() )); DPRINTF("%#x: %#x: *** closing socket %#x: %#x vs. %#x\n", GetCurrentThreadId(), Fsm, pSocket->GetSocket(), pSocket->GetFlags(), fsm.m_dwSocketFlags ); pSocket->SetLinger(FALSE, 0); pSocket->Shutdown(2); //dprintf("GetConnection: destroying different type socket %#x\n", pSocket->GetSocket()); pSocket->Destroy(); pSocket = NULL; // If we were trying to wait for established SSL tunnel, // but one wasn't found, then this connection is open // for anyone. if (!UnlimitedConnections() && fsm.m_lpszSecureTunnelHost) { ++m_ConnectionsAvailable; } } else { DPRINTF("%#x: %#x: *** returning k-a connection %#x as non-k-a\n", GetCurrentThreadId(), Fsm, pSocket->GetSocket() ); } CHECK_CONNECTION_COUNT(); } UnlockSerializedList(&m_KeepAliveList); if (fHaveConnection) { goto exit; } } DPRINTF("%#x: %#x: blocking %s FSM %#x state %s %d/%d\n", GetCurrentThreadId(), Fsm, Fsm->MapType(), Fsm, Fsm->MapState(), m_ConnectionsAvailable, m_ConnectionLimit ); // // we have to wait for a connection to become available. If we are an // async request then we queue this FSM & return the thread to the pool // or, if app thread, return pending indication to the app. If this is // a sync request (in an app thread) then we block on an event waiting // for a connection to become available // if (!bAsyncRequest) { // // create unnamed, initially unsignalled, auto-reset event // hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (hEvent == NULL) { error = GetLastError(); goto exit; } } CConnectionWaiter * pWaiter; DWORD dwStatus = ERROR_SUCCESS; #if INET_DEBUG for (pWaiter = (CConnectionWaiter *)m_Waiters.Head(); pWaiter != (CConnectionWaiter *)m_Waiters.Self(); pWaiter = (CConnectionWaiter *)pWaiter->Next()) { INET_ASSERT(pWaiter->Id() != (DWORD_PTR)(bAsyncRequest ? (DWORD_PTR)Fsm : lpThreadInfo->ThreadId)); } #endif pWaiter = New CConnectionWaiter(&m_Waiters, !bAsyncRequest, (fsm.m_dwSocketFlags & SF_KEEP_ALIVE) ? TRUE : FALSE, bAsyncRequest ? (DWORD_PTR)Fsm : lpThreadInfo->ThreadId, hEvent, // // priority in request handle object // controls relative position in list // of waiters // ((HTTP_REQUEST_HANDLE_OBJECT *) lpThreadInfo->hObjectMapped)-> GetPriority(), &dwStatus ); DPRINTF("%#x: %#x: new waiter %#x: as=%B, K-A=%B, id=%#x, hE=%#x, pri=%d, status=%#x, sf=%#x, preq=%#x ssl=%s\n", GetCurrentThreadId(), Fsm, pWaiter, bAsyncRequest, (fsm.m_dwSocketFlags & SF_KEEP_ALIVE) ? TRUE : FALSE, bAsyncRequest ? (DWORD_PTR)Fsm : lpThreadInfo->ThreadId, hEvent, ((HTTP_REQUEST_HANDLE_OBJECT *)lpThreadInfo->hObjectMapped)-> GetPriority(), dwStatus, fsm.m_dwSocketFlags, ((HTTP_REQUEST_HANDLE_OBJECT *)lpThreadInfo->hObjectMapped), fsm.m_lpszSecureTunnelHost ? fsm.m_lpszSecureTunnelHost : "" ); if (pWaiter == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; goto exit; } else if (dwStatus != ERROR_SUCCESS) { error = dwStatus; delete pWaiter; // free since it wasn't inserted goto exit; } if (bAsyncRequest) { // // ensure that when the FSM is unblocked normally, the new state // is STATE_CONTINUE // Fsm->SetState(FSM_STATE_CONTINUE); error = BlockWorkItem(Fsm, (DWORD_PTR)pWaiter, fsm.m_dwTimeout ); if (error == ERROR_SUCCESS) { error = ERROR_IO_PENDING; } } else { m_Waiters.Release(); bUnlockList = FALSE; DPRINTF("%#x: %#x: %s FSM %#x %s waiting %d msec\n", GetCurrentThreadId(), Fsm, Fsm->MapType(), Fsm, Fsm->MapState(), fsm.m_dwTimeout ); DWORD dwWaitTime = (fsm.m_dwTimeout != INFINITE) ? (fsm.m_dwTimeout - fsm.GetElapsedTime()) : INFINITE; if (((int)dwWaitTime <= 0) && (dwWaitTime != INFINITE)) { DEBUG_PRINT(SESSION, ERROR, ("SYNC wait timed out (%d mSec)\n", fsm.m_dwTimeout )); error = ERROR_WINHTTP_TIMEOUT; } else { DEBUG_PRINT(SESSION, INFO, ("waiting %d mSec for SYNC event %#x\n", dwWaitTime, hEvent )); // // we'd better not be doing a sync wait if we are in the // context of an app thread making an async request // INET_ASSERT(lpThreadInfo->IsAsyncWorkerThread || !((INTERNET_HANDLE_OBJECT *)lpThreadInfo-> hObjectMapped)->IsAsyncHandle()); //INET_ASSERT(dwWaitTime <= 60000); error = WaitForSingleObject(hEvent, dwWaitTime); DPRINTF("%#x: %#x: sync waiter unblocked - error = %d\n", GetCurrentThreadId(), Fsm, error ); } if (error == STATUS_TIMEOUT) { DPRINTF("%#x: %#x: %s: %d+%d/%d: timed out %#x (%s FSM %#x %s)\n", GetCurrentThreadId(), Fsm, GetHostName(), m_ConnectionsAvailable, ElementsOnSerializedList(&m_KeepAliveList), m_ConnectionLimit, GetCurrentThreadId(), Fsm->MapType(), Fsm, Fsm->MapState() ); RemoveWaiter(lpThreadInfo->ThreadId); error = ERROR_WINHTTP_TIMEOUT; } BOOL bOk; bOk = CloseHandle(hEvent); INET_ASSERT(bOk); if (error == WAIT_OBJECT_0) { DPRINTF("%#x: %#x: sync requester trying again\n", GetCurrentThreadId(), Fsm ); fsm.SetState(FSM_STATE_CONTINUE); goto try_again; } } } exit: // // if we are returning a (keep-alive) socket that has a different blocking // mode from that requested, change it // if (pSocket != NULL) { if ((pSocket->GetFlags() & SF_NON_BLOCKING) ^ (fsm.m_dwSocketFlags & SF_NON_BLOCKING)) { DEBUG_PRINT(SESSION, INFO, ("different blocking modes requested: %#x, %#x\n", fsm.m_dwSocketFlags, pSocket->GetFlags() )); DPRINTF("%#x: %#x: *** changing socket %#x to %sBLOCKING\n", GetCurrentThreadId(), Fsm, pSocket->GetSocket(), fsm.m_dwSocketFlags & SF_NON_BLOCKING ? "NON-" : "" ); if (!(GlobalRunningNovellClient32 && !GlobalNonBlockingClient32)) { pSocket->SetNonBlockingMode(fsm.m_dwSocketFlags & SF_NON_BLOCKING); } } *fsm.m_lplpSocket = pSocket; } if (bUnlockList) { m_Waiters.Release(); } quit: if (error != ERROR_IO_PENDING) { fsm.SetDone(); } DPRINTF("%#x: %#x: %s: %d+%d/%d: get: %d, %#x, %d\n", GetCurrentThreadId(), Fsm, GetHostName(), m_ConnectionsAvailable, ElementsOnSerializedList(&m_KeepAliveList), m_ConnectionLimit, error, pSocket ? pSocket->GetSocket() : 0, m_Waiters.Count() ); PERF_LEAVE(GetConnection); DEBUG_LEAVE(error); return error; } DWORD CServerInfo::ReleaseConnection( IN ICSocket * lpSocket OPTIONAL ) /*++ Routine Description: Returns a keep-alive connection to the pool, or allows another requester to create a connection Arguments: lpSocket - pointer to ICSocket if we are returning a keep-alive connection Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_SESSION, Dword, "CServerInfo::ReleaseConnection", "{%q [%d+%d/%d]} %#x [%#x]", GetHostName(), AvailableConnections(), KeepAliveConnections(), ConnectionLimit(), lpSocket, lpSocket ? lpSocket->GetSocket() : 0 )); PERF_ENTER(ReleaseConnection); DWORD error = ERROR_SUCCESS; BOOL bRelease = FALSE; if (!m_Waiters.Acquire()) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } // // quite often (at least with catapult proxy based on IIS) the server may // drop the connection even though it indicated it would keep it open. This // typically happens on 304 (frequent) and 302 (less so) responses. If we // determine the server has dropped the connection then throw it away and // allow the app to create a new one // if (lpSocket != NULL) { if (lpSocket->IsClosed() || lpSocket->IsReset()) { DEBUG_PRINT(SESSION, INFO, ("socket %#x already dead - throwing it out\n", lpSocket->GetSocket() )); DPRINTF("%#x: socket %#x: already reset\n", GetCurrentThreadId(), lpSocket->GetSocket() ); //dprintf("ReleaseConnection: destroying already closed socket %#x\n", lpSocket->GetSocket()); BOOL bDestroyed = lpSocket->Dereference(); INET_ASSERT(bDestroyed); lpSocket = NULL; } else { // // if we are returning a keep-alive socket, put it in non-blocking // mode if not already. Typically, Internet Explorer uses non-blocking // sockets. In the infrequent cases where we want a blocking socket // - mainly when doing java downloads - we will convert the socket // to blocking mode when we get it from the pool // if (!lpSocket->IsNonBlocking()) { DPRINTF("%#x: ***** WARNING: releasing BLOCKING k-a socket %#x\n", GetCurrentThreadId(), lpSocket->GetSocket() ); if (!(GlobalRunningNovellClient32 && !GlobalNonBlockingClient32)) { lpSocket->SetNonBlockingMode(TRUE); } } } } if (lpSocket != NULL) { DPRINTF("%#x: releasing K-A %#x (%d+%d/%d)\n", GetCurrentThreadId(), lpSocket ? lpSocket->GetSocket() : 0, AvailableConnections(), KeepAliveConnections(), ConnectionLimit() ); INET_ASSERT(lpSocket->IsOpen()); INET_ASSERT(!lpSocket->IsOnList()); //INET_ASSERT(!lpSocket->IsReset()); lpSocket->SetKeepAlive(); DEBUG_PRINT(SESSION, INFO, ("releasing keep-alive socket %#x\n", lpSocket->GetSocket() )); lpSocket->SetExpiryTime(GlobalKeepAliveSocketTimeout); INET_ASSERT(!IsOnSerializedList(&m_KeepAliveList, lpSocket->List())); if (!InsertAtTailOfSerializedList(&m_KeepAliveList, lpSocket->List())) { DEBUG_PRINT(SESSION, INFO, ("not enough memory to release %#x to k-a pool\n", lpSocket->GetSocket() )); lpSocket->Dereference(); if (!UnlimitedConnections()) { ++m_ConnectionsAvailable; } } lpSocket = NULL; INET_ASSERT(UnlimitedConnections() ? TRUE : (KeepAliveConnections() <= ConnectionLimit())); bRelease = TRUE; } else { DPRINTF("%#x: releasing connection (%d+%d/%d)\n", GetCurrentThreadId(), AvailableConnections(), KeepAliveConnections(), ConnectionLimit() ); if (!UnlimitedConnections()) { ++m_ConnectionsAvailable; } CHECK_CONNECTION_COUNT(); bRelease = TRUE; } if (bRelease && !UnlimitedConnections()) { CHECK_CONNECTION_COUNT(); CConnectionWaiter * pWaiter = (CConnectionWaiter *)m_Waiters.RemoveHead(); if (pWaiter != NULL) { DEBUG_PRINT(SESSION, INFO, ("unblocking %s waiter %#x, pri=%d\n", pWaiter->IsSync() ? "SYNC" : "ASYNC", pWaiter->Id(), pWaiter->GetPriority() )); DPRINTF("%#x: Unblocking %s connection waiter %#x, pri=%d\n", GetCurrentThreadId(), pWaiter->IsSync() ? "Sync" : "Async", pWaiter->Id(), pWaiter->GetPriority() ); if (pWaiter->IsSync()) { pWaiter->Signal(); } else { DWORD n = UnblockWorkItems(1, (DWORD_PTR)pWaiter, ERROR_SUCCESS); //INET_ASSERT(n == 1); } delete pWaiter; } else { DEBUG_PRINT(SESSION, INFO, ("no waiters\n" )); DPRINTF("%#x: !!! NOT unblocking connection waiter\n", GetCurrentThreadId() ); } } else { DPRINTF("%#x: !!! NOT releasing or unlimited?\n", GetCurrentThreadId() ); DEBUG_PRINT(SESSION, INFO, ("bRelease = %B, UnlimitedConnections() = %B\n", bRelease, UnlimitedConnections() )); } DEBUG_PRINT(SESSION, INFO, ("avail+k-a/limit = %d+%d/%d\n", AvailableConnections(), KeepAliveConnections(), ConnectionLimit() )); if (IsNewLimit()) { UpdateConnectionLimit(); } m_Waiters.Release(); quit: PERF_LEAVE(ReleaseConnection); DEBUG_LEAVE(error); DPRINTF("%#x: %s: %d+%d/%d: rls %#x: %d, %d\n", GetCurrentThreadId(), GetHostName(), AvailableConnections(), KeepAliveConnections(), ConnectionLimit(), lpSocket ? lpSocket->GetSocket() : 0, error, m_Waiters.Count() ); return error; } VOID CServerInfo::RemoveWaiter( IN DWORD_PTR dwId ) /*++ Routine Description: Removes a CConnectionWaiter corresponding to the FSM Arguments: dwId - waiter id to match Return Value: None. --*/ { DEBUG_ENTER((DBG_SESSION, None, "CServerInfo::RemoveWaiter", "%#x", dwId )); if (!m_Waiters.Acquire()) goto quit; CConnectionWaiter * pWaiter; BOOL found = FALSE; for (pWaiter = (CConnectionWaiter *)m_Waiters.Head(); pWaiter != (CConnectionWaiter *)m_Waiters.Self(); pWaiter = (CConnectionWaiter *)pWaiter->Next()) { if (pWaiter->Id() == dwId) { m_Waiters.Remove((CPriorityListEntry *)pWaiter); delete pWaiter; found = TRUE; break; } } m_Waiters.Release(); quit: //INET_ASSERT(found); DEBUG_LEAVE(0); } // // private CServerInfo methods // ICSocket * CServerInfo::FindKeepAliveConnection( IN DWORD dwSocketFlags, IN INTERNET_PORT nPort, IN LPSTR pszTunnelServer ) /*++ Routine Description: Find a keep-alive connection with the requested attributes and port number Arguments: dwSocketFlags - socket type flags (e.g. SF_SECURE) nPort - port to server pszTunnelServer - hostname of server through SSL tunnel, or NULL if not checked. Return Value: ICSocket * --*/ { DPRINTF("%#x: *** looking for K-A connection\n", GetCurrentThreadId()); DEBUG_ENTER((DBG_SESSION, Pointer, "CServerInfo::FindKeepAliveConnection", "{%q} %#x, %d", GetHostName(), dwSocketFlags, nPort )); ICSocket * pSocket = NULL; BOOL bFound = FALSE; // // don't check whether socket is non-blocking - we only really want to match // on secure/non-secure. Possible flags to check on are: // // SF_ENCRYPT - should be subsumed by SF_SECURE // SF_DECRYPT - should be subsumed by SF_SECURE // SF_NON_BLOCKING - this isn't criterion for match // SF_CONNECTIONLESS - not implemented? // SF_AUTHORIZED - must be set if authorized & in pool // SF_SECURE - opened for SSL/PCT if set // SF_KEEP_ALIVE - must be set // SF_TUNNEL - must be set if we're looking for a CONNECT tunnel to proxy // dwSocketFlags &= ~SF_NON_BLOCKING; if (!LockSerializedList(&m_KeepAliveList)) goto quit; PLIST_ENTRY pEntry; for (pEntry = HeadOfSerializedList(&m_KeepAliveList); pEntry != (PLIST_ENTRY)SlSelf(&m_KeepAliveList); pEntry = pEntry->Flink) { pSocket = ContainingICSocket(pEntry); INET_ASSERT(pSocket->IsKeepAlive()); // // We make sure the socket we request is the correct socket, // Match() is a bit confusing and needs a bit of explaining, // Match IS NOT AN EXACT MATCH, it mearly checks to make sure // that the requesting flags (dwSocketFlags) are found in the // socket flags. So this can lead to a secure socket being returned // on a non-secure open request, now realistically this doesn't happen // because of the port number. But in the case of tunnelling this may be // an issue, so we add an additional check to make sure that we only // get a tunneled socket to a proxy if we specifically request one. // if (pSocket->Match(dwSocketFlags) && (pSocket->GetPort() == nPort) && pSocket->MatchTunnelSemantics(dwSocketFlags, pszTunnelServer) && RemoveFromSerializedList(&m_KeepAliveList, pSocket->List())) { INET_ASSERT(!IsOnSerializedList(&m_KeepAliveList, pSocket->List())); bFound = TRUE; DEBUG_PRINT(SESSION, INFO, ("returning keep-alive socket %#x\n", pSocket->GetSocket() )); DPRINTF("%#x: *** %s keep-alive connection %#x (%d/%d), wantf=%#x, gotf=%#x\n", GetCurrentThreadId(), GetHostName(), pSocket->GetSocket(), AvailableConnections(), ConnectionLimit(), dwSocketFlags, pSocket->GetFlags() ); break; } } UnlockSerializedList(&m_KeepAliveList); if (!bFound) { pSocket = NULL; } quit: DEBUG_LEAVE(pSocket); return pSocket; } BOOL CServerInfo::KeepAliveWaiters( VOID ) /*++ Routine Description: Determine if any of the waiters on the list are for keep-alive connections Arguments: None. Return Value: BOOL --*/ { DEBUG_ENTER((DBG_SESSION, Bool, "CServerInfo::KeepAliveWaiters", NULL )); BOOL found = FALSE; CConnectionWaiter * pWaiter; if (!m_Waiters.Acquire()) goto quit; for (pWaiter = (CConnectionWaiter *)m_Waiters.Head(); pWaiter != (CConnectionWaiter *)m_Waiters.Self(); pWaiter = (CConnectionWaiter *)pWaiter->Next()) { if (pWaiter->IsKeepAlive()) { found = TRUE; break; } } m_Waiters.Release(); quit: DEBUG_LEAVE(found); return found; } VOID CServerInfo::UpdateConnectionLimit( VOID ) /*++ Routine Description: Change connection limit to new limit Assumes: 1. Caller has acquired this object before calling this function Arguments: None. Return Value: None. --*/ { DEBUG_ENTER((DBG_SESSION, None, "CServerInfo::UpdateConnectionLimit", "{%q: %d=>%d (%d+%d)}", GetHostName(), ConnectionLimit(), GetNewLimit(), AvailableConnections(), KeepAliveConnections() )); LONG difference = GetNewLimit() - ConnectionLimit(); // // BUGBUG - only handling increases in limit for now // INET_ASSERT(difference > 0); if (difference > 0) { m_ConnectionsAvailable += difference; } m_ConnectionLimit = m_NewLimit; DEBUG_PRINT(SESSION, INFO, ("%q: new: %d+%d/%d\n", GetHostName(), AvailableConnections(), KeepAliveConnections(), ConnectionLimit() )); DEBUG_LEAVE(0); } VOID CServerInfo::PurgeKeepAlives( IN DWORD dwForce ) /*++ Routine Description: Purges any timed-out keep-alive connections Arguments: dwForce - force to apply when purging. Value can be: PKA_NO_FORCE - only purge timed-out sockets or sockets in close-wait state (default) PKA_NOW - purge all sockets PKA_AUTH_FAILED - purge sockets that have been marked as failing authentication Return Value: None. --*/ { //dprintf("%#x PurgeKeepAlives(%d)\n", GetCurrentThreadId(), dwForce); DEBUG_ENTER((DBG_SESSION, None, "CServerInfo::PurgeKeepAlives", "{%q [ref=%d, k-a=%d]} %s [%d]", GetHostName(), ReferenceCount(), KeepAliveConnections(), (dwForce == PKA_NO_FORCE) ? "NO_FORCE" : (dwForce == PKA_NOW) ? "NOW" : (dwForce == PKA_AUTH_FAILED) ? "AUTH_FAILED" : "?", dwForce )); if (IsKeepAliveListInitialized()) { INET_ASSERT(ReferenceCount() >= 1); if (!m_Waiters.Acquire()) goto quit; if (!LockSerializedList(&m_KeepAliveList)) goto Cleanup; PLIST_ENTRY last = (PLIST_ENTRY)SlSelf(&m_KeepAliveList); DWORD ticks = GetTickCountWrap(); for (PLIST_ENTRY pEntry = HeadOfSerializedList(&m_KeepAliveList); pEntry != (PLIST_ENTRY)SlSelf(&m_KeepAliveList); pEntry = last->Flink) { ICSocket * pSocket = ContainingICSocket(pEntry); BOOL bDelete; if (pSocket->IsReset()) { //dprintf("%q: socket %#x/%d CLOSE-WAIT\n", GetHostName(), pSocket->GetSocket(), pSocket->GetSourcePort()); bDelete = TRUE; } else if (dwForce == PKA_NO_FORCE) { bDelete = pSocket->HasExpired(ticks); } else if (dwForce == PKA_NOW) { bDelete = TRUE; } else if (dwForce == PKA_AUTH_FAILED) { bDelete = pSocket->IsAuthorized(); } else { INET_ASSERT(FALSE); // invalid value for dwForce! bDelete = TRUE; } if (bDelete) { //dprintf("%q: socket %#x/%d. Close-Wait=%B, Expired=%B, Now=%B, Auth=%B\n", // GetHostName(), // pSocket->GetSocket(), // pSocket->GetSourcePort(), // pSocket->IsReset(), // pSocket->HasExpired(ticks), // (dwForce == PKA_NOW), // (dwForce == PKA_AUTH_FAILED) && pSocket->IsAuthorized() // ); DEBUG_PRINT(SESSION, INFO, ("purging keep-alive socket %#x/%d: Close-Wait=%B, Expired=%B, Now=%B, Auth=%B\n", pSocket->GetSocket(), pSocket->GetSourcePort(), pSocket->IsReset(), pSocket->HasExpired(ticks), (dwForce == PKA_NOW), (dwForce == PKA_AUTH_FAILED) && pSocket->IsAuthorized() )); if (RemoveFromSerializedList(&m_KeepAliveList, pEntry)) { BOOL bDestroyed; bDestroyed = pSocket->Dereference(); INET_ASSERT(bDestroyed); if (!UnlimitedConnections()) { ++m_ConnectionsAvailable; INET_ASSERT(m_ConnectionsAvailable <= m_ConnectionLimit); } } else { DEBUG_PRINT(SESSION, INFO, ("k-a socket %#x couldn't be removed from the list\n", pSocket->GetSocket() )); } } else { DEBUG_PRINT(SESSION, INFO, ("socket %#x/%d expires in %d mSec\n", pSocket->GetSocket(), pSocket->GetSourcePort(), pSocket->GetExpiryTime() - ticks )); last = pEntry; } } UnlockSerializedList(&m_KeepAliveList); Cleanup: m_Waiters.Release(); } quit: DEBUG_LEAVE(0); } // // friend functions // CServerInfo * ContainingServerInfo( IN LPVOID lpAddress ) /*++ Routine Description: Returns address of CServerInfo given address of m_List Arguments: lpAddress - address of m_List Return Value: CServerInfo * --*/ { return CONTAINING_RECORD(lpAddress, CServerInfo, m_List); }