在Windows 8下,如果最初发出IO的线程在ReadFile中阻塞,则GetQueuedCompletionStatus无法从IOCP中取消排队的IO。

12

我在切换到Windows 8后,我的应用程序停止工作了。我花费了几个小时来调试问题,发现IOCP在Windows 8和之前的版本之间表现不同。我提取了必要的代码来演示和复现问题。

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;
}

这个程序监听1999端口,异步接受请求并读取阻塞管道。我在Windows 7、8、XP、2003和2008上测试了这个程序,在"telnet 127.0.0.1 1999"之后,控制台会打印"dequeued an IO\n",但Windows 8除外。

关键点是最初发出异步操作的线程不能在ReadFile中阻塞,否则GetQueuedCompletionStatus将无法在Windows 8上出列该IO,直到ReadFile返回。

我还使用了"scanf"代替读取管道进行测试,结果相同,因为"scanf"最终会调用ReadFile读取控制台。我不知道是否只有ReadFile受到影响,或者可能存在其他功能受到影响。

我能想到的是使用专用线程来发出异步操作,并且所有业务逻辑与该专用线程通信以执行accept/send/recv。但额外的层意味着额外的开销,有没有办法在Windows 8上实现与之前版本相同的性能?


有趣的是...如果您使用WSAAccept()以阻塞的方式接受,然后在对管道进行阻塞读取之前发出重叠读取,则不会以同样的方式阻塞。重叠读取按预期运行。因此,看起来只有AcceptEx表现出这种行为... - Len Holgate
就这个意义而言,我也查看了这个被削减的内容。我不明白为什么AcceptEx()完成不能被处理:(现在他们又搞砸了什么呢?<g>) - Martin James
Martin看起来像是一个bug,而且这个bug已经进入了Windows 8 RTM,按照这个速度,它也会进入Server 2012 RTM。我已经尝试在其他微软论坛上发布过这个问题,但到目前为止似乎没有人感兴趣(或者有足够的知识来评论)。 - Len Holgate
..或者我们漏掉了什么.. - Martin James
我刚在MS Connect上收到了一条通知,确认这是一个bug,并且他们“将在未来的某个时间修复它”。所以,我猜想,任何使用AcceptEx()并在Windows 8或任何Windows Server 2012变体上运行的代码现在都有可能出现问题。 - Len Holgate
显示剩余4条评论
1个回答

7
请查看https://connect.microsoft.com/WindowsServer/feedback/details/760161/breaking-change-to-acceptex-and-iocp-in-server-2012-and-windows-8。这是一个错误,官方MS回应为:“我们已将此传递给基础操作系统团队,他们将考虑在未来更新中解决此问题。我正在解决此次延期。”请注意,我在今天(2013年9月12日)使用全新的Windows 8版本进行了测试,以准备测试Windows 8.1,并发现问题似乎已在Windows 8上得到解决,但我不知道何时解决的。

至少你从他们那里得到了一些回复 :) - nbevans
是的,它并不像我希望的那样有用。能够提供问题发生原因和可能导致问题的API的指示会很好。 - Len Holgate
MS Connect是毫无希望的,但不幸的是这是向微软报告此类问题的唯一途径。例如,上次我报告WCF中的一个错误时,它陷入了繁文缛节的泥潭,至今仍未得到修复。不过,我猜测这个AcceptEx错误可能更重要,因为它影响了核心操作系统。但我仍然认为你可能需要等待大约6个月。 - nbevans
我怀疑它不会影响IIS或SQL Server(否则在RTM之前就已经发现了),所以我同意,我预计修复它将需要很长时间。这不太可能影响我的客户,并且了解它(以及相关问题的可能性)应该帮助人们发现它并尽可能地解决它。 - Len Holgate
我真心希望它比其他向微软报告的问题更受重视。例如,他们在VS2010中的正则表达式实现对于序列接收是有问题的(它实际上使用N-1而不是N作为出现次数),这个问题被立即报告了;他们的回应是:我们在VS2012中修复了它,所以当我们发布它时,请切换到那个版本(当时它仍处于Beta测试阶段)。呸。回归Boost的脚步声可以听到好几英里远。 - WhozCraig

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接