The information in this article applies to:
- The Microsoft Foundation Classes (MFC) included with:
Microsoft Visual C++, 32-bit Edition, version 4.2
SYMPTOMS
A call to CSocket Connect, Accept, Send, or Receive hangs if a timer
is active.
CAUSE
CSocket operations are written using "pseudo-blocking." The WinSock API
call is not a true blocking call, but if it doesn't return immediately then
CSocket will call PumpMessages. PumpMessages looks for the FD_xxx
notification and the CSocket function will not return until the FD_xxx
notification arrives.
PumpMessages will not detect the socket notification if a timer is active
because it looks for WM_TIMER messages first. When a WM_TIMER message
arrives, the function doesn't look any further for other messages.
The PumpMessages function is looking for WM_TIMER messages because it sets
up its own timer to cause the application to retry its socket calls and do
periodic idle-time processing while waiting for the socket notification.
RESOLUTION
There are two possible resolutions:
- Disable all timers while making CSocket calls.
- Override the PumpMessages function so that it only looks for its
own WM_TIMER messages and does not pick up other timer messages.
This can be done by borrowing the CSocket::PumpMessages function
from SOCKCORE.CPP and changing it as follows:
#if _MFC_VER == 0x0420
#define _AFX_SOCK_THREAD_STATE AFX_MODULE_THREAD_STATE
#define WM_SOCKET_NOTIFY 0x0373
#define WM_SOCKET_DEAD 0x0374
#define _afxSockThreadState AfxGetModuleThreadState()
#endif
BOOL CMySocket::PumpMessages(UINT uStopFlag)
{
#if _MFC_VER != 0x0420
return CSocket::PumpMessages(uStopFlag);
#else
// The same socket better not be blocking in more than one place.
ASSERT(m_pbBlocking == NULL);
_AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
ASSERT(pState->m_hSocketWindow != NULL);
BOOL bBlocking = TRUE;
m_pbBlocking = &bBlocking;
CWinThread* pThread = AfxGetThread();
// This is not a timeout in the WinSock sense, but more
// like a WM_KICKIDLE to keep message pumping alive
UINT nTimerID = ::SetTimer(pState->m_hSocketWindow, 0,
m_nTimeOut, NULL);
if (nTimerID == 0)
AfxThrowResourceException();
BOOL bPeek = TRUE;
while (bBlocking)
{
TRY
{
MSG msg;
if (::PeekMessage(&msg, pState->m_hSocketWindow,
WM_SOCKET_NOTIFY, WM_SOCKET_DEAD, PM_REMOVE))
{
if (msg.message == WM_SOCKET_NOTIFY &&
(SOCKET)msg.wParam == m_hSocket)
{
if (WSAGETSELECTEVENT(msg.lParam) == FD_CLOSE)
{
break;
}
if (WSAGETSELECTEVENT(msg.lParam) == uStopFlag)
{
if (uStopFlag == FD_CONNECT)
m_nConnectError = WSAGETSELECTERROR(msg.lParam);
break;
}
}
if (msg.wParam != 0 || msg.lParam != 0)
CSocket::AuxQueueAdd(msg.message, msg.wParam, msg.lParam);
bPeek = TRUE;
}
else if (::PeekMessage(&msg, pState->m_hSocketWindow,
WM_TIMER, WM_TIMER, PM_REMOVE))
{
break;
}
if (bPeek && ::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
{
if (OnMessagePending())
{
// allow user-interface updates
pThread->OnIdle(-1);
}
else
{
bPeek = FALSE;
}
}
else
{
// no work to do -- allow CPU to sleep
WaitMessage();
bPeek = TRUE;
}
}
CATCH_ALL(e)
{
TRACE0("Error: caught exception in PumpMessage - continuing.\n");
bPeek = TRUE;
}
END_CATCH_ALL
}
::KillTimer(pState->m_hSocketWindow, nTimerID);
if (!bBlocking)
{
WSASetLastError(WSAEINTR);
return FALSE;
}
m_pbBlocking = NULL;
::PostMessage(pState->m_hSocketWindow,WM_SOCKET_NOTIFY,0,0);
return TRUE;
#endif // _MFC_VER
}
STATUS
Microsoft has confirmed this to be a bug in the Microsoft products listed
at the beginning of this article. This bug has been fixed with the Visual
C++ 4.2b patch. For more information on this patch, please see the
following articles in the Microsoft Knowledge Base:
ARTICLE-ID: Q156934
TITLE : PATCH: Visual C++ 4.2b Patch
ARTICLE-ID: Q160491
TITLE : Information on Files Modified by VC42b Patch: Part 1 of 4
ARTICLE-ID: Q160496
TITLE : Information on Files Modified by VC42b Patch: Part 2 of 4
ARTICLE-ID: Q160505
TITLE : Information on Files Modified by VC42b Patch: Part 3 of 4
ARTICLE-ID: Q160506
TITLE : Information on Files Modified by VC42b Patch: Part 4 of 4
|