Search code examples
winapinamed-pipesiocp

Detecting named pipe disconnects with I/O completion


I have a question about the correct approach for detecting client disconnects using named pipes with I/O completion ports. We have a server that creates child processes with stdin/stdout redirected to named pipes. The pipes are opened OVERLAPPED.

We've seen that after the client issues CreateFile() the I/O completion port receives a packet with lpNumberOfBytes of zero -- which quite effectively indicates a connection from the client. But detecting when the child process has closed its' stdin/stdout and exited does not generate a similar event.

We've come up with two approaches to detecting the named pipe disconnects;

1) periodically poll the process HANDLE of the child process to detect when the process has ended,

OR

2) create a separate thread which blocks on WaitForSingleObject() on the child process's HANDLE and when it becomes signaled the process has ended, to then generate PostQueuedCompletionStatus() to the I/O completion port with a prearranged COMPLETION_KEY.

Neither of these is difficult -- but I wanted to make sure I wasn't missing something obvious. Has anyone found an alternative to being notified when a named pipe associated with IOCP has been closed?


Solution

  • Ok, I discovered why the IOCP was not delivering disconnect packets, and it had todo with how I was testing the issue. We had developed a unittest harness and our unittest was acting as both server and client. When the child process ended, the child's write-pipe handle was still open in the unittest, and therefore IOCP did not unblock any handler threads.

    To effectively run a pipe server requires you create a new thread and within that thread to do the work of connecting to the pipe, creating the child process and waiting for the process to end. After the child ends to then close the pipe handle which causes IOCP to then deliver a dequeue packet with lpNumberOfBytes set to zero.

    Here is a sample of how we did this from a thread created with _beginthread().

    void __cdecl childproc(void* p) {
    
        TCHAR* pipename = (TCHAR*)p;
    
        /* make sure pipe handle is "inheritable" */
        SECURITY_ATTRIBUTES sattr;
        sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
        sattr.bInheritHandle = TRUE;
        sattr.lpSecurityDescriptor = NULL;
    
        HANDLE pipe = ::CreateFile(
                            pipename,
                            GENERIC_READ | GENERIC_WRITE,
                            0,
                            &sattr,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL);
        if (pipe == INVALID_HANDLE_VALUE) {
            _tprintf(_T("connect to named pipe failed %ld\n", GetLastError());
            _endthread();
            }
    
        /* redirect stdin/stdout/stderr to pipe */
        PROCESS_INFORMATION procinfo;
        STARTUPINFO startinfo;
        memset(&procinfo, 0, sizeof(procinfo));
        memset(&startinfo, 0, sizeof(startinfo));
        startinfo.cb = sizeof(startinfo);
        startinfo.hStdError = pipe;
        startinfo.hStdOutput = pipe;
        startinfo.hStdInput = pipe;
        startinfo.dwFlags |= STARTF_USESTDHANDLES;
    
        /* create child to do a simple "cmd.exe /c dir" */
        DWORD rc = ::CreateProcess(
                        _T("C:\\Windows\\System32\\cmd.exe"),
                        _T("C:\\Windows\\System32\\cmd.exe /C dir"),
                        NULL,
                        NULL,
                        TRUE,
                        0,
                        NULL,
                        NULL,
                        &startinfo,
                        &procinfo);
        if (rc == 0) {
            _tprintf(_T("cannot create child process: %ld\n"), GetLastError());
            _endthread();
            }
        if (::WaitForSingleObject(procinfo.hProcess, INFINITE) != WAIT_OBJECT_0) {
            _tprintf(_T("error waiting for child to end: %ld\n"), GetLastError());
            }
    
        /* cleanup */
        ::CloseHandle(procinfo.hProcess);
        ::CloseHandle(procinfo.hThread);
        ::CloseHandle(pipe);
    
        _endthread();
        }