GetQueuedCompletionStatus não pode dequeue IO do IOCP se o thread que originalmente emitiu o IO estiver bloqueando no ReadFile no Windows 8

Meu aplicativo parou de funcionar depois de mudar para o Windows 8. Passei horas para depurar o problema, descobri que o IOCP se comportava de maneira diferente entre o Windows 8 e versões anteriores. Eu extraio o código necessário para demonstrar e reproduzir o problema.

SOCKET sListen;

DWORD WINAPI WorkerProc(LPVOID lpParam)
{
    ULONG_PTR dwKey;
    DWORD dwTrans;
    LPOVERLAPPED lpol;
    while(true)
    {
        GetQueuedCompletionStatus((HANDLE)lpParam, &dwTrans, &dwKey, (LPOVERLAPPED*)&lpol, WSA_INFINITE);
        printf("dequeued an IO\n");
    }
}
DWORD WINAPI StartProc(LPVOID lpParam)
{
    WSADATA WsaData;
    if (WSAStartup(0x202,&WsaData)!=0) return 1;
    sListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    SOCKADDR_IN si;
    ZeroMemory(&si,sizeof(si));
    si.sin_family = AF_INET;
    si.sin_port = ntohs(1999);
    si.sin_addr.S_un.S_addr = INADDR_ANY;
    if(bind(sListen, (sockaddr*)&si, sizeof(si)) == SOCKET_ERROR) return 1;
    listen(sListen, SOMAXCONN);
    HANDLE hCompletion = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
    CreateIoCompletionPort((HANDLE)sListen, hCompletion, (DWORD)0, 0);
    CreateThread(NULL, 0, WorkerProc, hCompletion, 0, NULL);
    return 0;
}
DWORD WINAPI AcceptProc(LPVOID lpParam)
{
    DWORD dwBytes;
    LPOVERLAPPED pol=(LPOVERLAPPED)malloc(sizeof(OVERLAPPED));
    ZeroMemory(pol,sizeof(OVERLAPPED));
    SOCKET sClient = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    BOOL b = AcceptEx(sListen, 
        sClient,
        malloc ((sizeof(sockaddr_in) + 16) * 2), 
        0,
        sizeof(sockaddr_in) + 16, 
        sizeof(sockaddr_in) + 16, 
        &dwBytes, 
        pol);
    if(!b && WSAGetLastError() != WSA_IO_PENDING)   return 1;
    HANDLE hPipe=CreateNamedPipeA("\\\\.\\pipe\\testpipe",PIPE_ACCESS_DUPLEX,PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,4096,4096,999999999,NULL);
    BYTE chBuf[1024]; 
    DWORD  cbRead; 
    CreateFileA("\\\\.\\pipe\\testpipe", GENERIC_READ |GENERIC_WRITE,  0,NULL, OPEN_EXISTING, 0, NULL);
    ReadFile(hPipe,chBuf,1024, &cbRead,NULL);
    return 0;
}

int main()
{
    printf ("Starting server on port 1999...");
    WaitForSingleObject(CreateThread(NULL, 0, StartProc, NULL, 0, NULL),INFINITE);
    CreateThread(NULL, 0,AcceptProc, NULL, 0, NULL);
    printf ("done\n");
    Sleep(10000000);
    return 0;
}

Este programa escuta na porta 1999 e emite uma aceitação assíncrona e então lê um tubo de bloqueio. Eu testei este programa no Windows 7, 8, XP, 2003, 2008, depois de "telnet 127.0.0.1 1999", "dequeued um IO \ n" será impresso no console, exceto o Windows 8.

O ponto é que o encadeamento que originalmente emitiu a operação assíncrona não deve bloquear em ReadFile ou GetQueuedCompletionStatus jamais retirará o IO até que o ReadFile retorne no windows 8.

Eu também testei usando "scanf" em vez de leitura de pipe, os resultados são os mesmos, já que "scanf" irá chamar ReadFile para ler console eventualmente. Eu não sei se ReadFile é a única função afetada ou pode haver outras funções.

O que posso imaginar é usar um thread dedicado para emitir operações assíncronas, e toda a lógica de negócios se comunica com esse thread dedicado para executar accept / send / recv. Mas camada extra significa sobrecarga extra, existe alguma maneira de alcançar o mesmo desempenho que as versões anteriores do Windows no Windows 8?

questionAnswers(1)

yourAnswerToTheQuestion