Microsoft KB Archive/154649

From BetaArchive Wiki
Knowledge Base


FIX: CSocket Operation Hangs if a Timer is Active

Article ID: 154649

Article Last Modified on 11/21/2006



APPLIES TO

  • Microsoft Foundation Class Library 4.2, when used with:
    • Microsoft Visual C++ 4.2 Enterprise Edition
    • Microsoft Visual C++ 4.2 Professional Edition



This article was previously published under Q154649


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:

  1. Disable all timers while making CSocket calls.
  2. 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:

156934 PATCH: Visual C++ 4.2b Patch

160491 Information on Files Modified by VC42b Patch: Part 1 of 4

160496 Information on Files Modified by VC42b Patch: Part 2 of 4

160505 Information on Files Modified by VC42b Patch: Part 3 of 4

160506 Information on Files Modified by VC42b Patch: Part 4 of 4



Additional query words: kbVC420bug kbDSupport CAsyncSocket CSocket hangs timer

Keywords: kbbug kbfix kbnetwork kbvc420fix kbwinsock KB154649