Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Named Pipes server, how to interrupt or timeout the wait for client connection and for incoming data

I am writing a simple Named Pipes server for Windows, calling the Windows API (in Java with JNA but this is not relevant).

I am trying to figure out how to avoid that the server stays stuck forever waiting for a client to connect or for data to come from the client.

The server code does the following:

1) It creates the pipe by calling CreateNamedPipe, with PIPE_WAIT in the dwPipeMode argument.

2) It calls ConnectNamedPipe which doesn't return until a client has connected.

3) It enters a loop where it repeatedly reads a message from the client by calling ReadFile which doesn't return until data is read, and for each received message it sends a message back to the client in response by calling WriteFile.

4) After many such conversations the client and the server will disconnect from the pipe.

I just would like to be able to set timeouts in the wait for ConnectNamedPipe at step 2 and ReadFile at step 3, and I can't see where to set the timeouts. There is the nDefaultTimeOut argument in CreateNamedPipe function, but it doesn't really sound to be intended for that; the API doc says:

The default time-out value, in milliseconds, if the WaitNamedPipe function specifies NMPWAIT_USE_DEFAULT_WAIT.

So the nDefaultTimeOut arg in CreateNamedPipe sounds like the default timeout that the clients which would connect to the pipe would use for their operations and only if they call the WaitNamedPipe function. In fact in my tests values of 0 or 1000 don't make a difference, the call to ConnectNamedPipe never returns (unless a client connects). What I'm looking for is timeouts in the server instead, on the calls to ConnectNamedPipe and ReadFile.

As the doc of CreateNamedPipe, for the dwPipeMode argument with PIPE_WAIT says, Blocking mode is enabled. When the pipe handle is specified in the ReadFile, WriteFile, or ConnectNamedPipe function, the operations are not completed until there is data to read, all data is written, or a client is connected. Use of this mode can mean waiting indefinitely in some situations for a client process to perform an action.

So maybe the way to implement such timeouts is to create the pipe in non-blocking mode (with PIPE_NOWAIT instead of PIPE_WAIT) so that calls to ReadFile, WriteFile and ConnectNamedPipe return immediately, and then somehow monitor myself the event (client connected or data received) in a loop, and check myself within the loop whether a timeout elapsed or another interrupting event occurred (like the user clicking a Cancel button) ?

ADDED: It looks like for the ReadFile call I might be able to use PeekNamedPipe which returns immediately, to check if there is data to read, and only then call ReadFile. I will try that. But I still have the same problem for the call to ConnectNamedPipe.

ADDED: As I suspected and the answers confirmed, being a novice to pipes I was looking at them from a somehow skew angle, from which the need for timeouts appeared greater than it actually is.

F.ex. the reasoning behind wanting to timeout calls to ReadFile was that if I (the server) am inside it reading data from the client and the client suddenly shuts down, sometimes I might end up stuck inside the ReadFile. But now I know that if the ReadFile is reading from a pipe and the client shuts down, the ReadFile will always error out, so the execution won't be stuck inside it.

like image 716
SantiBailors Avatar asked Mar 06 '16 15:03

SantiBailors


2 Answers

I suggest you set FILE_FLAG_OVERLAPPED and use an event to check/wait for completion.

Although this is originally intended for asynchronous IO, you can instead time the event to your predefined time to live.

If you then wanted to cancel the I/O operation, you can use the CancelIo() function. If you just wanted to do some work and then resume waiting, you can do that too - timing out the wait doesn't automatically cancel the I/O, so you would not need to call ConnectNamedPipe again.

You could also, as you yourself suggested set PIPE_NOWAIT and poll the connection until successfull, either way should bring the same result in this use case. Note however that this is legacy functionality and Microsoft discourage use of this option.

like image 80
Melis Avatar answered Oct 17 '22 06:10

Melis


Some real-world code to demonstrate the asynchronous use of the server end of a pipe, in a GUI application:

