diff options
Diffstat (limited to 'lib/server/WinNamedPipeStream.cpp')
-rw-r--r-- | lib/server/WinNamedPipeStream.cpp | 555 |
1 files changed, 267 insertions, 288 deletions
diff --git a/lib/server/WinNamedPipeStream.cpp b/lib/server/WinNamedPipeStream.cpp index 1179516e..448a3c9d 100644 --- a/lib/server/WinNamedPipeStream.cpp +++ b/lib/server/WinNamedPipeStream.cpp @@ -19,10 +19,12 @@ #include <errno.h> #include <windows.h> -#include "WinNamedPipeStream.h" -#include "ServerException.h" +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "BoxTime.h" #include "CommonException.h" #include "Socket.h" +#include "WinNamedPipeStream.h" #include "MemLeakFindOn.h" @@ -37,13 +39,14 @@ std::string WinNamedPipeStream::sPipeNamePrefix = "\\\\.\\pipe\\"; // // -------------------------------------------------------------------------- WinNamedPipeStream::WinNamedPipeStream() - : mSocketHandle(INVALID_HANDLE_VALUE), - mReadableEvent(INVALID_HANDLE_VALUE), - mBytesInBuffer(0), - mReadClosed(false), - mWriteClosed(false), - mIsServer(false), - mIsConnected(false) +: mSocketHandle(INVALID_HANDLE_VALUE), + mReadableEvent(INVALID_HANDLE_VALUE), + mBytesInBuffer(0), + mReadClosed(false), + mWriteClosed(false), + mIsServer(false), + mIsConnected(false), + mNeedAnotherRead(false) { } // -------------------------------------------------------------------------- @@ -55,14 +58,21 @@ WinNamedPipeStream::WinNamedPipeStream() // // -------------------------------------------------------------------------- WinNamedPipeStream::WinNamedPipeStream(HANDLE hNamedPipe) - : mSocketHandle(hNamedPipe), - mReadableEvent(INVALID_HANDLE_VALUE), - mBytesInBuffer(0), - mReadClosed(false), - mWriteClosed(false), - mIsServer(true), - mIsConnected(true) +: mSocketHandle(hNamedPipe), + mReadableEvent(INVALID_HANDLE_VALUE), + mBytesInBuffer(0), + mReadClosed(false), + mWriteClosed(false), + mIsServer(true), + mIsConnected(true), + mNeedAnotherRead(false) { + StartFirstRead(); +} + +// Start the first overlapped read +void WinNamedPipeStream::StartFirstRead() +{ // create the Readable event mReadableEvent = CreateEvent(NULL, TRUE, FALSE, NULL); @@ -74,23 +84,50 @@ WinNamedPipeStream::WinNamedPipeStream(HANDLE hNamedPipe) THROW_EXCEPTION(CommonException, Internal) } - // initialise the OVERLAPPED structure + StartOverlappedRead(); +} + +void WinNamedPipeStream::StartOverlappedRead() +{ + // We should only do this when the buffer is empty. We don't want + // to start an overlapped read anywhere else than the start of the + // buffer, because it could complete at any time and we don't want + // to mess about with interrupting the read already in progress. + ASSERT(mBytesInBuffer == 0); + + // Initialise the OVERLAPPED structure memset(&mReadOverlap, 0, sizeof(mReadOverlap)); mReadOverlap.hEvent = mReadableEvent; - // start the first overlapped read if (!ReadFile(mSocketHandle, mReadBuffer, sizeof(mReadBuffer), NULL, &mReadOverlap)) { DWORD err = GetLastError(); - - if (err != ERROR_IO_PENDING) + if (err == ERROR_IO_PENDING) + { + // Don't reset yet, there might be data + // in the buffer waiting to be read, + // will check below. + // ResetEvent(mReadableEvent); + } + else if (err == ERROR_HANDLE_EOF) + { + BOX_INFO("Control client disconnected"); + mReadClosed = true; + } + else if (err == ERROR_BROKEN_PIPE || + err == ERROR_PIPE_NOT_CONNECTED) + { + BOX_NOTICE("Control client disconnected"); + mReadClosed = true; + mIsConnected = false; + } + else { - BOX_ERROR("Failed to start overlapped read: " << - GetErrorMessage(err)); Close(); - THROW_EXCEPTION(ConnectionException, - Conn_SocketReadError) + THROW_WIN_ERROR_NUMBER("Failed to start overlapped " + "read", err, ConnectionException, + SocketReadError) } } } @@ -105,6 +142,12 @@ WinNamedPipeStream::WinNamedPipeStream(HANDLE hNamedPipe) // -------------------------------------------------------------------------- WinNamedPipeStream::~WinNamedPipeStream() { + for(std::list<WriteInProgress*>::iterator i = mWritesInProgress.begin(); + i != mWritesInProgress.end(); i++) + { + delete *i; + } + if (mSocketHandle != INVALID_HANDLE_VALUE) { try @@ -157,36 +200,7 @@ void WinNamedPipeStream::Accept() mIsServer = true; // must flush and disconnect before closing mIsConnected = true; - // create the Readable event - mReadableEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - if (mReadableEvent == INVALID_HANDLE_VALUE) - { - BOX_ERROR("Failed to create the Readable event: " << - GetErrorMessage(GetLastError())); - Close(); - THROW_EXCEPTION(CommonException, Internal) - } - - // initialise the OVERLAPPED structure - memset(&mReadOverlap, 0, sizeof(mReadOverlap)); - mReadOverlap.hEvent = mReadableEvent; - - // start the first overlapped read - if (!ReadFile(mSocketHandle, mReadBuffer, sizeof(mReadBuffer), - NULL, &mReadOverlap)) - { - DWORD err = GetLastError(); - - if (err != ERROR_IO_PENDING) - { - BOX_ERROR("Failed to start overlapped read: " << - GetErrorMessage(err)); - Close(); - THROW_EXCEPTION(ConnectionException, - Conn_SocketReadError) - } - } + StartFirstRead(); } */ @@ -214,7 +228,7 @@ void WinNamedPipeStream::Connect(const std::string& rName) 0, // no sharing NULL, // default security attributes OPEN_EXISTING, - 0, // default attributes + 0, // FILE_FLAG_OVERLAPPED, // dwFlagsAndAttributes NULL); // no template file if (mSocketHandle == INVALID_HANDLE_VALUE) @@ -237,6 +251,86 @@ void WinNamedPipeStream::Connect(const std::string& rName) mWriteClosed = false; mIsServer = false; // just close the socket mIsConnected = true; + + StartFirstRead(); +} + +// Returns true if the operation is complete (and you will need to start +// another one), or false otherwise (you can wait again). +bool WinNamedPipeStream::WaitForOverlappedOperation(OVERLAPPED& Overlapped, + int Timeout, int64_t* pBytesTransferred) +{ + if (Timeout == IOStream::TimeOutInfinite) + { + Timeout = INFINITE; + } + + // overlapped I/O completed successfully? (wait if needed) + DWORD waitResult = WaitForSingleObject(Overlapped.hEvent, Timeout); + DWORD NumBytesTransferred = -1; + + if (waitResult == WAIT_FAILED) + { + THROW_WIN_ERROR_NUMBER("Failed to wait for overlapped I/O", + GetLastError(), ServerException, Internal); + } + + if (waitResult == WAIT_ABANDONED) + { + THROW_EXCEPTION_MESSAGE(ServerException, Internal, + "Wait for overlapped I/O abandoned by system"); + } + + if (waitResult == WAIT_TIMEOUT) + { + // wait timed out, nothing to read + *pBytesTransferred = 0; + return false; + } + + if (waitResult != WAIT_OBJECT_0) + { + THROW_EXCEPTION_MESSAGE(ServerException, BadSocketHandle, + "Failed to wait for overlapped I/O: unknown " + "result code: " << waitResult); + } + + // Overlapped operation completed successfully. Return the number + // of bytes transferred. + if (GetOverlappedResult(mSocketHandle, &Overlapped, + &NumBytesTransferred, TRUE)) + { + *pBytesTransferred = NumBytesTransferred; + return true; + } + + // We are here because GetOverlappedResult() informed us that the + // overlapped operation encountered an error, so what was it? + DWORD err = GetLastError(); + + if (err == ERROR_HANDLE_EOF) + { + Close(); + *pBytesTransferred = 0; + return true; + } + + // ERROR_NO_DATA is a strange name for + // "The pipe is being closed". No exception wanted. + + if (err == ERROR_NO_DATA || + err == ERROR_PIPE_NOT_CONNECTED || + err == ERROR_BROKEN_PIPE) + { + BOX_INFO(BOX_WIN_ERRNO_MESSAGE(err, + "Named pipe peer disconnected")); + Close(); + *pBytesTransferred = 0; + return true; + } + + THROW_WIN_ERROR_NUMBER("Failed to wait for overlapped I/O " + "to complete", err, ConnectionException, SocketReadError); } // -------------------------------------------------------------------------- @@ -249,192 +343,61 @@ void WinNamedPipeStream::Connect(const std::string& rName) // -------------------------------------------------------------------------- int WinNamedPipeStream::Read(void *pBuffer, int NBytes, int Timeout) { - // TODO no support for timeouts yet - if (!mIsServer && Timeout != IOStream::TimeOutInfinite) - { - THROW_EXCEPTION(CommonException, AssertFailed) - } - if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) { - THROW_EXCEPTION(ServerException, BadSocketHandle) + THROW_EXCEPTION_MESSAGE(ServerException, BadSocketHandle, + "Tried to read from closed pipe"); } if (mReadClosed) { - THROW_EXCEPTION(ConnectionException, SocketShutdownError) + THROW_EXCEPTION_MESSAGE(ConnectionException, + SocketShutdownError, "Tried to read from closing pipe"); } // ensure safe to cast NBytes to unsigned if (NBytes < 0) { - THROW_EXCEPTION(CommonException, AssertFailed) + THROW_EXCEPTION(CommonException, AssertFailed); } - DWORD NumBytesRead; + int64_t NumBytesRead; - if (mIsServer) + // Satisfy from buffer if possible, to avoid blocking on read. + if (mBytesInBuffer == 0) { - // satisfy from buffer if possible, to avoid - // blocking on read. - bool needAnotherRead = false; - if (mBytesInBuffer == 0) - { - // overlapped I/O completed successfully? - // (wait if needed) - DWORD waitResult = WaitForSingleObject( - mReadOverlap.hEvent, Timeout); - - if (waitResult == WAIT_ABANDONED) - { - BOX_ERROR("Wait for command socket read " - "abandoned by system"); - THROW_EXCEPTION(ServerException, - BadSocketHandle); - } - else if (waitResult == WAIT_TIMEOUT) - { - // wait timed out, nothing to read - NumBytesRead = 0; - } - else if (waitResult != WAIT_OBJECT_0) - { - BOX_ERROR("Failed to wait for command " - "socket read: unknown result " << - waitResult); - } - // object is ready to read from - else if (GetOverlappedResult(mSocketHandle, - &mReadOverlap, &NumBytesRead, TRUE)) - { - needAnotherRead = true; - } - else - { - DWORD err = GetLastError(); - - if (err == ERROR_HANDLE_EOF) - { - mReadClosed = true; - } - else - { - if (err == ERROR_BROKEN_PIPE) - { - BOX_NOTICE("Control client " - "disconnected"); - } - else - { - BOX_ERROR("Failed to wait for " - "ReadFile to complete: " - << GetErrorMessage(err)); - } - - Close(); - THROW_EXCEPTION(ConnectionException, - Conn_SocketReadError) - } - } - } - else - { - NumBytesRead = 0; - } - - size_t BytesToCopy = NumBytesRead + mBytesInBuffer; - size_t BytesRemaining = 0; - - if (BytesToCopy > (size_t)NBytes) - { - BytesRemaining = BytesToCopy - NBytes; - BytesToCopy = NBytes; - } - - memcpy(pBuffer, mReadBuffer, BytesToCopy); - memmove(mReadBuffer, mReadBuffer + BytesToCopy, BytesRemaining); - - mBytesInBuffer = BytesRemaining; - NumBytesRead = BytesToCopy; - - if (needAnotherRead) + if (mNeedAnotherRead) { - // reinitialise the OVERLAPPED structure - memset(&mReadOverlap, 0, sizeof(mReadOverlap)); - mReadOverlap.hEvent = mReadableEvent; + // Start the next overlapped read + StartOverlappedRead(); } - // start the next overlapped read - if (needAnotherRead && !ReadFile(mSocketHandle, - mReadBuffer + mBytesInBuffer, - sizeof(mReadBuffer) - mBytesInBuffer, - NULL, &mReadOverlap)) - { - DWORD err = GetLastError(); - if (err == ERROR_IO_PENDING) - { - // Don't reset yet, there might be data - // in the buffer waiting to be read, - // will check below. - // ResetEvent(mReadableEvent); - } - else if (err == ERROR_HANDLE_EOF) - { - mReadClosed = true; - } - else if (err == ERROR_BROKEN_PIPE) - { - BOX_ERROR("Control client disconnected"); - mReadClosed = true; - } - else - { - BOX_ERROR("Failed to start overlapped read: " - << GetErrorMessage(err)); - Close(); - THROW_EXCEPTION(ConnectionException, - Conn_SocketReadError) - } - } + mNeedAnotherRead = WaitForOverlappedOperation(mReadOverlap, + Timeout, &NumBytesRead); } else { - if (!ReadFile( - mSocketHandle, // pipe handle - pBuffer, // buffer to receive reply - NBytes, // size of buffer - &NumBytesRead, // number of bytes read - NULL)) // not overlapped - { - DWORD err = GetLastError(); - - Close(); + // Just return the existing data from the buffer + // this time around. The caller should call again, + // and then the buffer will be empty. + NumBytesRead = 0; + } - // ERROR_NO_DATA is a strange name for - // "The pipe is being closed". No exception wanted. - - if (err == ERROR_NO_DATA || - err == ERROR_PIPE_NOT_CONNECTED) - { - NumBytesRead = 0; - } - else - { - BOX_ERROR("Failed to read from control socket: " - << GetErrorMessage(err)); - THROW_EXCEPTION(ConnectionException, - Conn_SocketReadError) - } - } - - // Closed for reading at EOF? - if (NumBytesRead == 0) - { - mReadClosed = true; - } + int BytesToCopy = NumBytesRead + mBytesInBuffer; + + if (NBytes < BytesToCopy) + { + BytesToCopy = NBytes; } - - return NumBytesRead; + + memcpy(pBuffer, mReadBuffer, BytesToCopy); + + size_t BytesRemaining = mBytesInBuffer + NumBytesRead - BytesToCopy; + ASSERT(BytesToCopy + BytesRemaining <= sizeof(mReadBuffer)); + memmove(mReadBuffer, mReadBuffer + BytesToCopy, BytesRemaining); + mBytesInBuffer = BytesRemaining; + + return BytesToCopy; } // -------------------------------------------------------------------------- @@ -445,8 +408,15 @@ int WinNamedPipeStream::Read(void *pBuffer, int NBytes, int Timeout) // Created: 2003/07/31 // // -------------------------------------------------------------------------- -void WinNamedPipeStream::Write(const void *pBuffer, int NBytes) +void WinNamedPipeStream::Write(const void *pBuffer, int NBytes, int Timeout) { + // Calculate the deadline at the beginning. Not valid if Timeout is + // IOStream::TimeOutInfinite! + ASSERT(Timeout != IOStream::TimeOutInfinite); + + box_time_t deadline = GetCurrentBoxTime() + + MilliSecondsToBoxTime(Timeout); + if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) { THROW_EXCEPTION(ServerException, BadSocketHandle) @@ -454,41 +424,62 @@ void WinNamedPipeStream::Write(const void *pBuffer, int NBytes) // Buffer in byte sized type. ASSERT(sizeof(char) == 1); - const char *pByteBuffer = (char *)pBuffer; - - int NumBytesWrittenTotal = 0; - - while (NumBytesWrittenTotal < NBytes) + WriteInProgress* new_write = new WriteInProgress( + std::string((char *)pBuffer, NBytes)); + + // Start the WriteFile operation, and add to queue if pending. + BOOL Success = WriteFile( + mSocketHandle, // pipe handle + new_write->mBuffer.c_str(), // message + NBytes, // message length + NULL, // bytes written this time + &(new_write->mOverlap)); + + if (Success == TRUE) { - DWORD NumBytesWrittenThisTime = 0; - - bool Success = WriteFile( - mSocketHandle, // pipe handle - pByteBuffer + NumBytesWrittenTotal, // message - NBytes - NumBytesWrittenTotal, // message length - &NumBytesWrittenThisTime, // bytes written this time - NULL); // not overlapped + // Unfortunately this does happen. We should still call + // GetOverlappedResult() to get the number of bytes written, + // so we can treat it just the same. + // BOX_NOTICE("Write claimed success while overlapped?"); + mWritesInProgress.push_back(new_write); + } + else + { + DWORD err = GetLastError(); - if (!Success) + if (err == ERROR_IO_PENDING) { - // ERROR_NO_DATA is a strange name for - // "The pipe is being closed". - - DWORD err = GetLastError(); - - if (err != ERROR_NO_DATA) - { - BOX_ERROR("Failed to write to control " - "socket: " << GetErrorMessage(err)); - } - + BOX_TRACE("WriteFile is pending, adding to queue"); + mWritesInProgress.push_back(new_write); + } + else + { + // Not in progress any more, pop it Close(); - - THROW_EXCEPTION(ConnectionException, - Conn_SocketWriteError) + THROW_WIN_ERROR_NUMBER("Failed to start overlapped " + "write", err, ConnectionException, + SocketWriteError); } + } + + // Wait for previous WriteFile operations to complete, one at a time, + // until the deadline expires or the pipe becomes disconnected. + for(box_time_t remaining = deadline - GetCurrentBoxTime(); + remaining > 0 && !mWritesInProgress.empty() && mIsConnected; + remaining = deadline - GetCurrentBoxTime()) + { + int new_timeout = BoxTimeToMilliSeconds(remaining); + WriteInProgress* oldest_write = + *(mWritesInProgress.begin()); - NumBytesWrittenTotal += NumBytesWrittenThisTime; + int64_t bytes_written = 0; + if(WaitForOverlappedOperation(oldest_write->mOverlap, + new_timeout, &bytes_written)) + { + // This one is complete, pop it and start a new one + delete oldest_write; + mWritesInProgress.pop_front(); + } } } @@ -513,59 +504,47 @@ void WinNamedPipeStream::Close() THROW_EXCEPTION(ServerException, BadSocketHandle) } - if (mIsServer) + if (!CancelIo(mSocketHandle)) { - if (!CancelIo(mSocketHandle)) - { - BOX_ERROR("Failed to cancel outstanding I/O: " << - GetErrorMessage(GetLastError())); - } + BOX_LOG_WIN_ERROR("Failed to cancel outstanding I/O"); + } - if (mReadableEvent == INVALID_HANDLE_VALUE) - { - BOX_ERROR("Failed to destroy Readable event: " - "invalid handle"); - } - else if (!CloseHandle(mReadableEvent)) - { - BOX_ERROR("Failed to destroy Readable event: " << - GetErrorMessage(GetLastError())); - } + if (mReadableEvent == INVALID_HANDLE_VALUE) + { + BOX_ERROR("Failed to destroy Readable event: " + "invalid handle"); + } + else if (!CloseHandle(mReadableEvent)) + { + BOX_LOG_WIN_ERROR("Failed to destroy Readable event"); + } - mReadableEvent = INVALID_HANDLE_VALUE; + mReadableEvent = INVALID_HANDLE_VALUE; - if (!FlushFileBuffers(mSocketHandle)) - { - BOX_ERROR("Failed to FlushFileBuffers: " << - GetErrorMessage(GetLastError())); - } - - if (!DisconnectNamedPipe(mSocketHandle)) + if (mIsConnected && !FlushFileBuffers(mSocketHandle)) + { + BOX_LOG_WIN_ERROR("Failed to FlushFileBuffers"); + } + + if (mIsServer && mIsConnected && !DisconnectNamedPipe(mSocketHandle)) + { + DWORD err = GetLastError(); + if (err != ERROR_PIPE_NOT_CONNECTED) { - DWORD err = GetLastError(); - if (err != ERROR_PIPE_NOT_CONNECTED) - { - BOX_ERROR("Failed to DisconnectNamedPipe: " << - GetErrorMessage(err)); - } + BOX_LOG_WIN_ERROR("Failed to DisconnectNamedPipe"); } - - mIsServer = false; } - bool result = CloseHandle(mSocketHandle); + if (!CloseHandle(mSocketHandle)) + { + THROW_WIN_ERROR_NUMBER("Failed to CloseHandle", + GetLastError(), ServerException, SocketCloseError); + } mSocketHandle = INVALID_HANDLE_VALUE; mIsConnected = false; mReadClosed = true; mWriteClosed = true; - - if (!result) - { - BOX_ERROR("Failed to CloseHandle: " << - GetErrorMessage(GetLastError())); - THROW_EXCEPTION(ServerException, SocketCloseError) - } } // -------------------------------------------------------------------------- |