void wait_for_object(HANDLE object)
{
  DWORD dw;
  MSG msg;

  for (;;) 
  {
    dw = MsgWaitForMultipleObjectsEx(1, &object, INFINITE, QS_ALLINPUT, 0);

    if (dw == WAIT_OBJECT_0) break;
    if (dw == WAIT_OBJECT_0 + 1) 
    {
      while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg);
      continue;
    }
    srvfail(L"sleep() messageloop", GetLastError());
  }
}

HANDLE server_pipe;
HANDLE io_event;

void pipe_connection(void)
{
    OVERLAPPED overlapped;
    DWORD dw, err;

    SecureZeroMemory(&overlapped, sizeof(overlapped));
    overlapped.hEvent = io_event;

    if (!ReadFile(server_pipe, input_buffer, sizeof(input_buffer) - 1, NULL, &overlapped))
    {
        err = GetLastError();
        if (err == ERROR_IO_PENDING)
        {
            wait_for_object(io_event);
            if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
            {
                srvfail(L"Read from pipe failed asynchronously.", GetLastError());
            }
        }
        else
        {
            srvfail(L"Read from pipe failed synchronously.", GetLastError());
        }
    }
    else
    {
        if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
        {
            srvfail(L"GetOverlappedResult failed reading from pipe.", GetLastError());
        }
    }

    input_buffer[dw] = '\0';

    process_command();

    if (!WriteFile(server_pipe, &output_struct, 
        ((char *)&output_struct.output_string - (char *)&output_struct) + output_struct.string_length, 
        NULL, &overlapped))
    {
        err = GetLastError();
        if (err == ERROR_IO_PENDING)
        {
            wait_for_object(io_event);
            if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
            {
                srvfail(L"Write to pipe failed asynchronously.", GetLastError());
            }
        }
        else
        {
            srvfail(L"Write to pipe failed synchronously.", GetLastError());
        }
    }
    else
    {
        if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
        {
            srvfail(L"GetOverlappedResult failed writing to pipe.", GetLastError());
        }
    }

    if (!FlushFileBuffers(server_pipe)) srvfail(L"FlushFileBuffers failed.", GetLastError());
    if (!DisconnectNamedPipe(server_pipe)) srvfail(L"DisconnectNamedPipe failed.", GetLastError());
}

void server(void)
{
    OVERLAPPED overlapped;
    DWORD err, dw; 

    // Create the named pipe

    server_pipe = CreateNamedPipe(pipe_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 1, buffer_size, buffer_size, 0, NULL);
    if (server_pipe == INVALID_HANDLE_VALUE) srvfail(L"CreateNamedPipe failed.", GetLastError());

    // Wait for connections

    io_event = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (io_event == NULL) srvfail(L"CreateEvent(io_event) failed.", GetLastError());

    for (;;)
    {
        SecureZeroMemory(&overlapped, sizeof(overlapped));
        overlapped.hEvent = io_event;

        if (!ConnectNamedPipe(server_pipe, &overlapped))
        {
            err = GetLastError();
            if (err == ERROR_PIPE_CONNECTED)
            {
                pipe_connection();
            }
            else if (err == ERROR_IO_PENDING)
            {
                wait_for_object(io_event);
                if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
                {
                    srvfail(L"Pipe connection failed asynchronously.", GetLastError());
                }
                pipe_connection();
            }
            else
            {
                srvfail(L"Pipe connection failed synchronously.", GetLastError());
            }
        }
        else
        {
            if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
            {
                srvfail(L"GetOverlappedResult failed connecting pipe.", GetLastError());
            }
            pipe_connection();
        }
    }
}

(This code has been edited down from the original to remove extraneous logic. I haven't tried compiling the edited version, so there may be some minor problems. Also note the use of global variables, which is OK in my case because the application is very small, but should usually be avoided.)

The use of MsgWaitForMultipleObjectsEx() allows window messages to be processed while you are waiting for I/O to complete. If you were also waiting for something else to happen, you could pass it an array of handles rather than just a single handle - for example, if you wanted to monitor a child process and do something when it exited, you could pass an array containing both io_event and the process handle. And if you just had to do some other job periodically, you could set a timeout for the wait, or use a window timer.

like image 45
Harry Johnston Avatar answered Oct 17 '22 07:10

Harry Johnston