From bc2186631114416ba66db19819bfc231b4a08534 Mon Sep 17 00:00:00 2001 From: John Salem Date: Fri, 28 Feb 2020 15:48:24 -0800 Subject: [PATCH 01/52] Add Advertise IPC Command --- src/coreclr/src/vm/CMakeLists.txt | 2 + src/coreclr/src/vm/diagnosticsprotocol.h | 1 + .../vm/diagnosticsserverprotocolhelper.cpp | 38 ++++++++++++++ .../src/vm/diagnosticsserverprotocolhelper.h | 52 +++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 src/coreclr/src/vm/diagnosticsserverprotocolhelper.cpp create mode 100644 src/coreclr/src/vm/diagnosticsserverprotocolhelper.h diff --git a/src/coreclr/src/vm/CMakeLists.txt b/src/coreclr/src/vm/CMakeLists.txt index 2ce12c7250b461..2b4d3df8555c75 100644 --- a/src/coreclr/src/vm/CMakeLists.txt +++ b/src/coreclr/src/vm/CMakeLists.txt @@ -320,6 +320,7 @@ set(VM_SOURCES_WKS custommarshalerinfo.cpp autotrace.cpp diagnosticserver.cpp + diagnosticsserverprotocolhelper.cpp dllimportcallback.cpp eeconfig.cpp eecontract.cpp @@ -436,6 +437,7 @@ set(VM_HEADERS_WKS custommarshalerinfo.h autotrace.h diagnosticserver.h + diagnosticsserverprotocolhelper.h diagnosticsprotocol.h dllimportcallback.h eeconfig.h diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index bbc622a6411a3c..5ef3c5561e89ee 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -79,6 +79,7 @@ namespace DiagnosticsIpc { OK = 0x00, Error = 0xFF, + Advertise = 0x01, }; struct MagicVersion diff --git a/src/coreclr/src/vm/diagnosticsserverprotocolhelper.cpp b/src/coreclr/src/vm/diagnosticsserverprotocolhelper.cpp new file mode 100644 index 00000000000000..d560bd169af824 --- /dev/null +++ b/src/coreclr/src/vm/diagnosticsserverprotocolhelper.cpp @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "common.h" +#include "diagnosticsserverprotocolhelper.h" +#include "diagnosticsipc.h" +#include "diagnosticsprotocol.h" + +const DiagnosticsServerAdvertiseCommandPayload* DiagnosticsServerAdvertiseCommandPayload::TryParse(BYTE* lpBuffer, uint16_t& BufferSize) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(lpBuffer != nullptr); + } + CONTRACTL_END; + + NewHolder payload = new (nothrow) DiagnosticsServerAdvertiseCommandPayload; + if (payload == nullptr) + { + // OOM + return nullptr; + } + + payload->incomingBuffer = lpBuffer; + uint8_t* pBufferCursor = payload->incomingBuffer; + uint32_t bufferLen = BufferSize; + if (!::TryParse(pBufferCursor, bufferLen, payload->pid) || + !::TryParse(pBufferCursor, bufferLen, payload->hash)) + { + return nullptr; + } + + return payload; +} \ No newline at end of file diff --git a/src/coreclr/src/vm/diagnosticsserverprotocolhelper.h b/src/coreclr/src/vm/diagnosticsserverprotocolhelper.h new file mode 100644 index 00000000000000..4d77c9d21597c1 --- /dev/null +++ b/src/coreclr/src/vm/diagnosticsserverprotocolhelper.h @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __DIAGNOSTICSSERVER_PROTOCOL_HELPER_H__ +#define __DIAGNOSTICSSERVER_PROTOCOL_HELPER_H__ + +#ifdef FEATURE_PERFTRACING + +#include "common.h" +#include "diagnosticsipc.h" +#include "diagnosticsprotocol.h" + +class IpcStream; + +/** + * The Diagnostics Server command set is 0xFF + * see diagnosticsipc.h and diagnosticserver.h for more details + * enum class DiagnosticServerCommandId : uint8_t + * { + * OK = 0x00, + * Error = 0xFF, + * Advertise = 0x01, + * }; + */ + + +// Command = 0xFF01 +struct DiagnosticsServerAdvertiseCommandPayload +{ + NewArrayHolder incomingBuffer; + + // The protocol buffer is defined as: + // X, Y, Z means encode bytes for X followed by bytes for Y followed by bytes for Z + // + // PID = ulong + // hash = CLSID (GUID) + uint64_t pid; + CLSID hash; + static const DiagnosticsServerAdvertiseCommandPayload* TryParse(BYTE* lpBuffer, uint16_t& BufferSize); +}; + +class DiagnosticsServerProtocolHelper +{ +public: + // IPC event handlers. + static void HandleIpcMessage(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream); +}; + +#endif // FEATURE_PERFTRACING + +#endif // __DIAGNOSTICSSERVER_PROTOCOL_HELPER_H__ From 0255b37703a761c29c7a4ebc6ab83cd520e0f1cc Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 2 Mar 2020 11:57:01 -0800 Subject: [PATCH 02/52] Add untested select connect and nonblocking accept --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 36 ++++++++- .../debug/debug-pal/win/diagnosticsipc.cpp | 76 ++++++++++++++++++- src/coreclr/src/debug/inc/diagnosticsipc.h | 18 ++++- src/coreclr/src/vm/diagnosticserver.cpp | 2 +- 4 files changed, 120 insertions(+), 12 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index e70f35884f510a..460ca424aefb55 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -124,19 +124,47 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p return new IpcStream::DiagnosticsIpc(serverSocket, &serverAddress); } -IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) const +IpcStream *IpcStream::DiagnosticsIpc::Connect(const char *const pIpcName, ErrorCallback callback) const +{ + sockaddr_un serverAddress{}; + serverAddress.sun_family = AF_UNIX; + const int clientSocket = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (clientSocket == -1) + { + if (callback != nullptr) + callback(strerror(errno), errno); + // TODO: unlinks? + } + + if (pIpcName != nullptr) + { + int chars = snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", pIpcName); + _ASSERTE(chars > 0 && (unsigned int)chars < sizeof(serverAddress.sun_path)); + } + + if (::connect(clientSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0) + { + if (callback != nullptr) + callback(strerror(errno), errno); + // TODO: Anything else? + } + + return new IpcStream(clientSocket, ServerMode::CLIENT); +} + +IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback callback) const { sockaddr_un from; socklen_t fromlen = sizeof(from); - const int clientSocket = ::accept(_serverSocket, (sockaddr *)&from, &fromlen); - if (clientSocket == -1) + const int clientSocket = shouldBlock ? -1 : ::accept(_serverSocket, (sockaddr *)&from, &fromlen); + if (shouldBlock && clientSocket == -1) { if (callback != nullptr) callback(strerror(errno), errno); return nullptr; } - return new IpcStream(clientSocket); + return new IpcStream(shouldBlock ? clientSocket : _serverSocket); } void IpcStream::DiagnosticsIpc::Close(ErrorCallback callback) diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 36c11857cabe98..365f249869ad56 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -50,14 +50,16 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p return new IpcStream::DiagnosticsIpc(namedPipeName); } -IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) const +IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback callback) const { const uint32_t nInBufferSize = 16 * 1024; const uint32_t nOutBufferSize = 16 * 1024; HANDLE hPipe = ::CreateNamedPipeA( _pNamedPipeName, // pipe name PIPE_ACCESS_DUPLEX, // read/write access - PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, // message type pipe, message-read and blocking mode + PIPE_TYPE_BYTE | + (shouldBlock ? PIPE_WAIT : PIPE_NOWAIT) | + PIPE_REJECT_REMOTE_CLIENTS, // message type pipe, message-read and blocking mode PIPE_UNLIMITED_INSTANCES, // max. instances nOutBufferSize, // output buffer size nInBufferSize, // input buffer size @@ -77,6 +79,9 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) const const DWORD errorCode = ::GetLastError(); switch (errorCode) { + case ERROR_PIPE_LISTENING: + // Occurs when there isn't a pending client and we're + // in PIPE_NOWAIT mode case ERROR_PIPE_CONNECTED: // Occurs when a client connects before the function is called. // In this case, there is a connection between client and @@ -94,6 +99,31 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) const return new IpcStream(hPipe); } +IpcStream *IpcStream::DiagnosticsIpc::Connect(const char *const pIpcName, ErrorCallback callback) +{ + DiagnosticsIpc *diagnosticsIpc = DiagnosticsIpc::Create(pIpcName, callback); + const uint32_t nInBufferSize = 16 * 1024; + const uint32_t nOutBufferSize = 16 * 1024; + HANDLE hPipe = ::CreateFileA( + diagnosticsIpc->_pNamedPipeName, // pipe name + GENERIC_READ | // read and write access + GENERIC_WRITE, + 0, // no sharing + NULL, // default security attributes + OPEN_EXISTING, // opens existing pipe + 0, // default attributes + NULL); // no template file + + if (hPipe == INVALID_HANDLE_VALUE) + { + if (callback != nullptr) + callback("Failed to connect to named pipe.", ::GetLastError()); + return nullptr; + } + + return new IpcStream(hPipe, ConnectionMode::CLIENT); +} + void IpcStream::DiagnosticsIpc::Close(ErrorCallback) { } @@ -104,14 +134,52 @@ IpcStream::~IpcStream() { Flush(); - const BOOL fSuccessDisconnectNamedPipe = ::DisconnectNamedPipe(_hPipe); - assert(fSuccessDisconnectNamedPipe != 0); + if (_mode == ConnectionMode::SERVER) + { + const BOOL fSuccessDisconnectNamedPipe = ::DisconnectNamedPipe(_hPipe); + assert(fSuccessDisconnectNamedPipe != 0); + } const BOOL fSuccessCloseHandle = ::CloseHandle(_hPipe); assert(fSuccessCloseHandle != 0); } } +IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallback callback) +{ + // load up an array of handles + HANDLE *pHandles = new HANDLE[nStreams]; + for (uint32_t i = 0; i < nStreams; i++) + pHandles[i] = pStreams[i]->_hPipe; + + // call wait for multiple obj + DWORD dwWait = WaitForMultipleObjects( + nStreams, // count + pHandles, // handles + false, // Don't wait all + INFINITE); // wait infinitely + + // determine which of the streams signaled + DWORD index = dwWait - WAIT_OBJECT_0; + if (index < 0 || index > (nStreams - 1)) + { + if (callback != nullptr) + callback("Failed to select to named pipe.", ::GetLastError()); + return nullptr; + } + + // set that stream's mode to blocking + bool result = SetNamedPipeHandleState( + pHandles[index], // handle + PIPE_READMODE_BYTE | PIPE_WAIT, // read mode and wait mode + NULL, // no collecting + NULL); // no collecting + + // cleanup and return that stream + delete pHandles; + return pStreams[index]; +} + bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) const { assert(lpBuffer != nullptr); diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index eabea6c3ceaea0..bb30cebec78642 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -22,6 +22,13 @@ class IpcStream final bool Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) const; bool Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) const; bool Flush() const; + static IpcStream *Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallback callback = nullptr); + + enum ConnectionMode + { + CLIENT, + SERVER + }; class DiagnosticsIpc final { @@ -32,7 +39,9 @@ class IpcStream final static DiagnosticsIpc *Create(const char *const pIpcName, ErrorCallback callback = nullptr); //! Enables the underlaying IPC implementation to accept connection. - IpcStream *Accept(ErrorCallback callback = nullptr) const; + IpcStream *Accept(bool shouldBlock, ErrorCallback callback = nullptr) const; + + static IpcStream *Connect(const char *const pIpcName, ErrorCallback callback = nullptr); //! Closes an open IPC. void Close(ErrorCallback callback = nullptr); @@ -66,12 +75,15 @@ class IpcStream final private: #ifdef TARGET_UNIX int _clientSocket = -1; - IpcStream(int clientSocket) : _clientSocket(clientSocket) {} + IpcStream(int clientSocket, ConnectionMode mode = ConnectionMode::SERVER) + : _clientSocket(clientSocket), _mode(mode) {} #else HANDLE _hPipe = INVALID_HANDLE_VALUE; - IpcStream(HANDLE hPipe) : _hPipe(hPipe) {} + IpcStream(HANDLE hPipe, ConnectionMode mode = ConnectionMode::SERVER) : _hPipe(hPipe), _mode(mode) {} #endif /* TARGET_UNIX */ + ConnectionMode _mode; + IpcStream() = delete; IpcStream(const IpcStream &src) = delete; IpcStream(IpcStream &&src) = delete; diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index 5a80396179ae00..d45b8eef58679c 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -48,7 +48,7 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) while (!s_shuttingDown) { // FIXME: Ideally this would be something like a std::shared_ptr - IpcStream *pStream = s_pIpc->Accept(LoggingCallback); + IpcStream *pStream = s_pIpc->Accept(true, LoggingCallback); if (pStream == nullptr) continue; From 7f8bb61575d4efd16f4fecbf24b7f07cac4eb337 Mon Sep 17 00:00:00 2001 From: John Salem Date: Tue, 3 Mar 2020 19:05:56 -0800 Subject: [PATCH 03/52] Add select API on windows * no select on unix * untested on unix --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 57 ++++--- .../debug/debug-pal/win/diagnosticsipc.cpp | 145 ++++++++++++++---- src/coreclr/src/debug/inc/diagnosticsipc.h | 27 ++-- src/coreclr/src/vm/diagnosticserver.cpp | 8 +- 4 files changed, 164 insertions(+), 73 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 460ca424aefb55..e19bd7ee6bda28 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -12,10 +12,11 @@ #include "diagnosticsipc.h" #include "processdescriptor.h" -IpcStream::DiagnosticsIpc::DiagnosticsIpc(const int serverSocket, sockaddr_un *const pServerAddress) : +IpcStream::DiagnosticsIpc::DiagnosticsIpc(const int serverSocket, sockaddr_un *const pServerAddress, ConnectionMode mode) : _serverSocket(serverSocket), _pServerAddress(new sockaddr_un), - _isClosed(false) + _isClosed(false), + _mode(mode) { _ASSERTE(_pServerAddress != nullptr); _ASSERTE(_serverSocket != -1); @@ -32,24 +33,8 @@ IpcStream::DiagnosticsIpc::~DiagnosticsIpc() delete _pServerAddress; } -IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const pIpcName, ErrorCallback callback) +IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const pIpcName, ConnectionMode mode, ErrorCallback callback) { -#ifdef __APPLE__ - mode_t prev_mask = umask(~(S_IRUSR | S_IWUSR)); // This will set the default permission bit to 600 -#endif // __APPLE__ - - const int serverSocket = ::socket(AF_UNIX, SOCK_STREAM, 0); - if (serverSocket == -1) - { - if (callback != nullptr) - callback(strerror(errno), errno); -#ifdef __APPLE__ - umask(prev_mask); -#endif // __APPLE__ - _ASSERTE(!"Failed to create diagnostics IPC socket."); - return nullptr; - } - sockaddr_un serverAddress{}; serverAddress.sun_family = AF_UNIX; @@ -71,6 +56,24 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p "socket"); } + if (mode == ConnectionMode::CLIENT) + return new IpcStream::DiagnosticsIpc(-1, &serverAddress, ConnectionMode::CLIENT); + +#ifdef __APPLE__ + mode_t prev_mask = umask(~(S_IRUSR | S_IWUSR)); // This will set the default permission bit to 600 +#endif // __APPLE__ + + const int serverSocket = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (serverSocket == -1) + { + if (callback != nullptr) + callback(strerror(errno), errno); +#ifdef __APPLE__ + umask(prev_mask); +#endif // __APPLE__ + _ASSERTE(!"Failed to create diagnostics IPC socket."); + return nullptr; + } #ifndef __APPLE__ if (fchmod(serverSocket, S_IRUSR | S_IWUSR) == -1) @@ -124,28 +127,24 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p return new IpcStream::DiagnosticsIpc(serverSocket, &serverAddress); } -IpcStream *IpcStream::DiagnosticsIpc::Connect(const char *const pIpcName, ErrorCallback callback) const +IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) const { - sockaddr_un serverAddress{}; - serverAddress.sun_family = AF_UNIX; + sockaddr_un clientAddress{}; + clientAddress.sun_family = AF_UNIX; const int clientSocket = ::socket(AF_UNIX, SOCK_STREAM, 0); if (clientSocket == -1) { if (callback != nullptr) callback(strerror(errno), errno); + return nullptr; // TODO: unlinks? } - if (pIpcName != nullptr) - { - int chars = snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", pIpcName); - _ASSERTE(chars > 0 && (unsigned int)chars < sizeof(serverAddress.sun_path)); - } - - if (::connect(clientSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0) + if (::connect(clientSocket, (struct sockaddr *)_pServerAddress, sizeof(*_pServerAddress)) < 0) { if (callback != nullptr) callback(strerror(errno), errno); + return nullptr; // TODO: Anything else? } diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 365f249869ad56..fefd7ba939328a 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -7,7 +7,10 @@ #include #include "diagnosticsipc.h" -IpcStream::DiagnosticsIpc::DiagnosticsIpc(const char(&namedPipeName)[MaxNamedPipeNameLength]) +#define _ASSERTE assert + +IpcStream::DiagnosticsIpc::DiagnosticsIpc(const char(&namedPipeName)[MaxNamedPipeNameLength], ConnectionMode mode) + : _mode(mode) { memcpy(_pNamedPipeName, namedPipeName, sizeof(_pNamedPipeName)); } @@ -17,7 +20,7 @@ IpcStream::DiagnosticsIpc::~DiagnosticsIpc() Close(); } -IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const pIpcName, ErrorCallback callback) +IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const pIpcName, ConnectionMode mode, ErrorCallback callback) { char namedPipeName[MaxNamedPipeNameLength]{}; int nCharactersWritten = -1; @@ -39,26 +42,40 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p ::GetCurrentProcessId()); } + if (mode == ConnectionMode::CLIENT) + { + // TODO: block here till the socket exists? + } + if (nCharactersWritten == -1) { if (callback != nullptr) callback("Failed to generate the named pipe name", nCharactersWritten); - assert(nCharactersWritten != -1); + _ASSERTE(nCharactersWritten != -1); return nullptr; } - return new IpcStream::DiagnosticsIpc(namedPipeName); + return new IpcStream::DiagnosticsIpc(namedPipeName, mode); } IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback callback) const { + _ASSERTE(_mode == ConnectionMode::SERVER); + if (_mode != ConnectionMode::SERVER) + { + if (callback != nullptr) + callback("Cannot call accept on a client connection", 0); + return nullptr; + } + const uint32_t nInBufferSize = 16 * 1024; const uint32_t nOutBufferSize = 16 * 1024; HANDLE hPipe = ::CreateNamedPipeA( _pNamedPipeName, // pipe name - PIPE_ACCESS_DUPLEX, // read/write access + PIPE_ACCESS_DUPLEX | + FILE_FLAG_OVERLAPPED, // read/write access PIPE_TYPE_BYTE | - (shouldBlock ? PIPE_WAIT : PIPE_NOWAIT) | + PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, // message type pipe, message-read and blocking mode PIPE_UNLIMITED_INSTANCES, // max. instances nOutBufferSize, // output buffer size @@ -73,7 +90,11 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal return nullptr; } - const BOOL fSuccess = ::ConnectNamedPipe(hPipe, NULL) != 0; + // TODO: Find a better way to do this than + // mixing abstractions + IpcStream *pStream = new IpcStream(hPipe, _mode); + + BOOL fSuccess = ::ConnectNamedPipe(hPipe, &pStream->_oOverlap) != 0; if (!fSuccess) { const DWORD errorCode = ::GetLastError(); @@ -82,6 +103,14 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal case ERROR_PIPE_LISTENING: // Occurs when there isn't a pending client and we're // in PIPE_NOWAIT mode + case ERROR_IO_PENDING: + if (shouldBlock) + { + fSuccess = GetOverlappedResult(pStream->_hPipe, + &pStream->_oOverlap, + NULL, + true); + } case ERROR_PIPE_CONNECTED: // Occurs when a client connects before the function is called. // In this case, there is a connection between client and @@ -92,20 +121,26 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal if (callback != nullptr) callback("A client process failed to connect.", errorCode); ::CloseHandle(hPipe); + delete pStream; return nullptr; } } - return new IpcStream(hPipe); + return pStream; } -IpcStream *IpcStream::DiagnosticsIpc::Connect(const char *const pIpcName, ErrorCallback callback) +IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) { - DiagnosticsIpc *diagnosticsIpc = DiagnosticsIpc::Create(pIpcName, callback); - const uint32_t nInBufferSize = 16 * 1024; - const uint32_t nOutBufferSize = 16 * 1024; + _ASSERTE(_mode == ConnectionMode::CLIENT); + if (_mode != ConnectionMode::CLIENT) + { + if (callback != nullptr) + callback("Cannot call connect on a client connection", 0); + return nullptr; + } + HANDLE hPipe = ::CreateFileA( - diagnosticsIpc->_pNamedPipeName, // pipe name + _pNamedPipeName, // pipe name GENERIC_READ | // read and write access GENERIC_WRITE, 0, // no sharing @@ -121,27 +156,35 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(const char *const pIpcName, ErrorC return nullptr; } - return new IpcStream(hPipe, ConnectionMode::CLIENT); + return new IpcStream(hPipe, _mode); } void IpcStream::DiagnosticsIpc::Close(ErrorCallback) { } +IpcStream::IpcStream(HANDLE hPipe, DiagnosticsIpc::ConnectionMode mode) : + _hPipe(hPipe), + _mode(mode) +{ + if (_mode == DiagnosticsIpc::ConnectionMode::SERVER) + _oOverlap.hEvent = CreateEvent(NULL, true, false, NULL); +} + IpcStream::~IpcStream() { if (_hPipe != INVALID_HANDLE_VALUE) { Flush(); - if (_mode == ConnectionMode::SERVER) + if (_mode == DiagnosticsIpc::ConnectionMode::SERVER) { const BOOL fSuccessDisconnectNamedPipe = ::DisconnectNamedPipe(_hPipe); - assert(fSuccessDisconnectNamedPipe != 0); + _ASSERTE(fSuccessDisconnectNamedPipe != 0); } const BOOL fSuccessCloseHandle = ::CloseHandle(_hPipe); - assert(fSuccessCloseHandle != 0); + _ASSERTE(fSuccessCloseHandle != 0); } } @@ -150,7 +193,16 @@ IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallb // load up an array of handles HANDLE *pHandles = new HANDLE[nStreams]; for (uint32_t i = 0; i < nStreams; i++) - pHandles[i] = pStreams[i]->_hPipe; + { + if (pStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER) + { + pHandles[i] = pStreams[i]->_oOverlap.hEvent; + } + else + { + pHandles[i] = pStreams[i]->_hPipe; + } + } // call wait for multiple obj DWORD dwWait = WaitForMultipleObjects( @@ -165,15 +217,26 @@ IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallb { if (callback != nullptr) callback("Failed to select to named pipe.", ::GetLastError()); + delete pHandles; return nullptr; } - // set that stream's mode to blocking - bool result = SetNamedPipeHandleState( - pHandles[index], // handle - PIPE_READMODE_BYTE | PIPE_WAIT, // read mode and wait mode - NULL, // no collecting - NULL); // no collecting + if (pStreams[index]->_mode == IpcStream::DiagnosticsIpc::ConnectionMode::SERVER) + { + // set that stream's mode to blocking + bool result = SetNamedPipeHandleState( + pHandles[index], // handle + PIPE_READMODE_BYTE | PIPE_WAIT, // read mode and wait mode + NULL, // no collecting + NULL); // no collecting + if (!result) + { + if (callback != nullptr) + callback("Failed to convert handle to wait mode", ::GetLastError()); + delete pHandles; + return nullptr; + } + } // cleanup and return that stream delete pHandles; @@ -182,18 +245,29 @@ IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallb bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) const { - assert(lpBuffer != nullptr); + _ASSERTE(lpBuffer != nullptr); DWORD nNumberOfBytesRead = 0; - const bool fSuccess = ::ReadFile( + LPOVERLAPPED overlap = (_mode == DiagnosticsIpc::ConnectionMode::SERVER) ? + const_cast(&_oOverlap) : + NULL; + bool fSuccess = ::ReadFile( _hPipe, // handle to pipe lpBuffer, // buffer to receive data nBytesToRead, // size of buffer &nNumberOfBytesRead, // number of bytes read - NULL) != 0; // not overlapped I/O + overlap) != 0; // not overlapped I/O if (!fSuccess) { + DWORD dwError = GetLastError(); + if (dwError == ERROR_IO_PENDING) + { + fSuccess = GetOverlappedResult(_hPipe, + overlap, + &nNumberOfBytesRead, + true) != 0; + } // TODO: Add error handling. } @@ -203,18 +277,29 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) const { - assert(lpBuffer != nullptr); + _ASSERTE(lpBuffer != nullptr); DWORD nNumberOfBytesWritten = 0; - const bool fSuccess = ::WriteFile( + LPOVERLAPPED overlap = (_mode == DiagnosticsIpc::ConnectionMode::SERVER) ? + const_cast(&_oOverlap) : + NULL; + bool fSuccess = ::WriteFile( _hPipe, // handle to pipe lpBuffer, // buffer to write from nBytesToWrite, // number of bytes to write &nNumberOfBytesWritten, // number of bytes written - NULL) != 0; // not overlapped I/O + overlap) != 0; // not overlapped I/O if (!fSuccess) { + DWORD dwError = GetLastError(); + if (dwError == ERROR_IO_PENDING) + { + fSuccess = GetOverlappedResult(_hPipe, + overlap, + &nNumberOfBytesWritten, + true) != 0; + } // TODO: Add error handling. } diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index bb30cebec78642..c71851604f8177 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -24,24 +24,24 @@ class IpcStream final bool Flush() const; static IpcStream *Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallback callback = nullptr); - enum ConnectionMode - { - CLIENT, - SERVER - }; - class DiagnosticsIpc final { public: + enum ConnectionMode + { + CLIENT, + SERVER + }; + ~DiagnosticsIpc(); //! Creates an IPC object - static DiagnosticsIpc *Create(const char *const pIpcName, ErrorCallback callback = nullptr); + static DiagnosticsIpc *Create(const char *const pIpcName, ConnectionMode mode, ErrorCallback callback = nullptr); //! Enables the underlaying IPC implementation to accept connection. IpcStream *Accept(bool shouldBlock, ErrorCallback callback = nullptr) const; - static IpcStream *Connect(const char *const pIpcName, ErrorCallback callback = nullptr); + IpcStream *Connect(ErrorCallback callback = nullptr); //! Closes an open IPC. void Close(ErrorCallback callback = nullptr); @@ -53,7 +53,7 @@ class IpcStream final sockaddr_un *const _pServerAddress; bool _isClosed; - DiagnosticsIpc(const int serverSocket, sockaddr_un *const pServerAddress); + DiagnosticsIpc(const int serverSocket, sockaddr_un *const pServerAddress, ConnectionMode mode = ConnectionMode::SERVER); //! Used to unlink the socket so it can be removed from the filesystem //! when the last reference to it is closed. @@ -62,9 +62,11 @@ class IpcStream final static const uint32_t MaxNamedPipeNameLength = 256; char _pNamedPipeName[MaxNamedPipeNameLength]; // https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createnamedpipea - DiagnosticsIpc(const char(&namedPipeName)[MaxNamedPipeNameLength]); + DiagnosticsIpc(const char(&namedPipeName)[MaxNamedPipeNameLength], ConnectionMode mode = ConnectionMode::SERVER); #endif /* TARGET_UNIX */ + ConnectionMode _mode; + DiagnosticsIpc() = delete; DiagnosticsIpc(const DiagnosticsIpc &src) = delete; DiagnosticsIpc(DiagnosticsIpc &&src) = delete; @@ -79,10 +81,11 @@ class IpcStream final : _clientSocket(clientSocket), _mode(mode) {} #else HANDLE _hPipe = INVALID_HANDLE_VALUE; - IpcStream(HANDLE hPipe, ConnectionMode mode = ConnectionMode::SERVER) : _hPipe(hPipe), _mode(mode) {} + OVERLAPPED _oOverlap = {}; + IpcStream(HANDLE hPipe, DiagnosticsIpc::ConnectionMode mode = DiagnosticsIpc::ConnectionMode::SERVER); #endif /* TARGET_UNIX */ - ConnectionMode _mode; + DiagnosticsIpc::ConnectionMode _mode; IpcStream() = delete; IpcStream(const IpcStream &src) = delete; diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index d45b8eef58679c..678e6f448479db 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -48,7 +48,9 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) while (!s_shuttingDown) { // FIXME: Ideally this would be something like a std::shared_ptr - IpcStream *pStream = s_pIpc->Accept(true, LoggingCallback); + IpcStream *pStream = s_pIpc->Accept(false, LoggingCallback); + + pStream = IpcStream::Select(&pStream, 1, LoggingCallback); if (pStream == nullptr) continue; @@ -149,8 +151,10 @@ bool DiagnosticServer::Initialize() } } + // TODO: Optionally block until connection with client mode is complete + // TODO: Should we handle/assert that (s_pIpc == nullptr)? - s_pIpc = IpcStream::DiagnosticsIpc::Create(address, ErrorCallback); + s_pIpc = IpcStream::DiagnosticsIpc::Create(address, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, ErrorCallback); if (s_pIpc != nullptr) { From 4b97ca88c81a5f3902675f4f6f9a4bec187fee94 Mon Sep 17 00:00:00 2001 From: John Salem Date: Wed, 4 Mar 2020 11:41:22 -0800 Subject: [PATCH 04/52] Implement select for unix --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 86 ++++++++++++++++++- src/coreclr/src/debug/inc/diagnosticsipc.h | 5 +- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index e19bd7ee6bda28..a44c84ba34394d 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include "diagnosticsipc.h" @@ -127,7 +128,7 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p return new IpcStream::DiagnosticsIpc(serverSocket, &serverAddress); } -IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) const +IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) { sockaddr_un clientAddress{}; clientAddress.sun_family = AF_UNIX; @@ -148,7 +149,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) const // TODO: Anything else? } - return new IpcStream(clientSocket, ServerMode::CLIENT); + return new IpcStream(clientSocket, -1, ConnectionMode::CLIENT); } IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback callback) const @@ -163,7 +164,86 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal return nullptr; } - return new IpcStream(shouldBlock ? clientSocket : _serverSocket); + return new IpcStream(clientSocket, _serverSocket); +} + +IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallback callback) +{ + // build FD_SET + fd_set readSet; + FD_ZERO(&readSet); + + int maxFd = -1; + for (int i = 0; i < nStreams; i++) + { + int fd = -1; + if (pStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && pStreams[i]->_clientSocket == -1) + { + fd = pStreams[i]->_serverSocket; + } + else + { + fd = pStreams[i]->_clientSocket; + } + + maxFd = (maxFd > fd) ? maxFd : fd; + FD_SET(fd, &readSet); + } + maxFd++; // needs to be 1 more than max FD + + // call select + int retval = select(maxFd, &readSet, NULL, NULL, NULL); + + // check for errors + if (retval == -1) + { + if (callback != nullptr) + callback(strerror(errno), errno); + return nullptr; + } + + // determine which FD signalled + // - decide on policy for which gets checked first so we don't starve one connection + IpcStream *pStream = nullptr; + for (int i = 0; i < nStreams; i++) + { + int fd = -1; + bool needToAccept = pStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && pStreams[i]->_clientSocket == -1; + if (needToAccept) + { + fd = pStreams[i]->_serverSocket; + } + else + { + fd = pStreams[i]->_clientSocket; + } + + if (FD_ISSET(fd, &readSet)) + { + if (needToAccept) + { + sockaddr_un from; + socklen_t fromlen = sizeof(from); + const int clientSocket = ::accept(pStreams[i]->_serverSocket, (sockaddr *)&from, &fromlen); + if (clientSocket == -1) + { + if (callback != nullptr) + callback(strerror(errno), errno); + return nullptr; + } + pStream = new IpcStream(clientSocket, pStreams[i]->_serverSocket, pStreams[i]->_mode); + } + else + { + pStream = pStreams[i]; + } + break; + } + } + + // return the correct IpcStream + _ASSERTE(pStream != nullptr); + return pStream; } void IpcStream::DiagnosticsIpc::Close(ErrorCallback callback) diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index c71851604f8177..316e8d5a90d10a 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -77,8 +77,9 @@ class IpcStream final private: #ifdef TARGET_UNIX int _clientSocket = -1; - IpcStream(int clientSocket, ConnectionMode mode = ConnectionMode::SERVER) - : _clientSocket(clientSocket), _mode(mode) {} + int _serverSocket = -1; + IpcStream(int clientSocket, int serverSocket, DiagnosticsIpc::ConnectionMode mode = DiagnosticsIpc::ConnectionMode::SERVER) + : _clientSocket(clientSocket), _serverSocket(serverSocket), _mode(mode) {} #else HANDLE _hPipe = INVALID_HANDLE_VALUE; OVERLAPPED _oOverlap = {}; From 0cb1e886e579deb5dd39068d80f7a460c51796cf Mon Sep 17 00:00:00 2001 From: John Salem Date: Thu, 5 Mar 2020 14:12:58 -0800 Subject: [PATCH 05/52] Update diagnostics server to use both modes * Change DOTNET_DiagnosticsServerAddress to DOTNET_DiagnosticsClientModeAddress * works for original connection mode * untested for client mode --- src/coreclr/src/inc/clrconfigvalues.h | 2 +- src/coreclr/src/vm/diagnosticserver.cpp | 43 ++++++++++++++++++------- src/coreclr/src/vm/diagnosticserver.h | 3 +- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/coreclr/src/inc/clrconfigvalues.h b/src/coreclr/src/inc/clrconfigvalues.h index fc711dc2f18c9e..48d3ba7a1d40ba 100644 --- a/src/coreclr/src/inc/clrconfigvalues.h +++ b/src/coreclr/src/inc/clrconfigvalues.h @@ -732,7 +732,7 @@ RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeProcNumbers, W("EventPipeProcNumbers" // // Diagnostics Server // -RETAIL_CONFIG_STRING_INFO_EX(EXTERNAL_DOTNET_DiagnosticsServerAddress, W("DOTNET_DiagnosticsServerAddress"), "The full path including filename for the OS transport (NamedPipe on Windows; Unix Domain Socket on Linux) to be used by the Diagnostics Server", CLRConfig::DontPrependCOMPlus_); +RETAIL_CONFIG_STRING_INFO_EX(EXTERNAL_DOTNET_DiagnosticsClientModeAddress, W("DOTNET_DiagnosticsClientModeAddress"), "The full path for the OS transport (NamedPipe on Windows; Unix Domain Socket on Linux) to be used by the Diagnostics Server in client mode", CLRConfig::DontPrependCOMPlus_); // // LTTng diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index 678e6f448479db..0a228680af5d98 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -19,7 +19,8 @@ #ifdef FEATURE_PERFTRACING -IpcStream::DiagnosticsIpc *DiagnosticServer::s_pIpc = nullptr; +IpcStream::DiagnosticsIpc *DiagnosticServer::s_pServerIpc = nullptr; +IpcStream::DiagnosticsIpc *DiagnosticServer::s_pClientIpc = nullptr; Volatile DiagnosticServer::s_shuttingDown(false); DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) @@ -33,7 +34,7 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) } CONTRACTL_END; - if (s_pIpc == nullptr) + if (s_pServerIpc == nullptr) { STRESS_LOG0(LF_DIAGNOSTICS_PORT, LL_ERROR, "Diagnostics IPC listener was undefined\n"); return 1; @@ -45,12 +46,18 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) EX_TRY { + int nIpcs = (s_pClientIpc == nullptr) ? 1 : 2; + NewArrayHolder pListeningIpcs = new IpcStream*[nIpcs]; while (!s_shuttingDown) { // FIXME: Ideally this would be something like a std::shared_ptr - IpcStream *pStream = s_pIpc->Accept(false, LoggingCallback); + pListeningIpcs[0] = s_pServerIpc->Accept(false, LoggingCallback); - pStream = IpcStream::Select(&pStream, 1, LoggingCallback); + // TODO: this should loop till connected + if (s_pClientIpc != nullptr) + pListeningIpcs[1] = s_pClientIpc->Connect(LoggingCallback); + + IpcStream *pStream = IpcStream::Select(pListeningIpcs, nIpcs, LoggingCallback); if (pStream == nullptr) continue; @@ -138,7 +145,7 @@ bool DiagnosticServer::Initialize() }; NewArrayHolder address = nullptr; - CLRConfigStringHolder wAddress = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_DOTNET_DiagnosticsServerAddress); + CLRConfigStringHolder wAddress = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_DOTNET_DiagnosticsClientModeAddress); int nCharactersWritten = 0; if (wAddress != nullptr) { @@ -149,14 +156,21 @@ bool DiagnosticServer::Initialize() nCharactersWritten = WideCharToMultiByte(CP_UTF8, 0, wAddress, -1, address, nCharactersWritten, NULL, NULL); assert(nCharactersWritten != 0); } + + // Create the clint mode connection + s_pClientIpc = IpcStream::DiagnosticsIpc::Create(address, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, ErrorCallback); } + s_pServerIpc = IpcStream::DiagnosticsIpc::Create(nullptr, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, ErrorCallback); + + // TODO: Error check the constructor + // TODO: Optionally block until connection with client mode is complete // TODO: Should we handle/assert that (s_pIpc == nullptr)? - s_pIpc = IpcStream::DiagnosticsIpc::Create(address, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, ErrorCallback); + // s_pIpc = IpcStream::DiagnosticsIpc::Create(address, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, ErrorCallback); - if (s_pIpc != nullptr) + if (s_pServerIpc != nullptr) { #ifdef FEATURE_AUTO_TRACE auto_trace_init(); @@ -167,14 +181,20 @@ bool DiagnosticServer::Initialize() nullptr, // no security attribute 0, // default stack size DiagnosticsServerThread, // thread proc - (LPVOID)s_pIpc, // thread parameter + (LPVOID)s_pServerIpc, // thread parameter 0, // not suspended &dwThreadId); // returns thread ID if (hServerThread == NULL) { - delete s_pIpc; - s_pIpc = nullptr; + delete s_pServerIpc; + s_pServerIpc = nullptr; + + if (s_pClientIpc != nullptr) + { + delete s_pClientIpc; + s_pClientIpc = nullptr; + } // Failed to create IPC thread. STRESS_LOG1( @@ -219,7 +239,7 @@ bool DiagnosticServer::Shutdown() EX_TRY { - if (s_pIpc != nullptr) + if (s_pServerIpc != nullptr) { auto ErrorCallback = [](const char *szMessage, uint32_t code) { STRESS_LOG2( @@ -229,7 +249,6 @@ bool DiagnosticServer::Shutdown() code, // data1 szMessage); // data2 }; - s_pIpc->Close(ErrorCallback); // This will break the accept waiting for client connection. } fSuccess = true; } diff --git a/src/coreclr/src/vm/diagnosticserver.h b/src/coreclr/src/vm/diagnosticserver.h index 393fbda0bd9ae2..3cdae445e88126 100644 --- a/src/coreclr/src/vm/diagnosticserver.h +++ b/src/coreclr/src/vm/diagnosticserver.h @@ -46,7 +46,8 @@ class DiagnosticServer final static DWORD WINAPI DiagnosticsServerThread(LPVOID lpThreadParameter); private: - static IpcStream::DiagnosticsIpc *s_pIpc; + static IpcStream::DiagnosticsIpc *s_pServerIpc; + static IpcStream::DiagnosticsIpc *s_pClientIpc; static Volatile s_shuttingDown; }; From dbc962405812b0cb52dda2462d2777b1a835919b Mon Sep 17 00:00:00 2001 From: John Salem Date: Tue, 10 Mar 2020 16:15:33 -0700 Subject: [PATCH 06/52] Add DiagnosticsIpcFactory abstraction --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 4 +- .../debug/debug-pal/win/diagnosticsipc.cpp | 14 ++-- src/coreclr/src/debug/inc/diagnosticsipc.h | 4 +- src/coreclr/src/inc/corhlprpriv.h | 1 + src/coreclr/src/vm/CMakeLists.txt | 2 + src/coreclr/src/vm/diagnosticserver.cpp | 43 +++--------- src/coreclr/src/vm/diagnosticserver.h | 3 +- src/coreclr/src/vm/diagnosticsipcfactory.cpp | 66 +++++++++++++++++++ src/coreclr/src/vm/diagnosticsipcfactory.h | 22 +++++++ src/coreclr/src/vm/diagnosticsprotocol.h | 49 ++++++++++++++ 10 files changed, 161 insertions(+), 47 deletions(-) create mode 100644 src/coreclr/src/vm/diagnosticsipcfactory.cpp create mode 100644 src/coreclr/src/vm/diagnosticsipcfactory.h diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index a44c84ba34394d..df3e57d36966ba 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -14,10 +14,10 @@ #include "processdescriptor.h" IpcStream::DiagnosticsIpc::DiagnosticsIpc(const int serverSocket, sockaddr_un *const pServerAddress, ConnectionMode mode) : + mode(mode), _serverSocket(serverSocket), _pServerAddress(new sockaddr_un), - _isClosed(false), - _mode(mode) + _isClosed(false) { _ASSERTE(_pServerAddress != nullptr); _ASSERTE(_serverSocket != -1); diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index fefd7ba939328a..6109e4502aa748 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -10,7 +10,7 @@ #define _ASSERTE assert IpcStream::DiagnosticsIpc::DiagnosticsIpc(const char(&namedPipeName)[MaxNamedPipeNameLength], ConnectionMode mode) - : _mode(mode) + : mode(mode) { memcpy(_pNamedPipeName, namedPipeName, sizeof(_pNamedPipeName)); } @@ -60,8 +60,8 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback callback) const { - _ASSERTE(_mode == ConnectionMode::SERVER); - if (_mode != ConnectionMode::SERVER) + _ASSERTE(mode == ConnectionMode::SERVER); + if (mode != ConnectionMode::SERVER) { if (callback != nullptr) callback("Cannot call accept on a client connection", 0); @@ -92,7 +92,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal // TODO: Find a better way to do this than // mixing abstractions - IpcStream *pStream = new IpcStream(hPipe, _mode); + IpcStream *pStream = new IpcStream(hPipe, mode); BOOL fSuccess = ::ConnectNamedPipe(hPipe, &pStream->_oOverlap) != 0; if (!fSuccess) @@ -131,8 +131,8 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) { - _ASSERTE(_mode == ConnectionMode::CLIENT); - if (_mode != ConnectionMode::CLIENT) + _ASSERTE(mode == ConnectionMode::CLIENT); + if (mode != ConnectionMode::CLIENT) { if (callback != nullptr) callback("Cannot call connect on a client connection", 0); @@ -156,7 +156,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) return nullptr; } - return new IpcStream(hPipe, _mode); + return new IpcStream(hPipe, mode); } void IpcStream::DiagnosticsIpc::Close(ErrorCallback) diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index 316e8d5a90d10a..ed0f4ba3a179c6 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -33,6 +33,8 @@ class IpcStream final SERVER }; + ConnectionMode mode; + ~DiagnosticsIpc(); //! Creates an IPC object @@ -65,8 +67,6 @@ class IpcStream final DiagnosticsIpc(const char(&namedPipeName)[MaxNamedPipeNameLength], ConnectionMode mode = ConnectionMode::SERVER); #endif /* TARGET_UNIX */ - ConnectionMode _mode; - DiagnosticsIpc() = delete; DiagnosticsIpc(const DiagnosticsIpc &src) = delete; DiagnosticsIpc(DiagnosticsIpc &&src) = delete; diff --git a/src/coreclr/src/inc/corhlprpriv.h b/src/coreclr/src/inc/corhlprpriv.h index 8fcafd08d93c34..7b9e5f1f885677 100644 --- a/src/coreclr/src/inc/corhlprpriv.h +++ b/src/coreclr/src/inc/corhlprpriv.h @@ -507,6 +507,7 @@ class CQuickArrayList : protected CQuickArray using CQuickArray::AllocNoThrow; using CQuickArray::ReSizeNoThrow; using CQuickArray::MaxSize; + using CQuickArray::Ptr; CQuickArrayList() : m_curSize(0) diff --git a/src/coreclr/src/vm/CMakeLists.txt b/src/coreclr/src/vm/CMakeLists.txt index 2b4d3df8555c75..11fdcebf5e75b3 100644 --- a/src/coreclr/src/vm/CMakeLists.txt +++ b/src/coreclr/src/vm/CMakeLists.txt @@ -320,6 +320,7 @@ set(VM_SOURCES_WKS custommarshalerinfo.cpp autotrace.cpp diagnosticserver.cpp + diagnosticsipcfactory.cpp diagnosticsserverprotocolhelper.cpp dllimportcallback.cpp eeconfig.cpp @@ -437,6 +438,7 @@ set(VM_HEADERS_WKS custommarshalerinfo.h autotrace.h diagnosticserver.h + diagnosticsipcfactory.h diagnosticsserverprotocolhelper.h diagnosticsprotocol.h dllimportcallback.h diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index 0a228680af5d98..df7b3a025d124e 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -4,6 +4,7 @@ #include "common.h" #include "diagnosticserver.h" +#include "diagnosticsipcfactory.h" #include "eventpipeprotocolhelper.h" #include "dumpdiagnosticprotocolhelper.h" #include "profilerdiagnosticprotocolhelper.h" @@ -19,9 +20,8 @@ #ifdef FEATURE_PERFTRACING -IpcStream::DiagnosticsIpc *DiagnosticServer::s_pServerIpc = nullptr; -IpcStream::DiagnosticsIpc *DiagnosticServer::s_pClientIpc = nullptr; Volatile DiagnosticServer::s_shuttingDown(false); +CQuickArrayList DiagnosticServer::s_rgIpcs = CQuickArrayList(); DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) { @@ -34,7 +34,7 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) } CONTRACTL_END; - if (s_pServerIpc == nullptr) + if (s_rgIpcs.Size() == 0) { STRESS_LOG0(LF_DIAGNOSTICS_PORT, LL_ERROR, "Diagnostics IPC listener was undefined\n"); return 1; @@ -46,18 +46,9 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) EX_TRY { - int nIpcs = (s_pClientIpc == nullptr) ? 1 : 2; - NewArrayHolder pListeningIpcs = new IpcStream*[nIpcs]; while (!s_shuttingDown) { - // FIXME: Ideally this would be something like a std::shared_ptr - pListeningIpcs[0] = s_pServerIpc->Accept(false, LoggingCallback); - - // TODO: this should loop till connected - if (s_pClientIpc != nullptr) - pListeningIpcs[1] = s_pClientIpc->Connect(LoggingCallback); - - IpcStream *pStream = IpcStream::Select(pListeningIpcs, nIpcs, LoggingCallback); + IpcStream *pStream = DiagnosticsIpcFactory::GetNextConnectedStream(s_rgIpcs.Ptr(), s_rgIpcs.Size(), LoggingCallback); if (pStream == nullptr) continue; @@ -158,19 +149,12 @@ bool DiagnosticServer::Initialize() } // Create the clint mode connection - s_pClientIpc = IpcStream::DiagnosticsIpc::Create(address, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, ErrorCallback); + s_rgIpcs.Push(DiagnosticsIpcFactory::CreateClient(address, ErrorCallback)); } - s_pServerIpc = IpcStream::DiagnosticsIpc::Create(nullptr, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, ErrorCallback); - - // TODO: Error check the constructor - - // TODO: Optionally block until connection with client mode is complete + s_rgIpcs.Push(DiagnosticsIpcFactory::CreateServer(nullptr, ErrorCallback)); - // TODO: Should we handle/assert that (s_pIpc == nullptr)? - // s_pIpc = IpcStream::DiagnosticsIpc::Create(address, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, ErrorCallback); - - if (s_pServerIpc != nullptr) + if (s_rgIpcs.Ptr() != nullptr) { #ifdef FEATURE_AUTO_TRACE auto_trace_init(); @@ -181,21 +165,12 @@ bool DiagnosticServer::Initialize() nullptr, // no security attribute 0, // default stack size DiagnosticsServerThread, // thread proc - (LPVOID)s_pServerIpc, // thread parameter + (LPVOID)s_rgIpcs.Ptr(), // thread parameter 0, // not suspended &dwThreadId); // returns thread ID if (hServerThread == NULL) { - delete s_pServerIpc; - s_pServerIpc = nullptr; - - if (s_pClientIpc != nullptr) - { - delete s_pClientIpc; - s_pClientIpc = nullptr; - } - // Failed to create IPC thread. STRESS_LOG1( LF_DIAGNOSTICS_PORT, // facility @@ -239,7 +214,7 @@ bool DiagnosticServer::Shutdown() EX_TRY { - if (s_pServerIpc != nullptr) + if (s_rgIpcs.Ptr() != nullptr) { auto ErrorCallback = [](const char *szMessage, uint32_t code) { STRESS_LOG2( diff --git a/src/coreclr/src/vm/diagnosticserver.h b/src/coreclr/src/vm/diagnosticserver.h index 3cdae445e88126..2062a24e10ac17 100644 --- a/src/coreclr/src/vm/diagnosticserver.h +++ b/src/coreclr/src/vm/diagnosticserver.h @@ -46,8 +46,7 @@ class DiagnosticServer final static DWORD WINAPI DiagnosticsServerThread(LPVOID lpThreadParameter); private: - static IpcStream::DiagnosticsIpc *s_pServerIpc; - static IpcStream::DiagnosticsIpc *s_pClientIpc; + static CQuickArrayList s_rgIpcs; static Volatile s_shuttingDown; }; diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.cpp b/src/coreclr/src/vm/diagnosticsipcfactory.cpp new file mode 100644 index 00000000000000..f305267eeb67c9 --- /dev/null +++ b/src/coreclr/src/vm/diagnosticsipcfactory.cpp @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "common.h" +#include "diagnosticsprotocol.h" +#include "diagnosticsipcfactory.h" + +#ifdef FEATURE_PERFTRACING + +IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateServer(const char *const pIpcName, ErrorCallback callback) +{ + return IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, callback); +} + +IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateClient(const char *const pIpcName, ErrorCallback callback) +{ + return IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); +} + +IpcStream *DiagnosticsIpcFactory::GetNextConnectedStream(IpcStream::DiagnosticsIpc **pIpcs, uint32_t nIpcs, ErrorCallback callback) +{ + IpcStream *pStreams[nIpcs]; + for (int i = 0; i < nIpcs; i++) + { + if (pIpcs[i]->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) + { + // TODO: Should we loop here to ensure connection? + pStreams[i] = pIpcs[i]->Connect(callback); + if (pStreams[i] != nullptr) + { + uint8_t advertiseBuffer[18]; + if (!DiagnosticsIpc::PopulateIpcAdvertisePayload_V1(advertiseBuffer)) + { + if (callback != nullptr) + callback("Unable to generate Advertise Buffer", -1); + return nullptr; + } + + uint32_t nBytesWritten = 0; + if (!pStreams[i]->Write(advertiseBuffer, sizeof(advertiseBuffer), nBytesWritten)) + { + if (callback != nullptr) + callback("Unable to send Advertise message", -1); + return nullptr; + } + _ASSERTE(nBytesWritten == sizeof(advertiseBuffer)); + } + } + else + { + pStreams[i] = pIpcs[i]->Accept(false, callback); + } + + if (pStreams[i] == nullptr) + { + if (callback != nullptr) + callback("Unable to establish stream", -1); + return nullptr; + } + } + + return IpcStream::Select(pStreams, nIpcs, callback); +} + +#endif // FEATURE_PERFTRACING \ No newline at end of file diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.h b/src/coreclr/src/vm/diagnosticsipcfactory.h new file mode 100644 index 00000000000000..630394860b0108 --- /dev/null +++ b/src/coreclr/src/vm/diagnosticsipcfactory.h @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __DIAGNOSTICS_IPC_FACTORY_H__ +#define __DIAGNOSTICS_IPC_FACTORY_H__ + +#ifdef FEATURE_PERFTRACING + +#include "diagnosticsipc.h" + +class DiagnosticsIpcFactory +{ +public: + static IpcStream::DiagnosticsIpc *CreateServer(const char *const pIpcName, ErrorCallback = nullptr); + static IpcStream::DiagnosticsIpc *CreateClient(const char *const pIpcName, ErrorCallback = nullptr); + static IpcStream *GetNextConnectedStream(IpcStream::DiagnosticsIpc **pIpcs, uint32_t nIpcs, ErrorCallback = nullptr); +}; + +#endif // FEATURE_PERFTRACING + +#endif // __DIAGNOSTICS_IPC_FACTORY_H__ \ No newline at end of file diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index 5ef3c5561e89ee..009b32e3f17262 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -57,6 +57,23 @@ bool TryParseString(uint8_t *&bufferCursor, uint32_t &bufferLen, const T *&resul return true; } +template +bool TryWriteNumberLittleEndian(uint8_t *&bufferCursor, uint32_t &bufferLen, const T &value) +{ + static_assert(std::is_integral::value, "Can only write integral types"); + + if (bufferLen < sizeof(value)) + return false; + + for (int i = 0; i < sizeof(value); i++) + { + *bufferCursor++ = (value >> (i * 8)) & 0xFF; + bufferLen += 8; + } + + return true; +} + namespace DiagnosticsIpc { enum class IpcMagicVersion : uint8_t @@ -104,6 +121,38 @@ namespace DiagnosticsIpc const MagicVersion DotnetIpcMagic_V1 = { "DOTNET_IPC_V1" }; + /** + * ==ADVERTISE PROTOCOL== + * Before standard IPC Protocol communication can occur on a client-mode connection + * the runtime must advertise itself over the connection. ALL SUBSEQUENT COMMUNICATION + * IS STANDARD DIAGNOSTICS IPC PROTOCOL COMMUNICATION. + * + * The flow for Advertise is a one-way burst of 24 bytes consisting of + * 6 bytes - "AD_V1\0" (ASCII chars + null byte) + * 4 bytes - CLR Instance ID (little-endian) + * 8 bytes - PID (little-endian) + */ + + const uint8_t AdvertiseMagic_V1[6] = "AD_V1"; + + inline bool PopulateIpcAdvertisePayload_V1(uint8_t (&buf)[18]) + { + uint16_t clrInstanceId = GetClrInstanceId(); + uint64_t pid = GetCurrentProcessId(); + uint8_t *bufferCursor = &buf[0]; + uint32_t bufferLen = sizeof(buf); + + for (int i = 0; i < sizeof(AdvertiseMagic_V1); i++) + if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, AdvertiseMagic_V1[i])) + return false; + + if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, clrInstanceId) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, pid)) + return false; + + return true; + } + const IpcHeader GenericSuccessHeader = { { DotnetIpcMagic_V1 }, From 52cd9b030e4dad6edea6aff4093bf4c02a7b6511 Mon Sep 17 00:00:00 2001 From: John Salem Date: Tue, 10 Mar 2020 17:48:43 -0700 Subject: [PATCH 07/52] Fix precondition * fix array alloc * properly cast array size --- .../src/debug/debug-pal/unix/diagnosticsipc.cpp | 4 ++-- src/coreclr/src/vm/diagnosticserver.cpp | 15 +++++++++++---- src/coreclr/src/vm/diagnosticsipcfactory.cpp | 10 +++++----- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index df3e57d36966ba..a7e30815942b85 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -174,7 +174,7 @@ IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallb FD_ZERO(&readSet); int maxFd = -1; - for (int i = 0; i < nStreams; i++) + for (uint32_t i = 0; i < nStreams; i++) { int fd = -1; if (pStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && pStreams[i]->_clientSocket == -1) @@ -205,7 +205,7 @@ IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallb // determine which FD signalled // - decide on policy for which gets checked first so we don't starve one connection IpcStream *pStream = nullptr; - for (int i = 0; i < nStreams; i++) + for (uint32_t i = 0; i < nStreams; i++) { int fd = -1; bool needToAccept = pStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && pStreams[i]->_clientSocket == -1; diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index df7b3a025d124e..f1d78da6aafeb7 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -30,7 +30,7 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; - PRECONDITION(s_pIpc != nullptr); + PRECONDITION(s_rgIpcs.Size() != 0); } CONTRACTL_END; @@ -48,7 +48,7 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) { while (!s_shuttingDown) { - IpcStream *pStream = DiagnosticsIpcFactory::GetNextConnectedStream(s_rgIpcs.Ptr(), s_rgIpcs.Size(), LoggingCallback); + IpcStream *pStream = DiagnosticsIpcFactory::GetNextConnectedStream(s_rgIpcs.Ptr(), (uint32_t)s_rgIpcs.Size(), LoggingCallback); if (pStream == nullptr) continue; @@ -154,7 +154,7 @@ bool DiagnosticServer::Initialize() s_rgIpcs.Push(DiagnosticsIpcFactory::CreateServer(nullptr, ErrorCallback)); - if (s_rgIpcs.Ptr() != nullptr) + if (s_rgIpcs.Size() != 0) { #ifdef FEATURE_AUTO_TRACE auto_trace_init(); @@ -171,6 +171,10 @@ bool DiagnosticServer::Initialize() if (hServerThread == NULL) { + for (int i = 0; i < s_rgIpcs.Size(); i++) + if (s_rgIpcs[i] != nullptr) + delete s_rgIpcs[i]; + // Failed to create IPC thread. STRESS_LOG1( LF_DIAGNOSTICS_PORT, // facility @@ -214,7 +218,7 @@ bool DiagnosticServer::Shutdown() EX_TRY { - if (s_rgIpcs.Ptr() != nullptr) + if (s_rgIpcs.Size() != 0) { auto ErrorCallback = [](const char *szMessage, uint32_t code) { STRESS_LOG2( @@ -224,6 +228,9 @@ bool DiagnosticServer::Shutdown() code, // data1 szMessage); // data2 }; + + for (int i = 0; i < s_rgIpcs.Size(); i++) + s_rgIpcs[i]->Close(ErrorCallback); } fSuccess = true; } diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.cpp b/src/coreclr/src/vm/diagnosticsipcfactory.cpp index f305267eeb67c9..2ac808a2d258a6 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.cpp +++ b/src/coreclr/src/vm/diagnosticsipcfactory.cpp @@ -20,13 +20,13 @@ IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateClient(const char *const IpcStream *DiagnosticsIpcFactory::GetNextConnectedStream(IpcStream::DiagnosticsIpc **pIpcs, uint32_t nIpcs, ErrorCallback callback) { - IpcStream *pStreams[nIpcs]; - for (int i = 0; i < nIpcs; i++) + CQuickArrayList pStreams; + for (uint64_t i = 0; i < nIpcs; i++) { if (pIpcs[i]->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) { // TODO: Should we loop here to ensure connection? - pStreams[i] = pIpcs[i]->Connect(callback); + pStreams.Push(pIpcs[i]->Connect(callback)); if (pStreams[i] != nullptr) { uint8_t advertiseBuffer[18]; @@ -49,7 +49,7 @@ IpcStream *DiagnosticsIpcFactory::GetNextConnectedStream(IpcStream::DiagnosticsI } else { - pStreams[i] = pIpcs[i]->Accept(false, callback); + pStreams.Push(pIpcs[i]->Accept(false, callback)); } if (pStreams[i] == nullptr) @@ -60,7 +60,7 @@ IpcStream *DiagnosticsIpcFactory::GetNextConnectedStream(IpcStream::DiagnosticsI } } - return IpcStream::Select(pStreams, nIpcs, callback); + return IpcStream::Select(pStreams.Ptr(), nIpcs, callback); } #endif // FEATURE_PERFTRACING \ No newline at end of file From 39180608be90e7e525b8ba30859825ca5243bd2b Mon Sep 17 00:00:00 2001 From: John Salem Date: Wed, 11 Mar 2020 09:40:58 -0700 Subject: [PATCH 08/52] fix x86 build --- src/coreclr/src/vm/diagnosticserver.cpp | 4 ++-- src/coreclr/src/vm/diagnosticsipcfactory.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index f1d78da6aafeb7..80780fe70d4fb9 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -171,7 +171,7 @@ bool DiagnosticServer::Initialize() if (hServerThread == NULL) { - for (int i = 0; i < s_rgIpcs.Size(); i++) + for (uint32_t i = 0; i < s_rgIpcs.Size(); i++) if (s_rgIpcs[i] != nullptr) delete s_rgIpcs[i]; @@ -229,7 +229,7 @@ bool DiagnosticServer::Shutdown() szMessage); // data2 }; - for (int i = 0; i < s_rgIpcs.Size(); i++) + for (uint32_t i = 0; i < s_rgIpcs.Size(); i++) s_rgIpcs[i]->Close(ErrorCallback); } fSuccess = true; diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.cpp b/src/coreclr/src/vm/diagnosticsipcfactory.cpp index 2ac808a2d258a6..28435154131204 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.cpp +++ b/src/coreclr/src/vm/diagnosticsipcfactory.cpp @@ -21,7 +21,7 @@ IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateClient(const char *const IpcStream *DiagnosticsIpcFactory::GetNextConnectedStream(IpcStream::DiagnosticsIpc **pIpcs, uint32_t nIpcs, ErrorCallback callback) { CQuickArrayList pStreams; - for (uint64_t i = 0; i < nIpcs; i++) + for (uint32_t i = 0; i < nIpcs; i++) { if (pIpcs[i]->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) { From 33da551693bed04193a4002ce4ea3c2543da7f2e Mon Sep 17 00:00:00 2001 From: John Salem Date: Thu, 12 Mar 2020 15:33:25 -0700 Subject: [PATCH 09/52] Fixed errors --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 93 ++++++++++--------- src/coreclr/src/vm/diagnosticsipcfactory.cpp | 2 +- src/coreclr/src/vm/diagnosticsprotocol.h | 8 +- 3 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index a7e30815942b85..3b7053ae65c803 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include "diagnosticsipc.h" @@ -20,7 +20,6 @@ IpcStream::DiagnosticsIpc::DiagnosticsIpc(const int serverSocket, sockaddr_un *c _isClosed(false) { _ASSERTE(_pServerAddress != nullptr); - _ASSERTE(_serverSocket != -1); _ASSERTE(pServerAddress != nullptr); if (_pServerAddress == nullptr || pServerAddress == nullptr) @@ -125,7 +124,7 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p umask(prev_mask); #endif // __APPLE__ - return new IpcStream::DiagnosticsIpc(serverSocket, &serverAddress); + return new IpcStream::DiagnosticsIpc(serverSocket, &serverAddress, mode); } IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) @@ -156,7 +155,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal { sockaddr_un from; socklen_t fromlen = sizeof(from); - const int clientSocket = shouldBlock ? -1 : ::accept(_serverSocket, (sockaddr *)&from, &fromlen); + const int clientSocket = shouldBlock ? ::accept(_serverSocket, (sockaddr *)&from, &fromlen) : -1; if (shouldBlock && clientSocket == -1) { if (callback != nullptr) @@ -169,11 +168,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallback callback) { - // build FD_SET - fd_set readSet; - FD_ZERO(&readSet); - - int maxFd = -1; + pollfd *pollfds = new pollfd[nStreams]; for (uint32_t i = 0; i < nStreams; i++) { int fd = -1; @@ -186,63 +181,69 @@ IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallb fd = pStreams[i]->_clientSocket; } - maxFd = (maxFd > fd) ? maxFd : fd; - FD_SET(fd, &readSet); + pollfds[i].fd = fd; + pollfds[i].events = POLLIN; } - maxFd++; // needs to be 1 more than max FD - - // call select - int retval = select(maxFd, &readSet, NULL, NULL, NULL); - // check for errors - if (retval == -1) + int retval = poll(pollfds, nStreams, -1); // -1 = infinite + + if (retval <= 0) { - if (callback != nullptr) - callback(strerror(errno), errno); + for (uint32_t i = 0; i < nStreams; i++) + { + if ((pollfds[i].revents & POLLERR) && callback != nullptr) + callback(strerror(errno), errno); + } + delete[] pollfds; return nullptr; } - // determine which FD signalled - // - decide on policy for which gets checked first so we don't starve one connection IpcStream *pStream = nullptr; for (uint32_t i = 0; i < nStreams; i++) { - int fd = -1; - bool needToAccept = pStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && pStreams[i]->_clientSocket == -1; - if (needToAccept) + if (pollfds[i].revents != 0) { - fd = pStreams[i]->_serverSocket; + bool needToAccept = pStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && pStreams[i]->_clientSocket == -1; + if (pollfds[i].revents & POLLIN) + { + if (needToAccept) + { + sockaddr_un from; + socklen_t fromlen = sizeof(from); + const int clientSocket = ::accept(pStreams[i]->_serverSocket, (sockaddr *)&from, &fromlen); + if (clientSocket == -1) + { + if (callback != nullptr) + callback(strerror(errno), errno); + delete[] pollfds; + return nullptr; + } + pStream = new IpcStream(clientSocket, pStreams[i]->_serverSocket, pStreams[i]->_mode); + } + else + { + pStream = pStreams[i]; + } + break; + } } else { - fd = pStreams[i]->_clientSocket; - } - - if (FD_ISSET(fd, &readSet)) - { - if (needToAccept) + if ((pollfds[i].revents & POLLERR) && callback != nullptr) { - sockaddr_un from; - socklen_t fromlen = sizeof(from); - const int clientSocket = ::accept(pStreams[i]->_serverSocket, (sockaddr *)&from, &fromlen); - if (clientSocket == -1) - { - if (callback != nullptr) - callback(strerror(errno), errno); - return nullptr; - } - pStream = new IpcStream(clientSocket, pStreams[i]->_serverSocket, pStreams[i]->_mode); + callback("POLLERR", POLLERR); } - else + else if ((pollfds[i].revents & POLLNVAL) && callback != nullptr) { - pStream = pStreams[i]; + callback("POLLNVAL", POLLNVAL); } - break; + + delete[] pollfds; + return nullptr; } } - // return the correct IpcStream - _ASSERTE(pStream != nullptr); + delete[] pollfds; return pStream; } diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.cpp b/src/coreclr/src/vm/diagnosticsipcfactory.cpp index 28435154131204..896a0dc4a3d1ef 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.cpp +++ b/src/coreclr/src/vm/diagnosticsipcfactory.cpp @@ -29,7 +29,7 @@ IpcStream *DiagnosticsIpcFactory::GetNextConnectedStream(IpcStream::DiagnosticsI pStreams.Push(pIpcs[i]->Connect(callback)); if (pStreams[i] != nullptr) { - uint8_t advertiseBuffer[18]; + uint8_t advertiseBuffer[DiagnosticsIpc::AdvertiseSize]; if (!DiagnosticsIpc::PopulateIpcAdvertisePayload_V1(advertiseBuffer)) { if (callback != nullptr) diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index 009b32e3f17262..637ad18f65876f 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -68,7 +68,7 @@ bool TryWriteNumberLittleEndian(uint8_t *&bufferCursor, uint32_t &bufferLen, con for (int i = 0; i < sizeof(value); i++) { *bufferCursor++ = (value >> (i * 8)) & 0xFF; - bufferLen += 8; + bufferLen -= 1; } return true; @@ -129,13 +129,15 @@ namespace DiagnosticsIpc * * The flow for Advertise is a one-way burst of 24 bytes consisting of * 6 bytes - "AD_V1\0" (ASCII chars + null byte) - * 4 bytes - CLR Instance ID (little-endian) + * 2 bytes - CLR Instance ID (little-endian) * 8 bytes - PID (little-endian) */ const uint8_t AdvertiseMagic_V1[6] = "AD_V1"; - inline bool PopulateIpcAdvertisePayload_V1(uint8_t (&buf)[18]) + const uint32_t AdvertiseSize = 16; + + inline bool PopulateIpcAdvertisePayload_V1(uint8_t (&buf)[AdvertiseSize]) { uint16_t clrInstanceId = GetClrInstanceId(); uint64_t pid = GetCurrentProcessId(); From f7a28c4690b6a29c2b4a998c2a6175da2334fd25 Mon Sep 17 00:00:00 2001 From: John Salem Date: Fri, 13 Mar 2020 13:01:11 -0700 Subject: [PATCH 10/52] fix pipe connection on window --- src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 6109e4502aa748..7ace85115adc39 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -141,8 +141,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) HANDLE hPipe = ::CreateFileA( _pNamedPipeName, // pipe name - GENERIC_READ | // read and write access - GENERIC_WRITE, + PIPE_ACCESS_DUPLEX, // pipe access 0, // no sharing NULL, // default security attributes OPEN_EXISTING, // opens existing pipe From 22e44760ac9f6c78b2c27887517986e392bde922 Mon Sep 17 00:00:00 2001 From: John Salem Date: Fri, 13 Mar 2020 13:39:52 -0700 Subject: [PATCH 11/52] fix gcc build --- src/coreclr/src/vm/diagnosticsprotocol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index 637ad18f65876f..b2ec39927fa0dd 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -65,7 +65,7 @@ bool TryWriteNumberLittleEndian(uint8_t *&bufferCursor, uint32_t &bufferLen, con if (bufferLen < sizeof(value)) return false; - for (int i = 0; i < sizeof(value); i++) + for (uint32_t i = 0; i < sizeof(value); i++) { *bufferCursor++ = (value >> (i * 8)) & 0xFF; bufferLen -= 1; From 8204e70c7731aca6b5a1096b9f16c04cd45a1760 Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 16 Mar 2020 10:17:59 -0700 Subject: [PATCH 12/52] fix gcc build --- src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp | 7 ++++++- src/coreclr/src/vm/diagnosticsprotocol.h | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 3b7053ae65c803..9ce7108d715626 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -7,12 +7,17 @@ #include #include #include -#include #include #include #include "diagnosticsipc.h" #include "processdescriptor.h" +#if __GNUC__ + #include +#else + #include +#endif // __GNUC__ + IpcStream::DiagnosticsIpc::DiagnosticsIpc(const int serverSocket, sockaddr_un *const pServerAddress, ConnectionMode mode) : mode(mode), _serverSocket(serverSocket), diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index b2ec39927fa0dd..4ade6c600c4d82 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -144,7 +144,7 @@ namespace DiagnosticsIpc uint8_t *bufferCursor = &buf[0]; uint32_t bufferLen = sizeof(buf); - for (int i = 0; i < sizeof(AdvertiseMagic_V1); i++) + for (uint32_t i = 0; i < sizeof(AdvertiseMagic_V1); i++) if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, AdvertiseMagic_V1[i])) return false; From a74f8a3a859bc0c73dd2aa4ab089a41c3dce7f5a Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 16 Mar 2020 10:34:19 -0700 Subject: [PATCH 13/52] Change environment variable name --- src/coreclr/src/inc/clrconfigvalues.h | 2 +- src/coreclr/src/vm/diagnosticserver.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/src/inc/clrconfigvalues.h b/src/coreclr/src/inc/clrconfigvalues.h index 48d3ba7a1d40ba..f3fd7b9c0d8a69 100644 --- a/src/coreclr/src/inc/clrconfigvalues.h +++ b/src/coreclr/src/inc/clrconfigvalues.h @@ -732,7 +732,7 @@ RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeProcNumbers, W("EventPipeProcNumbers" // // Diagnostics Server // -RETAIL_CONFIG_STRING_INFO_EX(EXTERNAL_DOTNET_DiagnosticsClientModeAddress, W("DOTNET_DiagnosticsClientModeAddress"), "The full path for the OS transport (NamedPipe on Windows; Unix Domain Socket on Linux) to be used by the Diagnostics Server in client mode", CLRConfig::DontPrependCOMPlus_); +RETAIL_CONFIG_STRING_INFO_EX(EXTERNAL_DOTNET_DiagnosticsMonitorAddress, W("DOTNET_DiagnosticsMonitorAddress"), "NamedPipe path without '\\\\.\\pipe\\' on Windows; Full path of Unix Domain Socket on Linux/Unix. Used for Diagnostics Monitoring Agents.", CLRConfig::DontPrependCOMPlus_); // // LTTng diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index 80780fe70d4fb9..724022e32d53af 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -136,7 +136,7 @@ bool DiagnosticServer::Initialize() }; NewArrayHolder address = nullptr; - CLRConfigStringHolder wAddress = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_DOTNET_DiagnosticsClientModeAddress); + CLRConfigStringHolder wAddress = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_DOTNET_DiagnosticsMonitorAddress); int nCharactersWritten = 0; if (wAddress != nullptr) { From 6b1fe2ad65b49506c7daca24adc090a8e6689c8f Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 16 Mar 2020 10:38:49 -0700 Subject: [PATCH 14/52] Remove unnecessary protocol code --- src/coreclr/src/vm/CMakeLists.txt | 2 - src/coreclr/src/vm/diagnosticsprotocol.h | 1 - .../vm/diagnosticsserverprotocolhelper.cpp | 38 -------------- .../src/vm/diagnosticsserverprotocolhelper.h | 52 ------------------- 4 files changed, 93 deletions(-) delete mode 100644 src/coreclr/src/vm/diagnosticsserverprotocolhelper.cpp delete mode 100644 src/coreclr/src/vm/diagnosticsserverprotocolhelper.h diff --git a/src/coreclr/src/vm/CMakeLists.txt b/src/coreclr/src/vm/CMakeLists.txt index 11fdcebf5e75b3..57a9bd1088a6ca 100644 --- a/src/coreclr/src/vm/CMakeLists.txt +++ b/src/coreclr/src/vm/CMakeLists.txt @@ -321,7 +321,6 @@ set(VM_SOURCES_WKS autotrace.cpp diagnosticserver.cpp diagnosticsipcfactory.cpp - diagnosticsserverprotocolhelper.cpp dllimportcallback.cpp eeconfig.cpp eecontract.cpp @@ -439,7 +438,6 @@ set(VM_HEADERS_WKS autotrace.h diagnosticserver.h diagnosticsipcfactory.h - diagnosticsserverprotocolhelper.h diagnosticsprotocol.h dllimportcallback.h eeconfig.h diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index 4ade6c600c4d82..0c10a576e6dda6 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -96,7 +96,6 @@ namespace DiagnosticsIpc { OK = 0x00, Error = 0xFF, - Advertise = 0x01, }; struct MagicVersion diff --git a/src/coreclr/src/vm/diagnosticsserverprotocolhelper.cpp b/src/coreclr/src/vm/diagnosticsserverprotocolhelper.cpp deleted file mode 100644 index d560bd169af824..00000000000000 --- a/src/coreclr/src/vm/diagnosticsserverprotocolhelper.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#include "common.h" -#include "diagnosticsserverprotocolhelper.h" -#include "diagnosticsipc.h" -#include "diagnosticsprotocol.h" - -const DiagnosticsServerAdvertiseCommandPayload* DiagnosticsServerAdvertiseCommandPayload::TryParse(BYTE* lpBuffer, uint16_t& BufferSize) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_PREEMPTIVE; - PRECONDITION(lpBuffer != nullptr); - } - CONTRACTL_END; - - NewHolder payload = new (nothrow) DiagnosticsServerAdvertiseCommandPayload; - if (payload == nullptr) - { - // OOM - return nullptr; - } - - payload->incomingBuffer = lpBuffer; - uint8_t* pBufferCursor = payload->incomingBuffer; - uint32_t bufferLen = BufferSize; - if (!::TryParse(pBufferCursor, bufferLen, payload->pid) || - !::TryParse(pBufferCursor, bufferLen, payload->hash)) - { - return nullptr; - } - - return payload; -} \ No newline at end of file diff --git a/src/coreclr/src/vm/diagnosticsserverprotocolhelper.h b/src/coreclr/src/vm/diagnosticsserverprotocolhelper.h deleted file mode 100644 index 4d77c9d21597c1..00000000000000 --- a/src/coreclr/src/vm/diagnosticsserverprotocolhelper.h +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#ifndef __DIAGNOSTICSSERVER_PROTOCOL_HELPER_H__ -#define __DIAGNOSTICSSERVER_PROTOCOL_HELPER_H__ - -#ifdef FEATURE_PERFTRACING - -#include "common.h" -#include "diagnosticsipc.h" -#include "diagnosticsprotocol.h" - -class IpcStream; - -/** - * The Diagnostics Server command set is 0xFF - * see diagnosticsipc.h and diagnosticserver.h for more details - * enum class DiagnosticServerCommandId : uint8_t - * { - * OK = 0x00, - * Error = 0xFF, - * Advertise = 0x01, - * }; - */ - - -// Command = 0xFF01 -struct DiagnosticsServerAdvertiseCommandPayload -{ - NewArrayHolder incomingBuffer; - - // The protocol buffer is defined as: - // X, Y, Z means encode bytes for X followed by bytes for Y followed by bytes for Z - // - // PID = ulong - // hash = CLSID (GUID) - uint64_t pid; - CLSID hash; - static const DiagnosticsServerAdvertiseCommandPayload* TryParse(BYTE* lpBuffer, uint16_t& BufferSize); -}; - -class DiagnosticsServerProtocolHelper -{ -public: - // IPC event handlers. - static void HandleIpcMessage(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream); -}; - -#endif // FEATURE_PERFTRACING - -#endif // __DIAGNOSTICSSERVER_PROTOCOL_HELPER_H__ From c3d1bfd5b20f0bad1a82593cc2611fb8d96c522a Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 16 Mar 2020 18:16:00 -0700 Subject: [PATCH 15/52] Add retry semantics to reversed pipe * GetNextConnectedStream -> GetNextAvailableStream * Add caching mechanism for client connections that have been opened * Select->Poll * Untested on Windows --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 65 +++++---- .../debug/debug-pal/win/diagnosticsipc.cpp | 33 +++-- src/coreclr/src/debug/inc/diagnosticsipc.h | 12 +- src/coreclr/src/vm/diagnosticserver.cpp | 2 +- src/coreclr/src/vm/diagnosticsipcfactory.cpp | 124 ++++++++++++++---- src/coreclr/src/vm/diagnosticsipcfactory.h | 4 +- src/coreclr/src/vm/diagnosticsprotocol.h | 14 ++ 7 files changed, 185 insertions(+), 69 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 9ce7108d715626..603310be1206ce 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -171,28 +171,29 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal return new IpcStream(clientSocket, _serverSocket); } -IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallback callback) +int32_t IpcStream::Poll(IpcStream **ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback) { + *ppStream = nullptr; pollfd *pollfds = new pollfd[nStreams]; for (uint32_t i = 0; i < nStreams; i++) { int fd = -1; - if (pStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && pStreams[i]->_clientSocket == -1) + if (ppStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && ppStreams[i]->_clientSocket == -1) { - fd = pStreams[i]->_serverSocket; + fd = ppStreams[i]->_serverSocket; } else { - fd = pStreams[i]->_clientSocket; + fd = ppStreams[i]->_clientSocket; } pollfds[i].fd = fd; pollfds[i].events = POLLIN; } - int retval = poll(pollfds, nStreams, -1); // -1 = infinite + int retval = poll(pollfds, nStreams, timeoutMs); - if (retval <= 0) + if (retval < 0) { for (uint32_t i = 0; i < nStreams; i++) { @@ -200,56 +201,62 @@ IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallb callback(strerror(errno), errno); } delete[] pollfds; - return nullptr; + return -1; + } + else if (retval == 0) + { + // we timed out + delete[] pollfds; + return 0; } - IpcStream *pStream = nullptr; for (uint32_t i = 0; i < nStreams; i++) { if (pollfds[i].revents != 0) { - bool needToAccept = pStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && pStreams[i]->_clientSocket == -1; - if (pollfds[i].revents & POLLIN) + bool needToAccept = ppStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && ppStreams[i]->_clientSocket == -1; + // error check FIRST + if (pollfds[i].revents & POLLHUP) + { + // check for hangup first because a closed socket + // will technically meet the requirements for POLLIN + // i.e., a call to recv/read won't block + *ppStream = ppStreams[i]; + return -1; + } + else if ((pollfds[i].revents & (POLLERR|POLLNVAL))) + { + if (callback != nullptr) + callback("Poll error", (uint32_t)pollfds[i].revents); + return -1; + } + else if (pollfds[i].revents & POLLIN) { if (needToAccept) { sockaddr_un from; socklen_t fromlen = sizeof(from); - const int clientSocket = ::accept(pStreams[i]->_serverSocket, (sockaddr *)&from, &fromlen); + const int clientSocket = ::accept(ppStreams[i]->_serverSocket, (sockaddr *)&from, &fromlen); if (clientSocket == -1) { if (callback != nullptr) callback(strerror(errno), errno); delete[] pollfds; - return nullptr; + return -1; } - pStream = new IpcStream(clientSocket, pStreams[i]->_serverSocket, pStreams[i]->_mode); + *ppStream = new IpcStream(clientSocket, ppStreams[i]->_serverSocket, ppStreams[i]->_mode); } else { - pStream = pStreams[i]; + *ppStream = ppStreams[i]; } break; } } - else - { - if ((pollfds[i].revents & POLLERR) && callback != nullptr) - { - callback("POLLERR", POLLERR); - } - else if ((pollfds[i].revents & POLLNVAL) && callback != nullptr) - { - callback("POLLNVAL", POLLNVAL); - } - - delete[] pollfds; - return nullptr; - } } delete[] pollfds; - return pStream; + return 1; } void IpcStream::DiagnosticsIpc::Close(ErrorCallback callback) diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 7ace85115adc39..2139198ccbf271 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -187,19 +187,20 @@ IpcStream::~IpcStream() } } -IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallback callback) +int32_t IpcStream::Poll(IpcStream **ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback) { + *ppStream = nullptr; // load up an array of handles HANDLE *pHandles = new HANDLE[nStreams]; for (uint32_t i = 0; i < nStreams; i++) { - if (pStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER) + if (ppStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER) { - pHandles[i] = pStreams[i]->_oOverlap.hEvent; + pHandles[i] = ppStreams[i]->_oOverlap.hEvent; } else { - pHandles[i] = pStreams[i]->_hPipe; + pHandles[i] = ppStreams[i]->_hPipe; } } @@ -208,7 +209,14 @@ IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallb nStreams, // count pHandles, // handles false, // Don't wait all - INFINITE); // wait infinitely + timeoutMs); // wait infinitely + + if (dwWait == WAIT_TIMEOUT) + { + // we timed out + delete[] pHandles; + return 0; + } // determine which of the streams signaled DWORD index = dwWait - WAIT_OBJECT_0; @@ -216,11 +224,11 @@ IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallb { if (callback != nullptr) callback("Failed to select to named pipe.", ::GetLastError()); - delete pHandles; - return nullptr; + delete[] pHandles; + return -1; } - if (pStreams[index]->_mode == IpcStream::DiagnosticsIpc::ConnectionMode::SERVER) + if (ppStreams[index]->_mode == IpcStream::DiagnosticsIpc::ConnectionMode::SERVER) { // set that stream's mode to blocking bool result = SetNamedPipeHandleState( @@ -232,14 +240,15 @@ IpcStream *IpcStream::Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallb { if (callback != nullptr) callback("Failed to convert handle to wait mode", ::GetLastError()); - delete pHandles; - return nullptr; + delete[] pHandles; + return -1; } } // cleanup and return that stream - delete pHandles; - return pStreams[index]; + *ppStream = ppStreams[index]; + delete[] pHandles; + return 1; } bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) const diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index ed0f4ba3a179c6..574e7151b13990 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -22,7 +22,17 @@ class IpcStream final bool Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) const; bool Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) const; bool Flush() const; - static IpcStream *Select(IpcStream **pStreams, uint32_t nStreams, ErrorCallback callback = nullptr); + + // Poll + // Paramters: + // - IpcStream **pStreams: Array of pointers to IpcStreams to poll + // - uint32_t nStreams: The number of streams to poll + // - int32_t timeoutMs: The timeout in milliseconds for the poll (-1 == infinite) + // - IpcStream **pStream: OUT PARAMETER nullptr for timeout or error, signalled stream for successful poll + // Returns: + // int32_t: -1 on error, 0 on timeout, >0 on successful poll + // - if ppStream is != nullptr and -1 is returned, that connection was hungup and it shouldn't be treated as an error + static int32_t Poll(IpcStream **ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback = nullptr); class DiagnosticsIpc final { diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index 724022e32d53af..1107f27dd9cee6 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -48,7 +48,7 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) { while (!s_shuttingDown) { - IpcStream *pStream = DiagnosticsIpcFactory::GetNextConnectedStream(s_rgIpcs.Ptr(), (uint32_t)s_rgIpcs.Size(), LoggingCallback); + IpcStream *pStream = DiagnosticsIpcFactory::GetNextAvailableStream(s_rgIpcs.Ptr(), (uint32_t)s_rgIpcs.Size(), LoggingCallback); if (pStream == nullptr) continue; diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.cpp b/src/coreclr/src/vm/diagnosticsipcfactory.cpp index 896a0dc4a3d1ef..422ccd2ebf05fd 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.cpp +++ b/src/coreclr/src/vm/diagnosticsipcfactory.cpp @@ -8,6 +8,8 @@ #ifdef FEATURE_PERFTRACING +IpcStream **DiagnosticsIpcFactory::s_ppActiveConnections = nullptr; + IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateServer(const char *const pIpcName, ErrorCallback callback) { return IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, callback); @@ -18,49 +20,121 @@ IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateClient(const char *const return IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); } -IpcStream *DiagnosticsIpcFactory::GetNextConnectedStream(IpcStream::DiagnosticsIpc **pIpcs, uint32_t nIpcs, ErrorCallback callback) +// TODO: const x2, ppIpcs, log info on looping counts +IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc **ppIpcs, uint32_t nIpcs, ErrorCallback callback) { - CQuickArrayList pStreams; - for (uint32_t i = 0; i < nIpcs; i++) + // a static array that holds open client connections that haven't been used + // Remove entries from this list that have been used, e.g., they are placed in pStream and returned + // This will prevent the runtime from continually reestablishing connection when the server loop loops. + // This does, however, introduce state to this method which is undesireable, but a justifiable cost to minimizing system calls. + + if (s_ppActiveConnections == nullptr) + { + s_ppActiveConnections = new IpcStream*[nIpcs]; + memset(s_ppActiveConnections, 0, nIpcs * sizeof(IpcStream*)); + } + + // when we get a connection, put it in this list. If we use that connection, remove it. + IpcStream *pStream = nullptr; + + // Polling timeout semantics + // If client connection is opted in + // and connection succeeds => set timeout to max + // and connection fails => set timeout to minimum and scale by falloff factor + // else => set timeout to -1 (infinite) + // + // If an agent closes its socket while we're still connected, + // the max timeout is the amount of time it will take for us to notice + int32_t pollTimeoutFalloffFactor = 2; + int32_t pollTimeoutMinMs = 250; + int32_t pollTimeoutMs = -1; + int32_t pollTimeoutMaxMs = 30000; // 30s + uint32_t nPollAttempts = 0; + + while (pStream == nullptr) { - if (pIpcs[i]->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) + CQuickArrayList pStreams; + for (uint32_t i = 0; i < nIpcs; i++) { - // TODO: Should we loop here to ensure connection? - pStreams.Push(pIpcs[i]->Connect(callback)); - if (pStreams[i] != nullptr) + if (ppIpcs[i]->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) { - uint8_t advertiseBuffer[DiagnosticsIpc::AdvertiseSize]; - if (!DiagnosticsIpc::PopulateIpcAdvertisePayload_V1(advertiseBuffer)) + pollTimeoutMs = (pollTimeoutMs == -1) ? pollTimeoutMinMs : pollTimeoutMs; + if (s_ppActiveConnections[i] != nullptr) { - if (callback != nullptr) - callback("Unable to generate Advertise Buffer", -1); - return nullptr; + // check if still usable and then push it + // s_ppActiveConnections[i]->IsConnected(); ???? + pStreams.Push(s_ppActiveConnections[i]); + continue; } - uint32_t nBytesWritten = 0; - if (!pStreams[i]->Write(advertiseBuffer, sizeof(advertiseBuffer), nBytesWritten)) + // loop here + IpcStream *pConnection = nullptr; + pConnection = ppIpcs[i]->Connect(callback); + + if (pConnection != nullptr) + { + if (!DiagnosticsIpc::SendIpcAdvertise_V1(pConnection)) + { + if (callback != nullptr) + callback("Failed to send advertise message", -1); + // TODO: Should we just fall through instead and ignore the client conn? + return nullptr; + } + + // Add connection to list + s_ppActiveConnections[i] = pConnection; + pStreams.Push(pConnection); + pollTimeoutMs = pollTimeoutMaxMs; + } + else + { + pollTimeoutMs = (pollTimeoutMs > pollTimeoutMaxMs) ? + pollTimeoutMaxMs : + pollTimeoutMs * pollTimeoutFalloffFactor; + } + } + else + { + IpcStream *pServer = ppIpcs[i]->Accept(false, callback); + if (pServer == nullptr) { if (callback != nullptr) - callback("Unable to send Advertise message", -1); + callback("DiagnosticsServer failed to accept", -1); return nullptr; } - _ASSERTE(nBytesWritten == sizeof(advertiseBuffer)); + pStreams.Push(pServer); } } - else - { - pStreams.Push(pIpcs[i]->Accept(false, callback)); - } - if (pStreams[i] == nullptr) + int32_t retval = IpcStream::Poll(pStreams.Ptr(), (uint32_t)pStreams.Size(), pollTimeoutMs, &pStream, callback); + nPollAttempts++; + + if (retval < 0) { - if (callback != nullptr) - callback("Unable to establish stream", -1); - return nullptr; + if (pStream != nullptr) + { + // This stream was hung up + for (uint32_t i = 0; i < nIpcs; i++) + { + if (s_ppActiveConnections[i] == pStream) + { + s_ppActiveConnections[i] = nullptr; + delete pStream; + pStream = nullptr; + } + } + continue; + } + + // TODO: error handle here? } } - return IpcStream::Select(pStreams.Ptr(), nIpcs, callback); + // Clean the Active Connection Cache of a used connection + for (uint32_t i = 0; i < nIpcs; i++) + if (s_ppActiveConnections[i] == pStream) + s_ppActiveConnections[i] = nullptr; + return pStream; } #endif // FEATURE_PERFTRACING \ No newline at end of file diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.h b/src/coreclr/src/vm/diagnosticsipcfactory.h index 630394860b0108..7926682b2b8b85 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.h +++ b/src/coreclr/src/vm/diagnosticsipcfactory.h @@ -14,7 +14,9 @@ class DiagnosticsIpcFactory public: static IpcStream::DiagnosticsIpc *CreateServer(const char *const pIpcName, ErrorCallback = nullptr); static IpcStream::DiagnosticsIpc *CreateClient(const char *const pIpcName, ErrorCallback = nullptr); - static IpcStream *GetNextConnectedStream(IpcStream::DiagnosticsIpc **pIpcs, uint32_t nIpcs, ErrorCallback = nullptr); + static IpcStream *GetNextAvailableStream(IpcStream::DiagnosticsIpc **ppIpcs, uint32_t nIpcs, ErrorCallback = nullptr); +private: + static IpcStream **s_ppActiveConnections; }; #endif // FEATURE_PERFTRACING diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index 0c10a576e6dda6..92c3abd50d836c 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -154,6 +154,20 @@ namespace DiagnosticsIpc return true; } + inline bool SendIpcAdvertise_V1(IpcStream *pStream) + { + uint8_t advertiseBuffer[DiagnosticsIpc::AdvertiseSize]; + if (!DiagnosticsIpc::PopulateIpcAdvertisePayload_V1(advertiseBuffer)) + return false; + + uint32_t nBytesWritten = 0; + if (!pStream->Write(advertiseBuffer, sizeof(advertiseBuffer), nBytesWritten)) + return false; + + _ASSERTE(nBytesWritten == sizeof(advertiseBuffer)); + return nBytesWritten == sizeof(advertiseBuffer); + } + const IpcHeader GenericSuccessHeader = { { DotnetIpcMagic_V1 }, From 0045b1ccb8a09adb3aaa3af76c22c1318b4735a1 Mon Sep 17 00:00:00 2001 From: John Salem Date: Tue, 17 Mar 2020 12:15:53 -0700 Subject: [PATCH 16/52] Use random 16 bit number for cookie --- src/coreclr/src/vm/diagnosticsprotocol.h | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index 92c3abd50d836c..f145af38ec75d3 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -128,7 +128,7 @@ namespace DiagnosticsIpc * * The flow for Advertise is a one-way burst of 24 bytes consisting of * 6 bytes - "AD_V1\0" (ASCII chars + null byte) - * 2 bytes - CLR Instance ID (little-endian) + * 2 bytes - random 16 bit number cookie (little-endian) * 8 bytes - PID (little-endian) */ @@ -136,9 +136,21 @@ namespace DiagnosticsIpc const uint32_t AdvertiseSize = 16; + static uint16_t AdvertiseCookie_V1 = 0; + + inline uint16_t GetAdvertiseCookie_V1() + { + if (AdvertiseCookie_V1 == 0) + { + AdvertiseCookie_V1 = (uint16_t)GetRandomInt((int)((uint16_t)-1)); + } + + return AdvertiseCookie_V1; + } + inline bool PopulateIpcAdvertisePayload_V1(uint8_t (&buf)[AdvertiseSize]) { - uint16_t clrInstanceId = GetClrInstanceId(); + uint16_t cookie = GetAdvertiseCookie_V1(); uint64_t pid = GetCurrentProcessId(); uint8_t *bufferCursor = &buf[0]; uint32_t bufferLen = sizeof(buf); @@ -147,7 +159,7 @@ namespace DiagnosticsIpc if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, AdvertiseMagic_V1[i])) return false; - if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, clrInstanceId) || + if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie) || !TryWriteNumberLittleEndian(bufferCursor, bufferLen, pid)) return false; From 2c3baa433924eb0b9c24949d5684b852250e4e84 Mon Sep 17 00:00:00 2001 From: John Salem Date: Tue, 17 Mar 2020 12:25:20 -0700 Subject: [PATCH 17/52] Adding const-ness to Poll APIs --- src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp | 2 +- src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp | 2 +- src/coreclr/src/debug/inc/diagnosticsipc.h | 2 +- src/coreclr/src/vm/diagnosticsipcfactory.cpp | 4 ++-- src/coreclr/src/vm/diagnosticsipcfactory.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 603310be1206ce..3e3e22a7dc9785 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -171,7 +171,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal return new IpcStream(clientSocket, _serverSocket); } -int32_t IpcStream::Poll(IpcStream **ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback) +int32_t IpcStream::Poll(IpcStream *const *const ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback) { *ppStream = nullptr; pollfd *pollfds = new pollfd[nStreams]; diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 2139198ccbf271..edd4913fe146ce 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -187,7 +187,7 @@ IpcStream::~IpcStream() } } -int32_t IpcStream::Poll(IpcStream **ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback) +int32_t IpcStream::Poll(IpcStream *const *const ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback) { *ppStream = nullptr; // load up an array of handles diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index 574e7151b13990..7ee61f5ffc56d8 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -32,7 +32,7 @@ class IpcStream final // Returns: // int32_t: -1 on error, 0 on timeout, >0 on successful poll // - if ppStream is != nullptr and -1 is returned, that connection was hungup and it shouldn't be treated as an error - static int32_t Poll(IpcStream **ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback = nullptr); + static int32_t Poll(IpcStream *const *const ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback = nullptr); class DiagnosticsIpc final { diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.cpp b/src/coreclr/src/vm/diagnosticsipcfactory.cpp index 422ccd2ebf05fd..bb6b212cf1ac1e 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.cpp +++ b/src/coreclr/src/vm/diagnosticsipcfactory.cpp @@ -20,8 +20,8 @@ IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateClient(const char *const return IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); } -// TODO: const x2, ppIpcs, log info on looping counts -IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc **ppIpcs, uint32_t nIpcs, ErrorCallback callback) +// TODO: log info on looping counts +IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc *const *const ppIpcs, uint32_t nIpcs, ErrorCallback callback) { // a static array that holds open client connections that haven't been used // Remove entries from this list that have been used, e.g., they are placed in pStream and returned diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.h b/src/coreclr/src/vm/diagnosticsipcfactory.h index 7926682b2b8b85..d2b1b0fc74385a 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.h +++ b/src/coreclr/src/vm/diagnosticsipcfactory.h @@ -14,7 +14,7 @@ class DiagnosticsIpcFactory public: static IpcStream::DiagnosticsIpc *CreateServer(const char *const pIpcName, ErrorCallback = nullptr); static IpcStream::DiagnosticsIpc *CreateClient(const char *const pIpcName, ErrorCallback = nullptr); - static IpcStream *GetNextAvailableStream(IpcStream::DiagnosticsIpc **ppIpcs, uint32_t nIpcs, ErrorCallback = nullptr); + static IpcStream *GetNextAvailableStream(IpcStream::DiagnosticsIpc *const *const ppIpcs, uint32_t nIpcs, ErrorCallback = nullptr); private: static IpcStream **s_ppActiveConnections; }; From 398d0d82d801849d64a11c70e2a1513bde5977da Mon Sep 17 00:00:00 2001 From: John Salem Date: Tue, 17 Mar 2020 15:28:29 -0700 Subject: [PATCH 18/52] reset timeout on connection failure/change --- src/coreclr/src/vm/diagnosticsipcfactory.cpp | 27 ++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.cpp b/src/coreclr/src/vm/diagnosticsipcfactory.cpp index bb6b212cf1ac1e..bca877f66b0ba8 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.cpp +++ b/src/coreclr/src/vm/diagnosticsipcfactory.cpp @@ -34,7 +34,6 @@ IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsI memset(s_ppActiveConnections, 0, nIpcs * sizeof(IpcStream*)); } - // when we get a connection, put it in this list. If we use that connection, remove it. IpcStream *pStream = nullptr; // Polling timeout semantics @@ -61,10 +60,23 @@ IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsI pollTimeoutMs = (pollTimeoutMs == -1) ? pollTimeoutMinMs : pollTimeoutMs; if (s_ppActiveConnections[i] != nullptr) { - // check if still usable and then push it - // s_ppActiveConnections[i]->IsConnected(); ???? - pStreams.Push(s_ppActiveConnections[i]); - continue; + // Check if the connection is still open by doing a 0 length read + // this should fail if the connection has been closed + // N.B.: this can race (connection closes between here and Poll) + // but retry semantics means it shouldn't matter cause we'll + // self-correct + uint32_t nBytesRead; + if (s_ppActiveConnections[i]->Read(nullptr, 0, nBytesRead)) + { + pStreams.Push(s_ppActiveConnections[i]); + continue; + } + else + { + delete s_ppActiveConnections[i]; + s_ppActiveConnections[i] = nullptr; + pollTimeoutMs = pollTimeoutMinMs; + } } // loop here @@ -88,7 +100,7 @@ IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsI } else { - pollTimeoutMs = (pollTimeoutMs > pollTimeoutMaxMs) ? + pollTimeoutMs = (pollTimeoutMs >= pollTimeoutMaxMs) ? pollTimeoutMaxMs : pollTimeoutMs * pollTimeoutFalloffFactor; } @@ -121,12 +133,11 @@ IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsI s_ppActiveConnections[i] = nullptr; delete pStream; pStream = nullptr; + pollTimeoutMs = pollTimeoutMinMs; } } continue; } - - // TODO: error handle here? } } From 17eb02513d16982d813bbb2741f202107321f77f Mon Sep 17 00:00:00 2001 From: John Salem Date: Tue, 17 Mar 2020 16:22:53 -0700 Subject: [PATCH 19/52] Clean up caching logic and add logging --- src/coreclr/src/vm/diagnosticsipcfactory.cpp | 53 ++++++++++---------- src/coreclr/src/vm/diagnosticsipcfactory.h | 32 +++++++++++- 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.cpp b/src/coreclr/src/vm/diagnosticsipcfactory.cpp index bca877f66b0ba8..57880ab2118de2 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.cpp +++ b/src/coreclr/src/vm/diagnosticsipcfactory.cpp @@ -8,7 +8,8 @@ #ifdef FEATURE_PERFTRACING -IpcStream **DiagnosticsIpcFactory::s_ppActiveConnections = nullptr; +IpcStream **DiagnosticsIpcFactory::s_ppActiveConnectionsCache = nullptr; +uint32_t DiagnosticsIpcFactory::s_ActiveConnectionsCacheSize = 0; IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateServer(const char *const pIpcName, ErrorCallback callback) { @@ -20,7 +21,6 @@ IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateClient(const char *const return IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); } -// TODO: log info on looping counts IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc *const *const ppIpcs, uint32_t nIpcs, ErrorCallback callback) { // a static array that holds open client connections that haven't been used @@ -28,10 +28,17 @@ IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsI // This will prevent the runtime from continually reestablishing connection when the server loop loops. // This does, however, introduce state to this method which is undesireable, but a justifiable cost to minimizing system calls. - if (s_ppActiveConnections == nullptr) + if (s_ppActiveConnectionsCache == nullptr) { - s_ppActiveConnections = new IpcStream*[nIpcs]; - memset(s_ppActiveConnections, 0, nIpcs * sizeof(IpcStream*)); + ResizeCache(nIpcs); + } + + if (s_ActiveConnectionsCacheSize != nIpcs) + { + // number of connections has changed + // (3/2020 - This isn't possible, but should be here for future proofing) + ClearCache(); + ResizeCache(nIpcs); } IpcStream *pStream = nullptr; @@ -58,7 +65,7 @@ IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsI if (ppIpcs[i]->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) { pollTimeoutMs = (pollTimeoutMs == -1) ? pollTimeoutMinMs : pollTimeoutMs; - if (s_ppActiveConnections[i] != nullptr) + if (s_ppActiveConnectionsCache[i] != nullptr) { // Check if the connection is still open by doing a 0 length read // this should fail if the connection has been closed @@ -66,15 +73,16 @@ IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsI // but retry semantics means it shouldn't matter cause we'll // self-correct uint32_t nBytesRead; - if (s_ppActiveConnections[i]->Read(nullptr, 0, nBytesRead)) + uint8_t buf[1]; + if (s_ppActiveConnectionsCache[i]->Read(buf, 0, nBytesRead)) { - pStreams.Push(s_ppActiveConnections[i]); + pStreams.Push(s_ppActiveConnectionsCache[i]); continue; } else { - delete s_ppActiveConnections[i]; - s_ppActiveConnections[i] = nullptr; + delete s_ppActiveConnectionsCache[i]; + s_ppActiveConnectionsCache[i] = nullptr; pollTimeoutMs = pollTimeoutMinMs; } } @@ -89,12 +97,11 @@ IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsI { if (callback != nullptr) callback("Failed to send advertise message", -1); - // TODO: Should we just fall through instead and ignore the client conn? return nullptr; } - // Add connection to list - s_ppActiveConnections[i] = pConnection; + // Add connection to cache + s_ppActiveConnectionsCache[i] = pConnection; pStreams.Push(pConnection); pollTimeoutMs = pollTimeoutMaxMs; } @@ -120,31 +127,25 @@ IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsI int32_t retval = IpcStream::Poll(pStreams.Ptr(), (uint32_t)pStreams.Size(), pollTimeoutMs, &pStream, callback); nPollAttempts++; + STRESS_LOG2(LF_DIAGNOSTICS_PORT, LL_INFO10, "DiagnosticsIpcFactory::GetNextAvailableStream - Poll attempt: %d, timeout: %dms.\n", nPollAttempts, pollTimeoutMs); if (retval < 0) { if (pStream != nullptr) { + STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_INFO10, "DiagnosticsIpcFactory::GetNextAvailableStream - Poll attempt: %d, connection hung up.\n", nPollAttempts); // This stream was hung up - for (uint32_t i = 0; i < nIpcs; i++) - { - if (s_ppActiveConnections[i] == pStream) - { - s_ppActiveConnections[i] = nullptr; - delete pStream; - pStream = nullptr; - pollTimeoutMs = pollTimeoutMinMs; - } - } + RemoveFromCache(pStream); + delete pStream; + pStream = nullptr; + pollTimeoutMs = pollTimeoutMinMs; continue; } } } // Clean the Active Connection Cache of a used connection - for (uint32_t i = 0; i < nIpcs; i++) - if (s_ppActiveConnections[i] == pStream) - s_ppActiveConnections[i] = nullptr; + RemoveFromCache(pStream); return pStream; } diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.h b/src/coreclr/src/vm/diagnosticsipcfactory.h index d2b1b0fc74385a..cf5e137ffc0c27 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.h +++ b/src/coreclr/src/vm/diagnosticsipcfactory.h @@ -16,7 +16,37 @@ class DiagnosticsIpcFactory static IpcStream::DiagnosticsIpc *CreateClient(const char *const pIpcName, ErrorCallback = nullptr); static IpcStream *GetNextAvailableStream(IpcStream::DiagnosticsIpc *const *const ppIpcs, uint32_t nIpcs, ErrorCallback = nullptr); private: - static IpcStream **s_ppActiveConnections; + static IpcStream **s_ppActiveConnectionsCache; + static uint32_t s_ActiveConnectionsCacheSize; + + static void ResizeCache(uint32_t size) + { + if (s_ppActiveConnectionsCache != nullptr) + delete[] s_ppActiveConnectionsCache; + + s_ppActiveConnectionsCache = new IpcStream*[size]; + s_ActiveConnectionsCacheSize = size; + memset(s_ppActiveConnectionsCache, 0, size * sizeof(IpcStream*)); + } + + static void RemoveFromCache(IpcStream *pStream) + { + for (uint32_t i = 0; i < s_ActiveConnectionsCacheSize; i++) + if (s_ppActiveConnectionsCache[i] == pStream) + s_ppActiveConnectionsCache[i] = nullptr; + } + + static void ClearCache() + { + for (uint32_t i = 0; i < s_ActiveConnectionsCacheSize; i++) + { + if (s_ppActiveConnectionsCache[i] != nullptr) + { + delete s_ppActiveConnectionsCache[i]; + s_ppActiveConnectionsCache[i] = nullptr; + } + } + } }; #endif // FEATURE_PERFTRACING From dcd0329802be54ff86ff063452b4a53f931de027 Mon Sep 17 00:00:00 2001 From: John Salem Date: Tue, 17 Mar 2020 17:04:20 -0700 Subject: [PATCH 20/52] Code cleaning --- src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp | 2 -- src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp | 9 ++++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 3e3e22a7dc9785..7d6004c7216a4b 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -142,7 +142,6 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) if (callback != nullptr) callback(strerror(errno), errno); return nullptr; - // TODO: unlinks? } if (::connect(clientSocket, (struct sockaddr *)_pServerAddress, sizeof(*_pServerAddress)) < 0) @@ -150,7 +149,6 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) if (callback != nullptr) callback(strerror(errno), errno); return nullptr; - // TODO: Anything else? } return new IpcStream(clientSocket, -1, ConnectionMode::CLIENT); diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index edd4913fe146ce..e69c98c1aab236 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -42,11 +42,6 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p ::GetCurrentProcessId()); } - if (mode == ConnectionMode::CLIENT) - { - // TODO: block here till the socket exists? - } - if (nCharactersWritten == -1) { if (callback != nullptr) @@ -256,6 +251,8 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt _ASSERTE(lpBuffer != nullptr); DWORD nNumberOfBytesRead = 0; + // Server connections are Overlapped to allow non-blocking Accept calls + // Client connections are not LPOVERLAPPED overlap = (_mode == DiagnosticsIpc::ConnectionMode::SERVER) ? const_cast(&_oOverlap) : NULL; @@ -288,6 +285,8 @@ bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32 _ASSERTE(lpBuffer != nullptr); DWORD nNumberOfBytesWritten = 0; + // Server connections are Overlapped to allow non-blocking Accept calls + // Client connections are not LPOVERLAPPED overlap = (_mode == DiagnosticsIpc::ConnectionMode::SERVER) ? const_cast(&_oOverlap) : NULL; From ebf9fb76af8475875879536f52c48482e4465a33 Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 23 Mar 2020 11:33:57 -0700 Subject: [PATCH 21/52] DiagnosticsIpcFactory -> IpcStreamFactory --- src/coreclr/src/vm/CMakeLists.txt | 4 ++-- src/coreclr/src/vm/diagnosticserver.cpp | 8 ++++---- ...osticsipcfactory.cpp => ipcstreamfactory.cpp} | 16 ++++++++-------- ...iagnosticsipcfactory.h => ipcstreamfactory.h} | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) rename src/coreclr/src/vm/{diagnosticsipcfactory.cpp => ipcstreamfactory.cpp} (85%) rename src/coreclr/src/vm/{diagnosticsipcfactory.h => ipcstreamfactory.h} (92%) diff --git a/src/coreclr/src/vm/CMakeLists.txt b/src/coreclr/src/vm/CMakeLists.txt index 57a9bd1088a6ca..3e37cebdc10cf7 100644 --- a/src/coreclr/src/vm/CMakeLists.txt +++ b/src/coreclr/src/vm/CMakeLists.txt @@ -320,7 +320,6 @@ set(VM_SOURCES_WKS custommarshalerinfo.cpp autotrace.cpp diagnosticserver.cpp - diagnosticsipcfactory.cpp dllimportcallback.cpp eeconfig.cpp eecontract.cpp @@ -365,6 +364,7 @@ set(VM_SOURCES_WKS interoputil.cpp interpreter.cpp invokeutil.cpp + ipcstreamfactory.cpp jithelpers.cpp managedmdimport.cpp marshalnative.cpp @@ -437,7 +437,6 @@ set(VM_HEADERS_WKS custommarshalerinfo.h autotrace.h diagnosticserver.h - diagnosticsipcfactory.h diagnosticsprotocol.h dllimportcallback.h eeconfig.h @@ -484,6 +483,7 @@ set(VM_HEADERS_WKS interpreter.h interpreter.hpp invokeutil.h + ipcstreamfactory.h managedmdimport.hpp marshalnative.h methodtablebuilder.h diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index 1107f27dd9cee6..ca0568f3f0677e 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -4,7 +4,7 @@ #include "common.h" #include "diagnosticserver.h" -#include "diagnosticsipcfactory.h" +#include "ipcstreamfactory.h" #include "eventpipeprotocolhelper.h" #include "dumpdiagnosticprotocolhelper.h" #include "profilerdiagnosticprotocolhelper.h" @@ -48,7 +48,7 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) { while (!s_shuttingDown) { - IpcStream *pStream = DiagnosticsIpcFactory::GetNextAvailableStream(s_rgIpcs.Ptr(), (uint32_t)s_rgIpcs.Size(), LoggingCallback); + IpcStream *pStream = IpcStreamFactory::GetNextAvailableStream(s_rgIpcs.Ptr(), (uint32_t)s_rgIpcs.Size(), LoggingCallback); if (pStream == nullptr) continue; @@ -149,10 +149,10 @@ bool DiagnosticServer::Initialize() } // Create the clint mode connection - s_rgIpcs.Push(DiagnosticsIpcFactory::CreateClient(address, ErrorCallback)); + s_rgIpcs.Push(IpcStreamFactory::CreateClient(address, ErrorCallback)); } - s_rgIpcs.Push(DiagnosticsIpcFactory::CreateServer(nullptr, ErrorCallback)); + s_rgIpcs.Push(IpcStreamFactory::CreateServer(nullptr, ErrorCallback)); if (s_rgIpcs.Size() != 0) { diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.cpp b/src/coreclr/src/vm/ipcstreamfactory.cpp similarity index 85% rename from src/coreclr/src/vm/diagnosticsipcfactory.cpp rename to src/coreclr/src/vm/ipcstreamfactory.cpp index 57880ab2118de2..0a372e8c87a89d 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.cpp +++ b/src/coreclr/src/vm/ipcstreamfactory.cpp @@ -4,24 +4,24 @@ #include "common.h" #include "diagnosticsprotocol.h" -#include "diagnosticsipcfactory.h" +#include "ipcstreamfactory.h" #ifdef FEATURE_PERFTRACING -IpcStream **DiagnosticsIpcFactory::s_ppActiveConnectionsCache = nullptr; -uint32_t DiagnosticsIpcFactory::s_ActiveConnectionsCacheSize = 0; +IpcStream **IpcStreamFactory::s_ppActiveConnectionsCache = nullptr; +uint32_t IpcStreamFactory::s_ActiveConnectionsCacheSize = 0; -IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateServer(const char *const pIpcName, ErrorCallback callback) +IpcStream::DiagnosticsIpc *IpcStreamFactory::CreateServer(const char *const pIpcName, ErrorCallback callback) { return IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, callback); } -IpcStream::DiagnosticsIpc *DiagnosticsIpcFactory::CreateClient(const char *const pIpcName, ErrorCallback callback) +IpcStream::DiagnosticsIpc *IpcStreamFactory::CreateClient(const char *const pIpcName, ErrorCallback callback) { return IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); } -IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc *const *const ppIpcs, uint32_t nIpcs, ErrorCallback callback) +IpcStream *IpcStreamFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc *const *const ppIpcs, uint32_t nIpcs, ErrorCallback callback) { // a static array that holds open client connections that haven't been used // Remove entries from this list that have been used, e.g., they are placed in pStream and returned @@ -127,13 +127,13 @@ IpcStream *DiagnosticsIpcFactory::GetNextAvailableStream(IpcStream::DiagnosticsI int32_t retval = IpcStream::Poll(pStreams.Ptr(), (uint32_t)pStreams.Size(), pollTimeoutMs, &pStream, callback); nPollAttempts++; - STRESS_LOG2(LF_DIAGNOSTICS_PORT, LL_INFO10, "DiagnosticsIpcFactory::GetNextAvailableStream - Poll attempt: %d, timeout: %dms.\n", nPollAttempts, pollTimeoutMs); + STRESS_LOG2(LF_DIAGNOSTICS_PORT, LL_INFO10, "IpcStreamFactory::GetNextAvailableStream - Poll attempt: %d, timeout: %dms.\n", nPollAttempts, pollTimeoutMs); if (retval < 0) { if (pStream != nullptr) { - STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_INFO10, "DiagnosticsIpcFactory::GetNextAvailableStream - Poll attempt: %d, connection hung up.\n", nPollAttempts); + STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_INFO10, "IpcStreamFactory::GetNextAvailableStream - Poll attempt: %d, connection hung up.\n", nPollAttempts); // This stream was hung up RemoveFromCache(pStream); delete pStream; diff --git a/src/coreclr/src/vm/diagnosticsipcfactory.h b/src/coreclr/src/vm/ipcstreamfactory.h similarity index 92% rename from src/coreclr/src/vm/diagnosticsipcfactory.h rename to src/coreclr/src/vm/ipcstreamfactory.h index cf5e137ffc0c27..f50714c4cb71b8 100644 --- a/src/coreclr/src/vm/diagnosticsipcfactory.h +++ b/src/coreclr/src/vm/ipcstreamfactory.h @@ -2,14 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#ifndef __DIAGNOSTICS_IPC_FACTORY_H__ -#define __DIAGNOSTICS_IPC_FACTORY_H__ +#ifndef __IPC_STREAM_FACTORY_H__ +#define __IPC_STREAM_FACTORY_H__ #ifdef FEATURE_PERFTRACING #include "diagnosticsipc.h" -class DiagnosticsIpcFactory +class IpcStreamFactory { public: static IpcStream::DiagnosticsIpc *CreateServer(const char *const pIpcName, ErrorCallback = nullptr); @@ -51,4 +51,4 @@ class DiagnosticsIpcFactory #endif // FEATURE_PERFTRACING -#endif // __DIAGNOSTICS_IPC_FACTORY_H__ \ No newline at end of file +#endif // __IPC_STREAM_FACTORY_H__ \ No newline at end of file From 5eae64ce42e8e10726247fd98c1dd01124c12a78 Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 23 Mar 2020 15:29:47 -0700 Subject: [PATCH 22/52] Change ownership of connections * IpcStreamFactory now owns all streams and IPCs * single ownership means it will properly clean up connections * still has separate caching array for now --- src/coreclr/src/vm/diagnosticserver.cpp | 24 ++++---- src/coreclr/src/vm/diagnosticserver.h | 1 - src/coreclr/src/vm/ipcstreamfactory.cpp | 78 ++++++++++++++++++------- src/coreclr/src/vm/ipcstreamfactory.h | 37 ++++++------ 4 files changed, 87 insertions(+), 53 deletions(-) diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index ca0568f3f0677e..fcd1eca52506b5 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -21,7 +21,6 @@ #ifdef FEATURE_PERFTRACING Volatile DiagnosticServer::s_shuttingDown(false); -CQuickArrayList DiagnosticServer::s_rgIpcs = CQuickArrayList(); DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) { @@ -30,11 +29,11 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; - PRECONDITION(s_rgIpcs.Size() != 0); + PRECONDITION(IpcStreamFactory::HasActiveConnections()); } CONTRACTL_END; - if (s_rgIpcs.Size() == 0) + if (!IpcStreamFactory::HasActiveConnections()) { STRESS_LOG0(LF_DIAGNOSTICS_PORT, LL_ERROR, "Diagnostics IPC listener was undefined\n"); return 1; @@ -48,7 +47,7 @@ DWORD WINAPI DiagnosticServer::DiagnosticsServerThread(LPVOID) { while (!s_shuttingDown) { - IpcStream *pStream = IpcStreamFactory::GetNextAvailableStream(s_rgIpcs.Ptr(), (uint32_t)s_rgIpcs.Size(), LoggingCallback); + IpcStream *pStream = IpcStreamFactory::GetNextAvailableStream(LoggingCallback); if (pStream == nullptr) continue; @@ -149,12 +148,12 @@ bool DiagnosticServer::Initialize() } // Create the clint mode connection - s_rgIpcs.Push(IpcStreamFactory::CreateClient(address, ErrorCallback)); + fSuccess &= IpcStreamFactory::CreateClient(address, ErrorCallback); } - s_rgIpcs.Push(IpcStreamFactory::CreateServer(nullptr, ErrorCallback)); + fSuccess &= IpcStreamFactory::CreateServer(nullptr, ErrorCallback); - if (s_rgIpcs.Size() != 0) + if (IpcStreamFactory::HasActiveConnections()) { #ifdef FEATURE_AUTO_TRACE auto_trace_init(); @@ -165,15 +164,13 @@ bool DiagnosticServer::Initialize() nullptr, // no security attribute 0, // default stack size DiagnosticsServerThread, // thread proc - (LPVOID)s_rgIpcs.Ptr(), // thread parameter + nullptr, // thread parameter 0, // not suspended &dwThreadId); // returns thread ID if (hServerThread == NULL) { - for (uint32_t i = 0; i < s_rgIpcs.Size(); i++) - if (s_rgIpcs[i] != nullptr) - delete s_rgIpcs[i]; + IpcStreamFactory::CloseConnections(); // Failed to create IPC thread. STRESS_LOG1( @@ -218,7 +215,7 @@ bool DiagnosticServer::Shutdown() EX_TRY { - if (s_rgIpcs.Size() != 0) + if (IpcStreamFactory::HasActiveConnections()) { auto ErrorCallback = [](const char *szMessage, uint32_t code) { STRESS_LOG2( @@ -229,8 +226,7 @@ bool DiagnosticServer::Shutdown() szMessage); // data2 }; - for (uint32_t i = 0; i < s_rgIpcs.Size(); i++) - s_rgIpcs[i]->Close(ErrorCallback); + IpcStreamFactory::CloseConnections(); } fSuccess = true; } diff --git a/src/coreclr/src/vm/diagnosticserver.h b/src/coreclr/src/vm/diagnosticserver.h index 2062a24e10ac17..a5b8f07f7847b2 100644 --- a/src/coreclr/src/vm/diagnosticserver.h +++ b/src/coreclr/src/vm/diagnosticserver.h @@ -46,7 +46,6 @@ class DiagnosticServer final static DWORD WINAPI DiagnosticsServerThread(LPVOID lpThreadParameter); private: - static CQuickArrayList s_rgIpcs; static Volatile s_shuttingDown; }; diff --git a/src/coreclr/src/vm/ipcstreamfactory.cpp b/src/coreclr/src/vm/ipcstreamfactory.cpp index 0a372e8c87a89d..2c22498376e842 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.cpp +++ b/src/coreclr/src/vm/ipcstreamfactory.cpp @@ -8,37 +8,73 @@ #ifdef FEATURE_PERFTRACING -IpcStream **IpcStreamFactory::s_ppActiveConnectionsCache = nullptr; -uint32_t IpcStreamFactory::s_ActiveConnectionsCacheSize = 0; +CQuickArrayList IpcStreamFactory::s_rgpIpcs = CQuickArrayList(); +CQuickArray IpcStreamFactory::s_rgpActiveConnectionsCache = CQuickArray(); -IpcStream::DiagnosticsIpc *IpcStreamFactory::CreateServer(const char *const pIpcName, ErrorCallback callback) +bool IpcStreamFactory::CreateServer(const char *const pIpcName, ErrorCallback callback) { - return IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, callback); + IpcStream::DiagnosticsIpc *pIpc = IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, callback); + if (pIpc != nullptr) + { + s_rgpIpcs.Push(pIpc); + return true; + } + else + { + return false; + } } -IpcStream::DiagnosticsIpc *IpcStreamFactory::CreateClient(const char *const pIpcName, ErrorCallback callback) +bool IpcStreamFactory::CreateClient(const char *const pIpcName, ErrorCallback callback) { - return IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); + IpcStream::DiagnosticsIpc *pIpc = IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); + if (pIpc != nullptr) + { + s_rgpIpcs.Push(pIpc); + return true; + } + else + { + return false; + } +} + +bool IpcStreamFactory::HasActiveConnections() +{ + return s_rgpIpcs.Size() > 0; +} + +void IpcStreamFactory::CloseConnections() +{ + for (uint32_t i = 0; i < (uint32_t)s_rgpIpcs.Size(); i++) + { + IpcStream::DiagnosticsIpc *pIpc = s_rgpIpcs.Pop(); + if (pIpc != nullptr) + delete pIpc; + + if (s_rgpActiveConnectionsCache[i] != nullptr) + delete s_rgpActiveConnectionsCache[i]; + } } -IpcStream *IpcStreamFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc *const *const ppIpcs, uint32_t nIpcs, ErrorCallback callback) +IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) { // a static array that holds open client connections that haven't been used // Remove entries from this list that have been used, e.g., they are placed in pStream and returned // This will prevent the runtime from continually reestablishing connection when the server loop loops. // This does, however, introduce state to this method which is undesireable, but a justifiable cost to minimizing system calls. - if (s_ppActiveConnectionsCache == nullptr) + if (s_rgpActiveConnectionsCache == nullptr) { - ResizeCache(nIpcs); + ResizeCache((uint32_t)s_rgpIpcs.Size()); } - if (s_ActiveConnectionsCacheSize != nIpcs) + if (s_rgpActiveConnectionsCache.Size() != s_rgpIpcs.Size()) { // number of connections has changed // (3/2020 - This isn't possible, but should be here for future proofing) ClearCache(); - ResizeCache(nIpcs); + ResizeCache((uint32_t)s_rgpIpcs.Size()); } IpcStream *pStream = nullptr; @@ -60,12 +96,12 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc *c while (pStream == nullptr) { CQuickArrayList pStreams; - for (uint32_t i = 0; i < nIpcs; i++) + for (uint32_t i = 0; i < (uint32_t)s_rgpIpcs.Size(); i++) { - if (ppIpcs[i]->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) + if (s_rgpIpcs[i]->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) { pollTimeoutMs = (pollTimeoutMs == -1) ? pollTimeoutMinMs : pollTimeoutMs; - if (s_ppActiveConnectionsCache[i] != nullptr) + if (s_rgpActiveConnectionsCache[i] != nullptr) { // Check if the connection is still open by doing a 0 length read // this should fail if the connection has been closed @@ -74,22 +110,22 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc *c // self-correct uint32_t nBytesRead; uint8_t buf[1]; - if (s_ppActiveConnectionsCache[i]->Read(buf, 0, nBytesRead)) + if (s_rgpActiveConnectionsCache[i]->Read(buf, 0, nBytesRead)) { - pStreams.Push(s_ppActiveConnectionsCache[i]); + pStreams.Push(s_rgpActiveConnectionsCache[i]); continue; } else { - delete s_ppActiveConnectionsCache[i]; - s_ppActiveConnectionsCache[i] = nullptr; + delete s_rgpActiveConnectionsCache[i]; + s_rgpActiveConnectionsCache[i] = nullptr; pollTimeoutMs = pollTimeoutMinMs; } } // loop here IpcStream *pConnection = nullptr; - pConnection = ppIpcs[i]->Connect(callback); + pConnection = s_rgpIpcs[i]->Connect(callback); if (pConnection != nullptr) { @@ -101,7 +137,7 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc *c } // Add connection to cache - s_ppActiveConnectionsCache[i] = pConnection; + s_rgpActiveConnectionsCache[i] = pConnection; pStreams.Push(pConnection); pollTimeoutMs = pollTimeoutMaxMs; } @@ -114,7 +150,7 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(IpcStream::DiagnosticsIpc *c } else { - IpcStream *pServer = ppIpcs[i]->Accept(false, callback); + IpcStream *pServer = s_rgpIpcs[i]->Accept(false, callback); if (pServer == nullptr) { if (callback != nullptr) diff --git a/src/coreclr/src/vm/ipcstreamfactory.h b/src/coreclr/src/vm/ipcstreamfactory.h index f50714c4cb71b8..13a28fec9a0db0 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.h +++ b/src/coreclr/src/vm/ipcstreamfactory.h @@ -12,38 +12,41 @@ class IpcStreamFactory { public: - static IpcStream::DiagnosticsIpc *CreateServer(const char *const pIpcName, ErrorCallback = nullptr); - static IpcStream::DiagnosticsIpc *CreateClient(const char *const pIpcName, ErrorCallback = nullptr); - static IpcStream *GetNextAvailableStream(IpcStream::DiagnosticsIpc *const *const ppIpcs, uint32_t nIpcs, ErrorCallback = nullptr); + static bool CreateServer(const char *const pIpcName, ErrorCallback = nullptr); + static bool CreateClient(const char *const pIpcName, ErrorCallback = nullptr); + static IpcStream *GetNextAvailableStream(ErrorCallback = nullptr); + static bool HasActiveConnections(); + static void CloseConnections(); private: - static IpcStream **s_ppActiveConnectionsCache; - static uint32_t s_ActiveConnectionsCacheSize; + static CQuickArrayList s_rgpIpcs; + static CQuickArray s_rgpActiveConnectionsCache; static void ResizeCache(uint32_t size) { - if (s_ppActiveConnectionsCache != nullptr) - delete[] s_ppActiveConnectionsCache; + if (s_rgpActiveConnectionsCache != nullptr) + ClearCache(); - s_ppActiveConnectionsCache = new IpcStream*[size]; - s_ActiveConnectionsCacheSize = size; - memset(s_ppActiveConnectionsCache, 0, size * sizeof(IpcStream*)); + // s_ppActiveConnectionsCache = new IpcStream*[size]; + // s_ActiveConnectionsCacheSize = size; + // memset(s_ppActiveConnectionsCache, 0, size * sizeof(IpcStream*)); + s_rgpActiveConnectionsCache.ReSizeThrows(size); } static void RemoveFromCache(IpcStream *pStream) { - for (uint32_t i = 0; i < s_ActiveConnectionsCacheSize; i++) - if (s_ppActiveConnectionsCache[i] == pStream) - s_ppActiveConnectionsCache[i] = nullptr; + for (uint32_t i = 0; i < (uint32_t)s_rgpActiveConnectionsCache.Size(); i++) + if (s_rgpActiveConnectionsCache[i] == pStream) + s_rgpActiveConnectionsCache[i] = nullptr; } static void ClearCache() { - for (uint32_t i = 0; i < s_ActiveConnectionsCacheSize; i++) + for (uint32_t i = 0; i < (uint32_t)s_rgpActiveConnectionsCache.Size(); i++) { - if (s_ppActiveConnectionsCache[i] != nullptr) + if (s_rgpActiveConnectionsCache[i] != nullptr) { - delete s_ppActiveConnectionsCache[i]; - s_ppActiveConnectionsCache[i] = nullptr; + delete s_rgpActiveConnectionsCache[i]; + s_rgpActiveConnectionsCache[i] = nullptr; } } } From 834d71dfb592240eb64cd6884a99149ea2ea83f7 Mon Sep 17 00:00:00 2001 From: John Salem Date: Tue, 24 Mar 2020 18:22:28 -0700 Subject: [PATCH 23/52] Modify abstraction * IpcStreamFactory::Poll now is more similar to the poll API from Linux * IpcPollHandle struct is used to abstract listening for client and server connections * Remove Accept call and change to Listen * Listen puts DiagnosticIpc into active mode for server connections * Listen is re-entrant safe and won't allocate on each call or leak * clean up overlap objects on windows * use overlapped io for all io on windows * untested on windows, tested on unix --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 94 ++++----- .../debug/debug-pal/win/diagnosticsipc.cpp | 142 +++++++------- src/coreclr/src/debug/inc/diagnosticsipc.h | 63 +++++-- src/coreclr/src/vm/ipcstreamfactory.cpp | 178 +++++++++--------- src/coreclr/src/vm/ipcstreamfactory.h | 33 +--- 5 files changed, 258 insertions(+), 252 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 7d6004c7216a4b..52552a448596f8 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -22,7 +22,8 @@ IpcStream::DiagnosticsIpc::DiagnosticsIpc(const int serverSocket, sockaddr_un *c mode(mode), _serverSocket(serverSocket), _pServerAddress(new sockaddr_un), - _isClosed(false) + _isClosed(false), + _isListening(false) { _ASSERTE(_pServerAddress != nullptr); _ASSERTE(pServerAddress != nullptr); @@ -107,29 +108,37 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p return nullptr; } - const int fSuccessfulListen = ::listen(serverSocket, /* backlog */ 255); +#ifdef __APPLE__ + umask(prev_mask); +#endif // __APPLE__ + + return new IpcStream::DiagnosticsIpc(serverSocket, &serverAddress, mode); +} + +bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) +{ + if (_isListening) + return true; + + const int fSuccessfulListen = ::listen(_serverSocket, /* backlog */ 255); if (fSuccessfulListen == -1) { if (callback != nullptr) callback(strerror(errno), errno); _ASSERTE(fSuccessfulListen != -1); - const int fSuccessUnlink = ::unlink(serverAddress.sun_path); + const int fSuccessUnlink = ::unlink(_pServerAddress->sun_path); _ASSERTE(fSuccessUnlink != -1); - const int fSuccessClose = ::close(serverSocket); + const int fSuccessClose = ::close(_serverSocket); _ASSERTE(fSuccessClose != -1); -#ifdef __APPLE__ - umask(prev_mask); -#endif // __APPLE__ - return nullptr; + return false; + } + else + { + _isListening = true; + return true; } - -#ifdef __APPLE__ - umask(prev_mask); -#endif // __APPLE__ - - return new IpcStream::DiagnosticsIpc(serverSocket, &serverAddress, mode); } IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) @@ -154,49 +163,40 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) return new IpcStream(clientSocket, -1, ConnectionMode::CLIENT); } -IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback callback) const -{ - sockaddr_un from; - socklen_t fromlen = sizeof(from); - const int clientSocket = shouldBlock ? ::accept(_serverSocket, (sockaddr *)&from, &fromlen) : -1; - if (shouldBlock && clientSocket == -1) - { - if (callback != nullptr) - callback(strerror(errno), errno); - return nullptr; - } - - return new IpcStream(clientSocket, _serverSocket); -} - -int32_t IpcStream::Poll(IpcStream *const *const ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback) +int32_t IpcStream::DiagnosticsIpc::Poll(IpcStream::IpcPollHandle *const * rgpIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback) { - *ppStream = nullptr; - pollfd *pollfds = new pollfd[nStreams]; - for (uint32_t i = 0; i < nStreams; i++) + // prepare the pollfd structs + pollfd *pollfds = new pollfd[nHandles]; + for (uint32_t i = 0; i < nHandles; i++) { + rgpIpcPollHandles[i]->revents = 0; // ignore any values in revents int fd = -1; - if (ppStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && ppStreams[i]->_clientSocket == -1) + if (rgpIpcPollHandles[i]->pIpc->mode == ConnectionMode::SERVER) { - fd = ppStreams[i]->_serverSocket; + // SERVER + fd = rgpIpcPollHandles[i]->pIpc->_serverSocket; } else { - fd = ppStreams[i]->_clientSocket; + // CLIENT + _ASSERTE(rgpIpcPollHandles[i]->pStream != nullptr); + fd = rgpIpcPollHandles[i]->pStream->_clientSocket; } pollfds[i].fd = fd; pollfds[i].events = POLLIN; } - int retval = poll(pollfds, nStreams, timeoutMs); - + int retval = poll(pollfds, nHandles, timeoutMs); + + // Check results if (retval < 0) { - for (uint32_t i = 0; i < nStreams; i++) + for (uint32_t i = 0; i < nHandles; i++) { if ((pollfds[i].revents & POLLERR) && callback != nullptr) callback(strerror(errno), errno); + rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::ERR; } delete[] pollfds; return -1; @@ -208,24 +208,25 @@ int32_t IpcStream::Poll(IpcStream *const *const ppStreams, uint32_t nStreams, in return 0; } - for (uint32_t i = 0; i < nStreams; i++) + for (uint32_t i = 0; i < nHandles; i++) { if (pollfds[i].revents != 0) { - bool needToAccept = ppStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER && ppStreams[i]->_clientSocket == -1; + bool needToAccept = rgpIpcPollHandles[i]->pIpc->mode == DiagnosticsIpc::ConnectionMode::SERVER; // error check FIRST if (pollfds[i].revents & POLLHUP) { // check for hangup first because a closed socket // will technically meet the requirements for POLLIN // i.e., a call to recv/read won't block - *ppStream = ppStreams[i]; + rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::HANGUP; return -1; } else if ((pollfds[i].revents & (POLLERR|POLLNVAL))) { if (callback != nullptr) callback("Poll error", (uint32_t)pollfds[i].revents); + rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::ERR; return -1; } else if (pollfds[i].revents & POLLIN) @@ -234,19 +235,22 @@ int32_t IpcStream::Poll(IpcStream *const *const ppStreams, uint32_t nStreams, in { sockaddr_un from; socklen_t fromlen = sizeof(from); - const int clientSocket = ::accept(ppStreams[i]->_serverSocket, (sockaddr *)&from, &fromlen); + const int clientSocket = ::accept(rgpIpcPollHandles[i]->pStream->_serverSocket, (sockaddr *)&from, &fromlen); if (clientSocket == -1) { if (callback != nullptr) callback(strerror(errno), errno); + rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::ERR; delete[] pollfds; return -1; } - *ppStream = new IpcStream(clientSocket, ppStreams[i]->_serverSocket, ppStreams[i]->_mode); + rgpIpcPollHandles[i]->pStream = new IpcStream(clientSocket, rgpIpcPollHandles[i]->pIpc->_serverSocket, rgpIpcPollHandles[i]->pIpc->mode); + rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::SIGNALED; } else { - *ppStream = ppStreams[i]; + // *ppStream = ppStreams[i]; + rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::SIGNALED; } break; } diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index e69c98c1aab236..193ff14f4236a0 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -9,8 +9,9 @@ #define _ASSERTE assert -IpcStream::DiagnosticsIpc::DiagnosticsIpc(const char(&namedPipeName)[MaxNamedPipeNameLength], ConnectionMode mode) - : mode(mode) +IpcStream::DiagnosticsIpc::DiagnosticsIpc(const char(&namedPipeName)[MaxNamedPipeNameLength], ConnectionMode mode) : + mode(mode), + _isListening(false) { memcpy(_pNamedPipeName, namedPipeName, sizeof(_pNamedPipeName)); } @@ -53,25 +54,20 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p return new IpcStream::DiagnosticsIpc(namedPipeName, mode); } -IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback callback) const +bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) { + if (_isListening) + return true; + _ASSERTE(mode == ConnectionMode::SERVER); - if (mode != ConnectionMode::SERVER) - { - if (callback != nullptr) - callback("Cannot call accept on a client connection", 0); - return nullptr; - } const uint32_t nInBufferSize = 16 * 1024; const uint32_t nOutBufferSize = 16 * 1024; HANDLE hPipe = ::CreateNamedPipeA( _pNamedPipeName, // pipe name - PIPE_ACCESS_DUPLEX | - FILE_FLAG_OVERLAPPED, // read/write access - PIPE_TYPE_BYTE | - PIPE_WAIT | - PIPE_REJECT_REMOTE_CLIENTS, // message type pipe, message-read and blocking mode + PIPE_ACCESS_DUPLEX | // read/write access + FILE_FLAG_OVERLAPPED, // async listening + PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, // message type pipe, message-read and blocking mode PIPE_UNLIMITED_INSTANCES, // max. instances nOutBufferSize, // output buffer size nInBufferSize, // input buffer size @@ -82,30 +78,19 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal { if (callback != nullptr) callback("Failed to create an instance of a named pipe.", ::GetLastError()); - return nullptr; + return false; } - // TODO: Find a better way to do this than - // mixing abstractions - IpcStream *pStream = new IpcStream(hPipe, mode); + _oOverlap.hEvent = CreateEvent(NULL, true, false, NULL); - BOOL fSuccess = ::ConnectNamedPipe(hPipe, &pStream->_oOverlap) != 0; + BOOL fSuccess = ::ConnectNamedPipe(hPipe, _oOverlap) != 0; if (!fSuccess) { const DWORD errorCode = ::GetLastError(); switch (errorCode) { - case ERROR_PIPE_LISTENING: - // Occurs when there isn't a pending client and we're - // in PIPE_NOWAIT mode case ERROR_IO_PENDING: - if (shouldBlock) - { - fSuccess = GetOverlappedResult(pStream->_hPipe, - &pStream->_oOverlap, - NULL, - true); - } + // There was a pending connection that can be waited on (will happen in poll) case ERROR_PIPE_CONNECTED: // Occurs when a client connects before the function is called. // In this case, there is a connection between client and @@ -116,12 +101,13 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(bool shouldBlock, ErrorCallback cal if (callback != nullptr) callback("A client process failed to connect.", errorCode); ::CloseHandle(hPipe); - delete pStream; - return nullptr; + ::CloseHandle(_oOverlap.hEvent); + return false; } } - return pStream; + _isListening = true; + return true; } IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) @@ -161,8 +147,7 @@ IpcStream::IpcStream(HANDLE hPipe, DiagnosticsIpc::ConnectionMode mode) : _hPipe(hPipe), _mode(mode) { - if (_mode == DiagnosticsIpc::ConnectionMode::SERVER) - _oOverlap.hEvent = CreateEvent(NULL, true, false, NULL); + _oOverlap.hEvent = CreateEvent(NULL, true, false, NULL); } IpcStream::~IpcStream() @@ -180,31 +165,36 @@ IpcStream::~IpcStream() const BOOL fSuccessCloseHandle = ::CloseHandle(_hPipe); _ASSERTE(fSuccessCloseHandle != 0); } + + if (_oOverlap.hEvent != INVALID_HANDLE_VALUE) + { + ::CloseHandle(_oOverlap.hEvent); + } } -int32_t IpcStream::Poll(IpcStream *const *const ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback) +int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback) { - *ppStream = nullptr; // load up an array of handles - HANDLE *pHandles = new HANDLE[nStreams]; - for (uint32_t i = 0; i < nStreams; i++) + HANDLE *pHandles = new HANDLE[nHandles]; + for (uint32_t i = 0; i < nHandles; i++) { - if (ppStreams[i]->_mode == DiagnosticsIpc::ConnectionMode::SERVER) + rgpIpcPollHandles[i]->revents = 0; // ignore any inputs on revents + if (rgpIpcPollHandles[i]->pIpc->mode == DiagnosticsIpc::ConnectionMode::SERVER) { - pHandles[i] = ppStreams[i]->_oOverlap.hEvent; + pHandles[i] = rgpIpcPollHandles[i]->pIpc->_oOverlap.hEvent; } else { - pHandles[i] = ppStreams[i]->_hPipe; + pHandles[i] = rgpIpcPollHandles[i]->pStream->_hPipe; } } // call wait for multiple obj DWORD dwWait = WaitForMultipleObjects( - nStreams, // count + nHandles, // count pHandles, // handles - false, // Don't wait all - timeoutMs); // wait infinitely + false, // Don't wait-all + timeoutMs); if (dwWait == WAIT_TIMEOUT) { @@ -213,35 +203,57 @@ int32_t IpcStream::Poll(IpcStream *const *const ppStreams, uint32_t nStreams, in return 0; } - // determine which of the streams signaled - DWORD index = dwWait - WAIT_OBJECT_0; - if (index < 0 || index > (nStreams - 1)) + if (dwWait == WAIT_FAILED) { + // we errored if (callback != nullptr) - callback("Failed to select to named pipe.", ::GetLastError()); + callback("WaitForMultipleObjects failed", ::GetLastError()); delete[] pHandles; return -1; } - if (ppStreams[index]->_mode == IpcStream::DiagnosticsIpc::ConnectionMode::SERVER) + // determine which of the streams signaled + DWORD index = dwWait - WAIT_OBJECT_0; + if (index < 0 || index > (nHandles - 1)) + { + // check if we abandoned something + DWORD abandonedIndex = dwWait - WAIT_ABANDONED_0; + if (abandonedIndex > 0 || abandonedIndex < (nHandles - 1)) + { + rgpIpcPollHandles[abandonedIndex]->revents = (uint8_t)IpcStream::PollEvents::HANGUP; + delete[] pHandles; + return -1; + } + else + { + if (callback != nullptr) + callback("WaitForMultipleObjects failed", ::GetLastError()); + delete[] pHandles; + return -1; + } + } + + if (rgpIpcPollHandles[index]->pIpc->mode == IpcStream::DiagnosticsIpc::ConnectionMode::SERVER) { - // set that stream's mode to blocking - bool result = SetNamedPipeHandleState( - pHandles[index], // handle - PIPE_READMODE_BYTE | PIPE_WAIT, // read mode and wait mode - NULL, // no collecting - NULL); // no collecting - if (!result) + bool fSuccess = GetOverlappedResult(rgpIpcPollHandles[index]->pIpc->_hPipe, + &rgpIpcPollHandles[index]->pIpc->_oOverlap, + NULL, + true); + if (!fSuccess) { if (callback != nullptr) - callback("Failed to convert handle to wait mode", ::GetLastError()); + callback("Failed to GetOverlappedResults for NamedPipe server", ::GetLastError()); + rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::PollEvents::ERR; delete[] pHandles; return -1; } + rgpIpcPollHandles[index]->pStream = new IpcStream(rgpIpcPollHandles[index]->pIpc->_hPipe, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER); + rgpIpcPollHandles[index]->pIpc->_hPipe = INVALID_HANDLE_VALUE; + rgpIpcPollHandles[index]->pIpc->_isListening = false; + ::CloseHandle(rgpIpcPollHandles[index]->pIpc->_oOverlap.hEvent); + rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::PollEvents::SIGNALED; } - // cleanup and return that stream - *ppStream = ppStreams[index]; delete[] pHandles; return 1; } @@ -251,11 +263,7 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt _ASSERTE(lpBuffer != nullptr); DWORD nNumberOfBytesRead = 0; - // Server connections are Overlapped to allow non-blocking Accept calls - // Client connections are not - LPOVERLAPPED overlap = (_mode == DiagnosticsIpc::ConnectionMode::SERVER) ? - const_cast(&_oOverlap) : - NULL; + LPOVERLAPPED overlap = const_cast(&_oOverlap); bool fSuccess = ::ReadFile( _hPipe, // handle to pipe lpBuffer, // buffer to receive data @@ -285,11 +293,7 @@ bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32 _ASSERTE(lpBuffer != nullptr); DWORD nNumberOfBytesWritten = 0; - // Server connections are Overlapped to allow non-blocking Accept calls - // Client connections are not - LPOVERLAPPED overlap = (_mode == DiagnosticsIpc::ConnectionMode::SERVER) ? - const_cast(&_oOverlap) : - NULL; + LPOVERLAPPED overlap = const_cast(&_oOverlap); bool fSuccess = ::WriteFile( _hPipe, // handle to pipe lpBuffer, // buffer to write from diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index 7ee61f5ffc56d8..48e1af6fc04985 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -23,16 +23,36 @@ class IpcStream final bool Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) const; bool Flush() const; - // Poll - // Paramters: - // - IpcStream **pStreams: Array of pointers to IpcStreams to poll - // - uint32_t nStreams: The number of streams to poll - // - int32_t timeoutMs: The timeout in milliseconds for the poll (-1 == infinite) - // - IpcStream **pStream: OUT PARAMETER nullptr for timeout or error, signalled stream for successful poll - // Returns: - // int32_t: -1 on error, 0 on timeout, >0 on successful poll - // - if ppStream is != nullptr and -1 is returned, that connection was hungup and it shouldn't be treated as an error - static int32_t Poll(IpcStream *const *const ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback = nullptr); + class DiagnosticsIpc; + + enum class PollEvents : uint8_t + { + TIMEOUT = 0x00, // implies timeout + SIGNALED = 0x01, // ready for use + HANGUP = 0x02, // connection remotely closed + ERR = 0x04 // other error + }; + + struct IpcPollHandle + { + DiagnosticsIpc *pIpc; + + // After calling Poll, will contain a usable IpcStream + // IFF (revents & (uint8_t)PollEvents::SIGNALED) != 0 + // + // DiagnosticsIpc::ConnectionMode::CLIENT connections should place a usable + // IpcStream here before calling Poll. + // + // DiagnosticsIpc::ConnectionMode::SERVER connections can leave this blank + IpcStream *pStream; + + // contains some set of PollEvents + // will be set by Poll + uint8_t revents; + }; + + + // static int32_t Poll(IpcStream *const *const ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback = nullptr); class DiagnosticsIpc final { @@ -43,6 +63,19 @@ class IpcStream final SERVER }; + // Poll + // Paramters: + // - IpcPollHandle *const * rgpIpcPollHandles: Array of pointers to IpcPollHandles to poll + // - uint32_t nHandles: The number of streams to poll + // - int32_t timeoutMs: The timeout in milliseconds for the poll (-1 == infinite) + // Returns: + // int32_t: -1 on error, 0 on timeout, >0 on successful poll + // Remarks: + // Check the events returned in revents for each IpcPollHandle to find the signaled handle. + // Signaled handles will have usable IpcStreams in the pStream field. + // The caller is responsible for cleaning up "hung up" connections. + static int32_t Poll(IpcPollHandle *const * rgpIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback = nullptr); + ConnectionMode mode; ~DiagnosticsIpc(); @@ -50,9 +83,11 @@ class IpcStream final //! Creates an IPC object static DiagnosticsIpc *Create(const char *const pIpcName, ConnectionMode mode, ErrorCallback callback = nullptr); - //! Enables the underlaying IPC implementation to accept connection. - IpcStream *Accept(bool shouldBlock, ErrorCallback callback = nullptr) const; + //! puts the DiagnosticsIpc into Listening Mode + //! Re-entrant safe + bool Listen(ErrorCallback callback = nullptr); + //! Connect to client connection (returns a usable stream) IpcStream *Connect(ErrorCallback callback = nullptr); //! Closes an open IPC. @@ -73,10 +108,14 @@ class IpcStream final #else static const uint32_t MaxNamedPipeNameLength = 256; char _pNamedPipeName[MaxNamedPipeNameLength]; // https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createnamedpipea + HANDLE _hPipe = INVALID_HANDLE_VALUE; + OVERLAPPED _oOverlap = {}; DiagnosticsIpc(const char(&namedPipeName)[MaxNamedPipeNameLength], ConnectionMode mode = ConnectionMode::SERVER); #endif /* TARGET_UNIX */ + bool _isListening; + DiagnosticsIpc() = delete; DiagnosticsIpc(const DiagnosticsIpc &src) = delete; DiagnosticsIpc(DiagnosticsIpc &&src) = delete; diff --git a/src/coreclr/src/vm/ipcstreamfactory.cpp b/src/coreclr/src/vm/ipcstreamfactory.cpp index 2c22498376e842..b5b4c202547eb0 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.cpp +++ b/src/coreclr/src/vm/ipcstreamfactory.cpp @@ -8,16 +8,25 @@ #ifdef FEATURE_PERFTRACING -CQuickArrayList IpcStreamFactory::s_rgpIpcs = CQuickArrayList(); -CQuickArray IpcStreamFactory::s_rgpActiveConnectionsCache = CQuickArray(); +CQuickArrayList IpcStreamFactory::s_rgIpcPollHandles = CQuickArrayList(); bool IpcStreamFactory::CreateServer(const char *const pIpcName, ErrorCallback callback) { IpcStream::DiagnosticsIpc *pIpc = IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER, callback); if (pIpc != nullptr) { - s_rgpIpcs.Push(pIpc); - return true; + if (pIpc->Listen(callback)) + { + IpcStream::IpcPollHandle ipcPollHandle { .pIpc = pIpc, .pStream = nullptr, .revents = 0 }; + s_rgIpcPollHandles.Push(ipcPollHandle); + // s_rgpIpcs.Push(pIpc); + return true; + } + else + { + delete pIpc; + return false; + } } else { @@ -30,7 +39,9 @@ bool IpcStreamFactory::CreateClient(const char *const pIpcName, ErrorCallback ca IpcStream::DiagnosticsIpc *pIpc = IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); if (pIpc != nullptr) { - s_rgpIpcs.Push(pIpc); + IpcStream::IpcPollHandle ipcPollHandle { .pIpc = pIpc, .pStream = nullptr, .revents = 0 }; + s_rgIpcPollHandles.Push(ipcPollHandle); + // s_rgpIpcs.Push(pIpc); return true; } else @@ -41,147 +52,126 @@ bool IpcStreamFactory::CreateClient(const char *const pIpcName, ErrorCallback ca bool IpcStreamFactory::HasActiveConnections() { - return s_rgpIpcs.Size() > 0; + return s_rgIpcPollHandles.Size() > 0; } void IpcStreamFactory::CloseConnections() { - for (uint32_t i = 0; i < (uint32_t)s_rgpIpcs.Size(); i++) + while (s_rgIpcPollHandles.Size() > 0) { - IpcStream::DiagnosticsIpc *pIpc = s_rgpIpcs.Pop(); - if (pIpc != nullptr) - delete pIpc; - - if (s_rgpActiveConnectionsCache[i] != nullptr) - delete s_rgpActiveConnectionsCache[i]; + IpcStream::IpcPollHandle ipcPollHandle = s_rgIpcPollHandles.Pop(); + if (ipcPollHandle.pStream != nullptr) + delete ipcPollHandle.pStream; + if (ipcPollHandle.pIpc != nullptr) + delete ipcPollHandle.pIpc; } } IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) { - // a static array that holds open client connections that haven't been used - // Remove entries from this list that have been used, e.g., they are placed in pStream and returned - // This will prevent the runtime from continually reestablishing connection when the server loop loops. - // This does, however, introduce state to this method which is undesireable, but a justifiable cost to minimizing system calls. - - if (s_rgpActiveConnectionsCache == nullptr) - { - ResizeCache((uint32_t)s_rgpIpcs.Size()); - } - - if (s_rgpActiveConnectionsCache.Size() != s_rgpIpcs.Size()) - { - // number of connections has changed - // (3/2020 - This isn't possible, but should be here for future proofing) - ClearCache(); - ResizeCache((uint32_t)s_rgpIpcs.Size()); - } - IpcStream *pStream = nullptr; + // View of s_rgIpcPollhandles + CQuickArrayList pIpcPollHandles; // Polling timeout semantics // If client connection is opted in - // and connection succeeds => set timeout to max + // and connection succeeds => set timeout to infinite // and connection fails => set timeout to minimum and scale by falloff factor // else => set timeout to -1 (infinite) // // If an agent closes its socket while we're still connected, - // the max timeout is the amount of time it will take for us to notice + // Poll will return and let us know which connection hung up int32_t pollTimeoutFalloffFactor = 2; + int32_t pollTimeoutInfinite = -1; int32_t pollTimeoutMinMs = 250; - int32_t pollTimeoutMs = -1; + int32_t pollTimeoutMs = pollTimeoutInfinite; int32_t pollTimeoutMaxMs = 30000; // 30s uint32_t nPollAttempts = 0; while (pStream == nullptr) { - CQuickArrayList pStreams; - for (uint32_t i = 0; i < (uint32_t)s_rgpIpcs.Size(); i++) + for (uint32_t i = 0; i < (uint32_t)s_rgIpcPollHandles.Size(); i++) { - if (s_rgpIpcs[i]->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) + if (s_rgIpcPollHandles[i].pIpc->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) { - pollTimeoutMs = (pollTimeoutMs == -1) ? pollTimeoutMinMs : pollTimeoutMs; - if (s_rgpActiveConnectionsCache[i] != nullptr) + pollTimeoutMs = (pollTimeoutMs == pollTimeoutInfinite) ? pollTimeoutMinMs : pollTimeoutMs; + if (s_rgIpcPollHandles[i].pStream == nullptr) { - // Check if the connection is still open by doing a 0 length read - // this should fail if the connection has been closed - // N.B.: this can race (connection closes between here and Poll) - // but retry semantics means it shouldn't matter cause we'll - // self-correct - uint32_t nBytesRead; - uint8_t buf[1]; - if (s_rgpActiveConnectionsCache[i]->Read(buf, 0, nBytesRead)) + // cache is empty, reconnect + IpcStream *pConnection = nullptr; + pConnection = s_rgIpcPollHandles[i].pIpc->Connect(callback); + + if (pConnection != nullptr) { - pStreams.Push(s_rgpActiveConnectionsCache[i]); - continue; + if (!DiagnosticsIpc::SendIpcAdvertise_V1(pConnection)) + { + if (callback != nullptr) + callback("Failed to send advertise message", -1); + delete pConnection; + return nullptr; + } + + // Add connection to cache + s_rgIpcPollHandles[i].pStream = pConnection; + pollTimeoutMs = pollTimeoutInfinite; + pIpcPollHandles.Push(&s_rgIpcPollHandles[i]); } else { - delete s_rgpActiveConnectionsCache[i]; - s_rgpActiveConnectionsCache[i] = nullptr; - pollTimeoutMs = pollTimeoutMinMs; + pollTimeoutMs = (pollTimeoutMs >= pollTimeoutMaxMs) ? + pollTimeoutMaxMs : + pollTimeoutMs * pollTimeoutFalloffFactor; } } - - // loop here - IpcStream *pConnection = nullptr; - pConnection = s_rgpIpcs[i]->Connect(callback); - - if (pConnection != nullptr) - { - if (!DiagnosticsIpc::SendIpcAdvertise_V1(pConnection)) - { - if (callback != nullptr) - callback("Failed to send advertise message", -1); - return nullptr; - } - - // Add connection to cache - s_rgpActiveConnectionsCache[i] = pConnection; - pStreams.Push(pConnection); - pollTimeoutMs = pollTimeoutMaxMs; - } - else - { - pollTimeoutMs = (pollTimeoutMs >= pollTimeoutMaxMs) ? - pollTimeoutMaxMs : - pollTimeoutMs * pollTimeoutFalloffFactor; - } } else { - IpcStream *pServer = s_rgpIpcs[i]->Accept(false, callback); - if (pServer == nullptr) + bool fSuccess = s_rgIpcPollHandles[i].pIpc->Listen(); + if (!fSuccess) { - if (callback != nullptr) - callback("DiagnosticsServer failed to accept", -1); - return nullptr; + // TODO: error check the server failing to listen } - pStreams.Push(pServer); + pIpcPollHandles.Push(&s_rgIpcPollHandles[i]); } } - int32_t retval = IpcStream::Poll(pStreams.Ptr(), (uint32_t)pStreams.Size(), pollTimeoutMs, &pStream, callback); + int32_t retval = IpcStream::DiagnosticsIpc::Poll(pIpcPollHandles.Ptr(), (uint32_t)pIpcPollHandles.Size(), pollTimeoutMs, callback); nPollAttempts++; STRESS_LOG2(LF_DIAGNOSTICS_PORT, LL_INFO10, "IpcStreamFactory::GetNextAvailableStream - Poll attempt: %d, timeout: %dms.\n", nPollAttempts, pollTimeoutMs); - if (retval < 0) + if (retval != 0) { - if (pStream != nullptr) + for (uint32_t i = 0; i < (uint32_t)pIpcPollHandles.Size(); i++) { - STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_INFO10, "IpcStreamFactory::GetNextAvailableStream - Poll attempt: %d, connection hung up.\n", nPollAttempts); - // This stream was hung up - RemoveFromCache(pStream); - delete pStream; - pStream = nullptr; - pollTimeoutMs = pollTimeoutMinMs; - continue; + switch ((IpcStream::PollEvents)pIpcPollHandles[i]->revents) + { + case IpcStream::PollEvents::HANGUP: + delete pIpcPollHandles[i]->pStream; + pIpcPollHandles[i]->pStream = nullptr; // clear the cache of the hung up connection; will trigger a reconnect poll + STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_INFO10, "IpcStreamFactory::GetNextAvailableStream - Poll attempt: %d, connection hung up.\n", nPollAttempts); + pollTimeoutMs = pollTimeoutMinMs; + break; + case IpcStream::PollEvents::SIGNALED: + if (pStream == nullptr) // only use first signaled stream; will get others on subsequent calls + { + pStream = pIpcPollHandles[i]->pStream; + pIpcPollHandles[i]->pStream = nullptr; // pass ownership to caller so we aren't caching the connection anymore + } + break; + case IpcStream::PollEvents::ERR: + default: + // TODO: Error handling + break; + } } } + + // clear the view + while (pIpcPollHandles.Size() > 0) + pIpcPollHandles.Pop(); } // Clean the Active Connection Cache of a used connection - RemoveFromCache(pStream); return pStream; } diff --git a/src/coreclr/src/vm/ipcstreamfactory.h b/src/coreclr/src/vm/ipcstreamfactory.h index 13a28fec9a0db0..5c7be91995cb7d 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.h +++ b/src/coreclr/src/vm/ipcstreamfactory.h @@ -18,38 +18,7 @@ class IpcStreamFactory static bool HasActiveConnections(); static void CloseConnections(); private: - static CQuickArrayList s_rgpIpcs; - static CQuickArray s_rgpActiveConnectionsCache; - - static void ResizeCache(uint32_t size) - { - if (s_rgpActiveConnectionsCache != nullptr) - ClearCache(); - - // s_ppActiveConnectionsCache = new IpcStream*[size]; - // s_ActiveConnectionsCacheSize = size; - // memset(s_ppActiveConnectionsCache, 0, size * sizeof(IpcStream*)); - s_rgpActiveConnectionsCache.ReSizeThrows(size); - } - - static void RemoveFromCache(IpcStream *pStream) - { - for (uint32_t i = 0; i < (uint32_t)s_rgpActiveConnectionsCache.Size(); i++) - if (s_rgpActiveConnectionsCache[i] == pStream) - s_rgpActiveConnectionsCache[i] = nullptr; - } - - static void ClearCache() - { - for (uint32_t i = 0; i < (uint32_t)s_rgpActiveConnectionsCache.Size(); i++) - { - if (s_rgpActiveConnectionsCache[i] != nullptr) - { - delete s_rgpActiveConnectionsCache[i]; - s_rgpActiveConnectionsCache[i] = nullptr; - } - } - } + static CQuickArrayList s_rgIpcPollHandles; }; #endif // FEATURE_PERFTRACING From 0a193b062851b41a373e7b1cfb7dc2c0e2591813 Mon Sep 17 00:00:00 2001 From: John Salem Date: Wed, 25 Mar 2020 15:31:18 -0700 Subject: [PATCH 24/52] Little bit of code cleaning --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 2 +- .../debug/debug-pal/win/diagnosticsipc.cpp | 2 +- src/coreclr/src/debug/inc/diagnosticsipc.h | 58 +++++++++---------- src/coreclr/src/vm/ipcstreamfactory.cpp | 22 +++---- src/coreclr/src/vm/ipcstreamfactory.h | 2 +- 5 files changed, 39 insertions(+), 47 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 52552a448596f8..13be74bf7966c7 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -163,7 +163,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) return new IpcStream(clientSocket, -1, ConnectionMode::CLIENT); } -int32_t IpcStream::DiagnosticsIpc::Poll(IpcStream::IpcPollHandle *const * rgpIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback) +int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback) { // prepare the pollfd structs pollfd *pollfds = new pollfd[nHandles]; diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 193ff14f4236a0..5d148a89d40d18 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -216,7 +216,7 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles DWORD index = dwWait - WAIT_OBJECT_0; if (index < 0 || index > (nHandles - 1)) { - // check if we abandoned something + // check if we abandoned something DWORD abandonedIndex = dwWait - WAIT_ABANDONED_0; if (abandonedIndex > 0 || abandonedIndex < (nHandles - 1)) { diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index 48e1af6fc04985..95d44a457509a1 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -23,37 +23,6 @@ class IpcStream final bool Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) const; bool Flush() const; - class DiagnosticsIpc; - - enum class PollEvents : uint8_t - { - TIMEOUT = 0x00, // implies timeout - SIGNALED = 0x01, // ready for use - HANGUP = 0x02, // connection remotely closed - ERR = 0x04 // other error - }; - - struct IpcPollHandle - { - DiagnosticsIpc *pIpc; - - // After calling Poll, will contain a usable IpcStream - // IFF (revents & (uint8_t)PollEvents::SIGNALED) != 0 - // - // DiagnosticsIpc::ConnectionMode::CLIENT connections should place a usable - // IpcStream here before calling Poll. - // - // DiagnosticsIpc::ConnectionMode::SERVER connections can leave this blank - IpcStream *pStream; - - // contains some set of PollEvents - // will be set by Poll - uint8_t revents; - }; - - - // static int32_t Poll(IpcStream *const *const ppStreams, uint32_t nStreams, int32_t timeoutMs, IpcStream **ppStream, ErrorCallback callback = nullptr); - class DiagnosticsIpc final { public: @@ -63,6 +32,33 @@ class IpcStream final SERVER }; + enum class PollEvents : uint8_t + { + TIMEOUT = 0x00, // implies timeout + SIGNALED = 0x01, // ready for use + HANGUP = 0x02, // connection remotely closed + ERR = 0x04 // other error + }; + + struct IpcPollHandle + { + DiagnosticsIpc *pIpc; + + // After calling Poll, will contain a usable IpcStream + // IFF (revents & (uint8_t)PollEvents::SIGNALED) != 0 + // + // DiagnosticsIpc::ConnectionMode::CLIENT connections should place a usable + // IpcStream here before calling Poll, i.e., pollHandle.pStream = pollHandle.pIpc->Connect() + // + // DiagnosticsIpc::ConnectionMode::SERVER connections can leave this null + IpcStream *pStream; + + // contains some set of PollEvents + // will be set by Poll + // Any values here are ignored by Poll + uint8_t revents; + }; + // Poll // Paramters: // - IpcPollHandle *const * rgpIpcPollHandles: Array of pointers to IpcPollHandles to poll diff --git a/src/coreclr/src/vm/ipcstreamfactory.cpp b/src/coreclr/src/vm/ipcstreamfactory.cpp index b5b4c202547eb0..21f7537b383f98 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.cpp +++ b/src/coreclr/src/vm/ipcstreamfactory.cpp @@ -8,7 +8,7 @@ #ifdef FEATURE_PERFTRACING -CQuickArrayList IpcStreamFactory::s_rgIpcPollHandles = CQuickArrayList(); +CQuickArrayList IpcStreamFactory::s_rgIpcPollHandles = CQuickArrayList(); bool IpcStreamFactory::CreateServer(const char *const pIpcName, ErrorCallback callback) { @@ -17,9 +17,8 @@ bool IpcStreamFactory::CreateServer(const char *const pIpcName, ErrorCallback ca { if (pIpc->Listen(callback)) { - IpcStream::IpcPollHandle ipcPollHandle { .pIpc = pIpc, .pStream = nullptr, .revents = 0 }; + IpcStream::DiagnosticsIpc::IpcPollHandle ipcPollHandle { .pIpc = pIpc, .pStream = nullptr, .revents = 0 }; s_rgIpcPollHandles.Push(ipcPollHandle); - // s_rgpIpcs.Push(pIpc); return true; } else @@ -39,9 +38,8 @@ bool IpcStreamFactory::CreateClient(const char *const pIpcName, ErrorCallback ca IpcStream::DiagnosticsIpc *pIpc = IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); if (pIpc != nullptr) { - IpcStream::IpcPollHandle ipcPollHandle { .pIpc = pIpc, .pStream = nullptr, .revents = 0 }; + IpcStream::DiagnosticsIpc::IpcPollHandle ipcPollHandle { .pIpc = pIpc, .pStream = nullptr, .revents = 0 }; s_rgIpcPollHandles.Push(ipcPollHandle); - // s_rgpIpcs.Push(pIpc); return true; } else @@ -59,7 +57,7 @@ void IpcStreamFactory::CloseConnections() { while (s_rgIpcPollHandles.Size() > 0) { - IpcStream::IpcPollHandle ipcPollHandle = s_rgIpcPollHandles.Pop(); + IpcStream::DiagnosticsIpc::IpcPollHandle ipcPollHandle = s_rgIpcPollHandles.Pop(); if (ipcPollHandle.pStream != nullptr) delete ipcPollHandle.pStream; if (ipcPollHandle.pIpc != nullptr) @@ -71,7 +69,7 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) { IpcStream *pStream = nullptr; // View of s_rgIpcPollhandles - CQuickArrayList pIpcPollHandles; + CQuickArrayList pIpcPollHandles; // Polling timeout semantics // If client connection is opted in @@ -111,7 +109,6 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) return nullptr; } - // Add connection to cache s_rgIpcPollHandles[i].pStream = pConnection; pollTimeoutMs = pollTimeoutInfinite; pIpcPollHandles.Push(&s_rgIpcPollHandles[i]); @@ -143,22 +140,22 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) { for (uint32_t i = 0; i < (uint32_t)pIpcPollHandles.Size(); i++) { - switch ((IpcStream::PollEvents)pIpcPollHandles[i]->revents) + switch ((IpcStream::DiagnosticsIpc::PollEvents)pIpcPollHandles[i]->revents) { - case IpcStream::PollEvents::HANGUP: + case IpcStream::DiagnosticsIpc::PollEvents::HANGUP: delete pIpcPollHandles[i]->pStream; pIpcPollHandles[i]->pStream = nullptr; // clear the cache of the hung up connection; will trigger a reconnect poll STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_INFO10, "IpcStreamFactory::GetNextAvailableStream - Poll attempt: %d, connection hung up.\n", nPollAttempts); pollTimeoutMs = pollTimeoutMinMs; break; - case IpcStream::PollEvents::SIGNALED: + case IpcStream::DiagnosticsIpc::PollEvents::SIGNALED: if (pStream == nullptr) // only use first signaled stream; will get others on subsequent calls { pStream = pIpcPollHandles[i]->pStream; pIpcPollHandles[i]->pStream = nullptr; // pass ownership to caller so we aren't caching the connection anymore } break; - case IpcStream::PollEvents::ERR: + case IpcStream::DiagnosticsIpc::PollEvents::ERR: default: // TODO: Error handling break; @@ -171,7 +168,6 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) pIpcPollHandles.Pop(); } - // Clean the Active Connection Cache of a used connection return pStream; } diff --git a/src/coreclr/src/vm/ipcstreamfactory.h b/src/coreclr/src/vm/ipcstreamfactory.h index 5c7be91995cb7d..5a27d1bb99fc3a 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.h +++ b/src/coreclr/src/vm/ipcstreamfactory.h @@ -18,7 +18,7 @@ class IpcStreamFactory static bool HasActiveConnections(); static void CloseConnections(); private: - static CQuickArrayList s_rgIpcPollHandles; + static CQuickArrayList s_rgIpcPollHandles; }; #endif // FEATURE_PERFTRACING From ddc2880d2eef21f358da68acf231bd79ac297793 Mon Sep 17 00:00:00 2001 From: John Salem Date: Wed, 25 Mar 2020 16:20:50 -0700 Subject: [PATCH 25/52] Change Advertise Cookie to 128 bit GUID --- src/coreclr/src/vm/diagnosticsprotocol.h | 32 ++++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index f145af38ec75d3..6a45d62e65a161 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -127,22 +127,22 @@ namespace DiagnosticsIpc * IS STANDARD DIAGNOSTICS IPC PROTOCOL COMMUNICATION. * * The flow for Advertise is a one-way burst of 24 bytes consisting of - * 6 bytes - "AD_V1\0" (ASCII chars + null byte) - * 2 bytes - random 16 bit number cookie (little-endian) - * 8 bytes - PID (little-endian) + * 6 bytes - "AD_V1\0" (ASCII chars + null byte) + * 16 bytes - random 128 bit number cookie (little-endian) + * 8 bytes - PID (little-endian) */ const uint8_t AdvertiseMagic_V1[6] = "AD_V1"; - const uint32_t AdvertiseSize = 16; + const uint32_t AdvertiseSize = 30; - static uint16_t AdvertiseCookie_V1 = 0; + static GUID AdvertiseCookie_V1 = GUID_NULL; - inline uint16_t GetAdvertiseCookie_V1() + inline GUID GetAdvertiseCookie_V1() { - if (AdvertiseCookie_V1 == 0) + if (AdvertiseCookie_V1 == GUID_NULL) { - AdvertiseCookie_V1 = (uint16_t)GetRandomInt((int)((uint16_t)-1)); + CoCreateGuid(&AdvertiseCookie_V1); } return AdvertiseCookie_V1; @@ -150,7 +150,7 @@ namespace DiagnosticsIpc inline bool PopulateIpcAdvertisePayload_V1(uint8_t (&buf)[AdvertiseSize]) { - uint16_t cookie = GetAdvertiseCookie_V1(); + GUID cookie = GetAdvertiseCookie_V1(); uint64_t pid = GetCurrentProcessId(); uint8_t *bufferCursor = &buf[0]; uint32_t bufferLen = sizeof(buf); @@ -158,8 +158,18 @@ namespace DiagnosticsIpc for (uint32_t i = 0; i < sizeof(AdvertiseMagic_V1); i++) if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, AdvertiseMagic_V1[i])) return false; - - if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie) || + + if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data1) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data2) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data3) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[0]) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[1]) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[2]) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[3]) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[4]) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[5]) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[6]) || + !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[7]) || !TryWriteNumberLittleEndian(bufferCursor, bufferLen, pid)) return false; From 74244be4a4caefda29d8037414d413b00c18e83f Mon Sep 17 00:00:00 2001 From: John Salem Date: Thu, 26 Mar 2020 10:48:46 -0700 Subject: [PATCH 26/52] Add comment about connect --- src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 13be74bf7966c7..720c8d5b02c9b9 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -153,6 +153,9 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) return nullptr; } + // We don't expect this to block since this is a Unix Domain Socket. `connect` may block until the + // TCP handshake is complete for TCP/IP sockets, but UDS don't use TCP. `connect` will return even if + // the server hasn't called `accept`. if (::connect(clientSocket, (struct sockaddr *)_pServerAddress, sizeof(*_pServerAddress)) < 0) { if (callback != nullptr) From 911ca0af1c0f299a9a9fd588b1d617b93dd4f6ce Mon Sep 17 00:00:00 2001 From: John Salem Date: Thu, 26 Mar 2020 11:19:12 -0700 Subject: [PATCH 27/52] Simplify advertise meta-protocol * make it 4 64-bit numbers * simplify writing logic * remove TryWriteNumberLittleEndian --- src/coreclr/src/vm/diagnosticsprotocol.h | 61 +++++------------------- 1 file changed, 12 insertions(+), 49 deletions(-) diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index 6a45d62e65a161..6e109a317ddeb5 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -57,23 +57,6 @@ bool TryParseString(uint8_t *&bufferCursor, uint32_t &bufferLen, const T *&resul return true; } -template -bool TryWriteNumberLittleEndian(uint8_t *&bufferCursor, uint32_t &bufferLen, const T &value) -{ - static_assert(std::is_integral::value, "Can only write integral types"); - - if (bufferLen < sizeof(value)) - return false; - - for (uint32_t i = 0; i < sizeof(value); i++) - { - *bufferCursor++ = (value >> (i * 8)) & 0xFF; - bufferLen -= 1; - } - - return true; -} - namespace DiagnosticsIpc { enum class IpcMagicVersion : uint8_t @@ -126,15 +109,17 @@ namespace DiagnosticsIpc * the runtime must advertise itself over the connection. ALL SUBSEQUENT COMMUNICATION * IS STANDARD DIAGNOSTICS IPC PROTOCOL COMMUNICATION. * + * See spec in: dotnet/diagnostics@documentation/design-docs/ipc-spec.md + * * The flow for Advertise is a one-way burst of 24 bytes consisting of - * 6 bytes - "AD_V1\0" (ASCII chars + null byte) + * 8 bytes - "ADVR_V1\0" (ASCII chars + null byte) * 16 bytes - random 128 bit number cookie (little-endian) * 8 bytes - PID (little-endian) */ - const uint8_t AdvertiseMagic_V1[6] = "AD_V1"; + const uint8_t AdvertiseMagic_V1[8] = "ADVR_V1"; - const uint32_t AdvertiseSize = 30; + const uint32_t AdvertiseSize = 32; static GUID AdvertiseCookie_V1 = GUID_NULL; @@ -148,39 +133,17 @@ namespace DiagnosticsIpc return AdvertiseCookie_V1; } - inline bool PopulateIpcAdvertisePayload_V1(uint8_t (&buf)[AdvertiseSize]) + inline bool SendIpcAdvertise_V1(IpcStream *pStream) { + uint8_t advertiseBuffer[DiagnosticsIpc::AdvertiseSize]; GUID cookie = GetAdvertiseCookie_V1(); uint64_t pid = GetCurrentProcessId(); - uint8_t *bufferCursor = &buf[0]; - uint32_t bufferLen = sizeof(buf); - - for (uint32_t i = 0; i < sizeof(AdvertiseMagic_V1); i++) - if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, AdvertiseMagic_V1[i])) - return false; - - if (!TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data1) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data2) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data3) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[0]) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[1]) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[2]) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[3]) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[4]) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[5]) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[6]) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, cookie.Data4[7]) || - !TryWriteNumberLittleEndian(bufferCursor, bufferLen, pid)) - return false; - - return true; - } - inline bool SendIpcAdvertise_V1(IpcStream *pStream) - { - uint8_t advertiseBuffer[DiagnosticsIpc::AdvertiseSize]; - if (!DiagnosticsIpc::PopulateIpcAdvertisePayload_V1(advertiseBuffer)) - return false; + uint64_t *buffer = (uint64_t*)advertiseBuffer; + buffer[0] = *(uint64_t*)AdvertiseMagic_V1; + buffer[1] = (((uint64_t)VAL32(cookie.Data1) << 32) | ((uint64_t)VAL16(cookie.Data2) << 16) | VAL16((uint64_t)cookie.Data3)); + buffer[2] = *(uint64_t*)cookie.Data4; + buffer[3] = VAL64(pid); uint32_t nBytesWritten = 0; if (!pStream->Write(advertiseBuffer, sizeof(advertiseBuffer), nBytesWritten)) From 8c0630a0d82bf2850cc471cf209b1abf713b9c4b Mon Sep 17 00:00:00 2001 From: John Salem Date: Thu, 26 Mar 2020 11:34:33 -0700 Subject: [PATCH 28/52] Clean up resources in ~IpcStream::DiagnosticsIpc --- .../src/debug/debug-pal/win/diagnosticsipc.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 5d148a89d40d18..64815d6220423b 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -141,6 +141,22 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) void IpcStream::DiagnosticsIpc::Close(ErrorCallback) { + if (_hPipe != INVALID_HANDLE_VALUE) + { + if (mode == DiagnosticsIpc::ConnectionMode::SERVER) + { + const BOOL fSuccessDisconnectNamedPipe = ::DisconnectNamedPipe(_hPipe); + _ASSERTE(fSuccessDisconnectNamedPipe != 0); + } + + const BOOL fSuccessCloseHandle = ::CloseHandle(_hPipe); + _ASSERTE(fSuccessCloseHandle != 0); + } + + if (_oOverlap.hEvent != INVALID_HANDLE_VALUE) + { + ::CloseHandle(_oOverlap.hEvent); + } } IpcStream::IpcStream(HANDLE hPipe, DiagnosticsIpc::ConnectionMode mode) : From ead4041321154346dce37274abd7d09ed323bd8d Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 30 Mar 2020 11:34:54 -0700 Subject: [PATCH 29/52] Fix Windows implementation * use Overlapped IO correctly * use 0 byte read for checking incoming data * clean up overlap handles in destructors --- .../debug/debug-pal/win/diagnosticsipc.cpp | 73 +++++++++++++++---- src/coreclr/src/debug/inc/diagnosticsipc.h | 4 +- src/coreclr/src/vm/ipcstreamfactory.cpp | 37 ++++++---- 3 files changed, 81 insertions(+), 33 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 64815d6220423b..12d84408676fda 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -14,6 +14,7 @@ IpcStream::DiagnosticsIpc::DiagnosticsIpc(const char(&namedPipeName)[MaxNamedPip _isListening(false) { memcpy(_pNamedPipeName, namedPipeName, sizeof(_pNamedPipeName)); + memset(&_oOverlap, 0, sizeof(OVERLAPPED)); } IpcStream::DiagnosticsIpc::~DiagnosticsIpc() @@ -63,7 +64,7 @@ bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) const uint32_t nInBufferSize = 16 * 1024; const uint32_t nOutBufferSize = 16 * 1024; - HANDLE hPipe = ::CreateNamedPipeA( + _hPipe = ::CreateNamedPipeA( _pNamedPipeName, // pipe name PIPE_ACCESS_DUPLEX | // read/write access FILE_FLAG_OVERLAPPED, // async listening @@ -74,7 +75,7 @@ bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) 0, // default client time-out NULL); // default security attribute - if (hPipe == INVALID_HANDLE_VALUE) + if (_hPipe == INVALID_HANDLE_VALUE) { if (callback != nullptr) callback("Failed to create an instance of a named pipe.", ::GetLastError()); @@ -83,7 +84,7 @@ bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) _oOverlap.hEvent = CreateEvent(NULL, true, false, NULL); - BOOL fSuccess = ::ConnectNamedPipe(hPipe, _oOverlap) != 0; + BOOL fSuccess = ::ConnectNamedPipe(_hPipe, &_oOverlap) != 0; if (!fSuccess) { const DWORD errorCode = ::GetLastError(); @@ -100,7 +101,7 @@ bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) default: if (callback != nullptr) callback("A client process failed to connect.", errorCode); - ::CloseHandle(hPipe); + ::CloseHandle(_hPipe); ::CloseHandle(_oOverlap.hEvent); return false; } @@ -122,11 +123,11 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) HANDLE hPipe = ::CreateFileA( _pNamedPipeName, // pipe name - PIPE_ACCESS_DUPLEX, // pipe access + PIPE_ACCESS_DUPLEX, // read/write access 0, // no sharing NULL, // default security attributes OPEN_EXISTING, // opens existing pipe - 0, // default attributes + FILE_FLAG_OVERLAPPED, // Overlapped NULL); // no template file if (hPipe == INVALID_HANDLE_VALUE) @@ -163,6 +164,7 @@ IpcStream::IpcStream(HANDLE hPipe, DiagnosticsIpc::ConnectionMode mode) : _hPipe(hPipe), _mode(mode) { + memset(&_oOverlap, 0, sizeof(OVERLAPPED)); _oOverlap.hEvent = CreateEvent(NULL, true, false, NULL); } @@ -201,7 +203,18 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles } else { - pHandles[i] = rgpIpcPollHandles[i]->pStream->_hPipe; + // check for data by doing an asynchronous 0 byte read. + // This will signal if the pipe closes (hangup) or the server + // sends new data + DWORD dummyDW = 0; + bool fSuccess = ::ReadFile( + rgpIpcPollHandles[i]->pStream->_hPipe, // handle + nullptr, // null buffer + 0, // read 0 bytes + &dummyDW, // dummy variable + &rgpIpcPollHandles[i]->pStream->_oOverlap); // overlap object to use + _ASSERTE(!fSuccess && ::GetLastError() == ERROR_IO_PENDING); + pHandles[i] = rgpIpcPollHandles[i]->pStream->_oOverlap.hEvent; } } @@ -230,13 +243,14 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles // determine which of the streams signaled DWORD index = dwWait - WAIT_OBJECT_0; + // error check the index if (index < 0 || index > (nHandles - 1)) { // check if we abandoned something DWORD abandonedIndex = dwWait - WAIT_ABANDONED_0; if (abandonedIndex > 0 || abandonedIndex < (nHandles - 1)) { - rgpIpcPollHandles[abandonedIndex]->revents = (uint8_t)IpcStream::PollEvents::HANGUP; + rgpIpcPollHandles[abandonedIndex]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::HANGUP; delete[] pHandles; return -1; } @@ -249,32 +263,59 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles } } - if (rgpIpcPollHandles[index]->pIpc->mode == IpcStream::DiagnosticsIpc::ConnectionMode::SERVER) + // Set revents depending on what signaled the stream + if (rgpIpcPollHandles[index]->pIpc->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) + { + // check if the connection got hung up + DWORD dummyDW = 0; + bool fSuccess = GetOverlappedResult(rgpIpcPollHandles[index]->pStream->_hPipe, + &rgpIpcPollHandles[index]->pStream->_oOverlap, + &dummyDW, + true); + if (!fSuccess) + { + DWORD error = ::GetLastError(); + if (error == ERROR_PIPE_NOT_CONNECTED) + rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::HANGUP; + else + rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::ERR; + } + else + { + rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::SIGNALED; + } + } + else { + // complete the async listen + DWORD dummyDW = 0; bool fSuccess = GetOverlappedResult(rgpIpcPollHandles[index]->pIpc->_hPipe, &rgpIpcPollHandles[index]->pIpc->_oOverlap, - NULL, + &dummyDW, true); if (!fSuccess) { if (callback != nullptr) callback("Failed to GetOverlappedResults for NamedPipe server", ::GetLastError()); - rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::PollEvents::ERR; + rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::ERR; delete[] pHandles; return -1; } + + // create new IpcStream using handle and reset the Server object so it can listen again rgpIpcPollHandles[index]->pStream = new IpcStream(rgpIpcPollHandles[index]->pIpc->_hPipe, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER); rgpIpcPollHandles[index]->pIpc->_hPipe = INVALID_HANDLE_VALUE; rgpIpcPollHandles[index]->pIpc->_isListening = false; ::CloseHandle(rgpIpcPollHandles[index]->pIpc->_oOverlap.hEvent); - rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::PollEvents::SIGNALED; + memset(&rgpIpcPollHandles[index]->pIpc->_oOverlap, 0, sizeof(OVERLAPPED)); // clear the overlapped objects state + rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::SIGNALED; } delete[] pHandles; return 1; } -bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) const +bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) { _ASSERTE(lpBuffer != nullptr); @@ -285,7 +326,7 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt lpBuffer, // buffer to receive data nBytesToRead, // size of buffer &nNumberOfBytesRead, // number of bytes read - overlap) != 0; // not overlapped I/O + overlap) != 0; // overlapped I/O if (!fSuccess) { @@ -304,7 +345,7 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt return fSuccess; } -bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) const +bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) { _ASSERTE(lpBuffer != nullptr); @@ -315,7 +356,7 @@ bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32 lpBuffer, // buffer to write from nBytesToWrite, // number of bytes to write &nNumberOfBytesWritten, // number of bytes written - overlap) != 0; // not overlapped I/O + overlap) != 0; // overlapped I/O if (!fSuccess) { diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index 95d44a457509a1..06a2c54b1db0a7 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -19,8 +19,8 @@ class IpcStream final { public: ~IpcStream(); - bool Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) const; - bool Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) const; + bool Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead); + bool Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten); bool Flush() const; class DiagnosticsIpc final diff --git a/src/coreclr/src/vm/ipcstreamfactory.cpp b/src/coreclr/src/vm/ipcstreamfactory.cpp index 21f7537b383f98..885aeb7462c544 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.cpp +++ b/src/coreclr/src/vm/ipcstreamfactory.cpp @@ -17,7 +17,7 @@ bool IpcStreamFactory::CreateServer(const char *const pIpcName, ErrorCallback ca { if (pIpc->Listen(callback)) { - IpcStream::DiagnosticsIpc::IpcPollHandle ipcPollHandle { .pIpc = pIpc, .pStream = nullptr, .revents = 0 }; + IpcStream::DiagnosticsIpc::IpcPollHandle ipcPollHandle = { pIpc, nullptr, 0 }; s_rgIpcPollHandles.Push(ipcPollHandle); return true; } @@ -38,7 +38,7 @@ bool IpcStreamFactory::CreateClient(const char *const pIpcName, ErrorCallback ca IpcStream::DiagnosticsIpc *pIpc = IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); if (pIpc != nullptr) { - IpcStream::DiagnosticsIpc::IpcPollHandle ipcPollHandle { .pIpc = pIpc, .pStream = nullptr, .revents = 0 }; + IpcStream::DiagnosticsIpc::IpcPollHandle ipcPollHandle = { pIpc, nullptr, 0 }; s_rgIpcPollHandles.Push(ipcPollHandle); return true; } @@ -69,7 +69,7 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) { IpcStream *pStream = nullptr; // View of s_rgIpcPollhandles - CQuickArrayList pIpcPollHandles; + CQuickArrayList rgpIpcPollHandles; // Polling timeout semantics // If client connection is opted in @@ -95,7 +95,7 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) pollTimeoutMs = (pollTimeoutMs == pollTimeoutInfinite) ? pollTimeoutMinMs : pollTimeoutMs; if (s_rgIpcPollHandles[i].pStream == nullptr) { - // cache is empty, reconnect + // cache is empty, reconnect, e.g., there was a disconnect IpcStream *pConnection = nullptr; pConnection = s_rgIpcPollHandles[i].pIpc->Connect(callback); @@ -111,15 +111,22 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) s_rgIpcPollHandles[i].pStream = pConnection; pollTimeoutMs = pollTimeoutInfinite; - pIpcPollHandles.Push(&s_rgIpcPollHandles[i]); + rgpIpcPollHandles.Push(&s_rgIpcPollHandles[i]); } else { + // connection failed, increment timeout pollTimeoutMs = (pollTimeoutMs >= pollTimeoutMaxMs) ? pollTimeoutMaxMs : pollTimeoutMs * pollTimeoutFalloffFactor; } } + else + { + // reuse the existing connection + pollTimeoutMs = pollTimeoutInfinite; + rgpIpcPollHandles.Push(&s_rgIpcPollHandles[i]); + } } else { @@ -128,31 +135,31 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) { // TODO: error check the server failing to listen } - pIpcPollHandles.Push(&s_rgIpcPollHandles[i]); + rgpIpcPollHandles.Push(&s_rgIpcPollHandles[i]); } } - int32_t retval = IpcStream::DiagnosticsIpc::Poll(pIpcPollHandles.Ptr(), (uint32_t)pIpcPollHandles.Size(), pollTimeoutMs, callback); + int32_t retval = IpcStream::DiagnosticsIpc::Poll(rgpIpcPollHandles.Ptr(), (uint32_t)rgpIpcPollHandles.Size(), pollTimeoutMs, callback); nPollAttempts++; STRESS_LOG2(LF_DIAGNOSTICS_PORT, LL_INFO10, "IpcStreamFactory::GetNextAvailableStream - Poll attempt: %d, timeout: %dms.\n", nPollAttempts, pollTimeoutMs); if (retval != 0) { - for (uint32_t i = 0; i < (uint32_t)pIpcPollHandles.Size(); i++) + for (uint32_t i = 0; i < (uint32_t)rgpIpcPollHandles.Size(); i++) { - switch ((IpcStream::DiagnosticsIpc::PollEvents)pIpcPollHandles[i]->revents) + switch ((IpcStream::DiagnosticsIpc::PollEvents)rgpIpcPollHandles[i]->revents) { case IpcStream::DiagnosticsIpc::PollEvents::HANGUP: - delete pIpcPollHandles[i]->pStream; - pIpcPollHandles[i]->pStream = nullptr; // clear the cache of the hung up connection; will trigger a reconnect poll + delete rgpIpcPollHandles[i]->pStream; + rgpIpcPollHandles[i]->pStream = nullptr; // clear the cache of the hung up connection; will trigger a reconnect poll STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_INFO10, "IpcStreamFactory::GetNextAvailableStream - Poll attempt: %d, connection hung up.\n", nPollAttempts); pollTimeoutMs = pollTimeoutMinMs; break; case IpcStream::DiagnosticsIpc::PollEvents::SIGNALED: if (pStream == nullptr) // only use first signaled stream; will get others on subsequent calls { - pStream = pIpcPollHandles[i]->pStream; - pIpcPollHandles[i]->pStream = nullptr; // pass ownership to caller so we aren't caching the connection anymore + pStream = rgpIpcPollHandles[i]->pStream; + rgpIpcPollHandles[i]->pStream = nullptr; // pass ownership to caller so we aren't caching the connection anymore } break; case IpcStream::DiagnosticsIpc::PollEvents::ERR: @@ -164,8 +171,8 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) } // clear the view - while (pIpcPollHandles.Size() > 0) - pIpcPollHandles.Pop(); + while (rgpIpcPollHandles.Size() > 0) + rgpIpcPollHandles.Pop(); } return pStream; From e9059581d1f0e09c77084f91148c6dbeaf7a2be8 Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 30 Mar 2020 11:48:19 -0700 Subject: [PATCH 30/52] fix const-ness on unix --- src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 720c8d5b02c9b9..a7c308f30cbca5 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -307,7 +307,7 @@ IpcStream::~IpcStream() } } -bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) const +bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) { _ASSERTE(lpBuffer != nullptr); @@ -323,7 +323,7 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt return fSuccess; } -bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) const +bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) { _ASSERTE(lpBuffer != nullptr); From 89ce8420fcb7f8ac3464f227343538c0871dcf0d Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 30 Mar 2020 17:01:16 -0700 Subject: [PATCH 31/52] Add some asserts and comments --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 16 +++++++++ .../debug/debug-pal/win/diagnosticsipc.cpp | 33 ++++++++++++++----- src/coreclr/src/debug/inc/diagnosticsipc.h | 1 + 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index a7c308f30cbca5..649f9748f537cf 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -117,6 +117,14 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) { + _ASSERTE(mode == ConnectionMode::SERVER); + if (mode != ConnectionMode::SERVER) + { + if (callback != nullptr) + callback("Cannot call Listen on a client connection", -1); + return false; + } + if (_isListening) return true; @@ -143,6 +151,14 @@ bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) { + _ASSERTE(mode == ConnectionMode::CLIENT); + if (mode != ConnectionMode::CLIENT) + { + if (callback != nullptr) + callback("Cannot call connect on a server connection", 0); + return nullptr; + } + sockaddr_un clientAddress{}; clientAddress.sun_family = AF_UNIX; const int clientSocket = ::socket(AF_UNIX, SOCK_STREAM, 0); diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 12d84408676fda..2ba7f43c0c961a 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -57,11 +57,17 @@ IpcStream::DiagnosticsIpc *IpcStream::DiagnosticsIpc::Create(const char *const p bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) { + _ASSERTE(mode == ConnectionMode::SERVER); + if (mode != ConnectionMode::SERVER) + { + if (callback != nullptr) + callback("Cannot call Listen on a client connection", -1); + return false; + } + if (_isListening) return true; - _ASSERTE(mode == ConnectionMode::SERVER); - const uint32_t nInBufferSize = 16 * 1024; const uint32_t nOutBufferSize = 16 * 1024; _hPipe = ::CreateNamedPipeA( @@ -117,7 +123,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) if (mode != ConnectionMode::CLIENT) { if (callback != nullptr) - callback("Cannot call connect on a client connection", 0); + callback("Cannot call connect on a server connection", 0); return nullptr; } @@ -149,6 +155,11 @@ void IpcStream::DiagnosticsIpc::Close(ErrorCallback) const BOOL fSuccessDisconnectNamedPipe = ::DisconnectNamedPipe(_hPipe); _ASSERTE(fSuccessDisconnectNamedPipe != 0); } + + // make sure overlapped io is complete before cleaning + DWORD dwDummy = 0; + const BOOL fSuccessOverlappedComplete = ::GetOverlappedResult(_hPipe, &_oOverlap, &dwDummy, true); + _ASSERTE(fSuccessOverlappedComplete != 0); const BOOL fSuccessCloseHandle = ::CloseHandle(_hPipe); _ASSERTE(fSuccessCloseHandle != 0); @@ -180,6 +191,10 @@ IpcStream::~IpcStream() _ASSERTE(fSuccessDisconnectNamedPipe != 0); } + DWORD dwDummy = 0; + const BOOL fSuccessOverlappedComplete = ::GetOverlappedResult(_hPipe, &_oOverlap, &dwDummy, true); + _ASSERTE(fSuccessOverlappedComplete != 0); + const BOOL fSuccessCloseHandle = ::CloseHandle(_hPipe); _ASSERTE(fSuccessCloseHandle != 0); } @@ -206,12 +221,12 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles // check for data by doing an asynchronous 0 byte read. // This will signal if the pipe closes (hangup) or the server // sends new data - DWORD dummyDW = 0; + DWORD dwDummy = 0; bool fSuccess = ::ReadFile( rgpIpcPollHandles[i]->pStream->_hPipe, // handle nullptr, // null buffer 0, // read 0 bytes - &dummyDW, // dummy variable + &dwDummy, // dummy variable &rgpIpcPollHandles[i]->pStream->_oOverlap); // overlap object to use _ASSERTE(!fSuccess && ::GetLastError() == ERROR_IO_PENDING); pHandles[i] = rgpIpcPollHandles[i]->pStream->_oOverlap.hEvent; @@ -267,10 +282,10 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles if (rgpIpcPollHandles[index]->pIpc->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) { // check if the connection got hung up - DWORD dummyDW = 0; + DWORD dwDummy = 0; bool fSuccess = GetOverlappedResult(rgpIpcPollHandles[index]->pStream->_hPipe, &rgpIpcPollHandles[index]->pStream->_oOverlap, - &dummyDW, + &dwDummy, true); if (!fSuccess) { @@ -288,10 +303,10 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles else { // complete the async listen - DWORD dummyDW = 0; + DWORD dwDummy = 0; bool fSuccess = GetOverlappedResult(rgpIpcPollHandles[index]->pIpc->_hPipe, &rgpIpcPollHandles[index]->pIpc->_oOverlap, - &dummyDW, + &dwDummy, true); if (!fSuccess) { diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index 06a2c54b1db0a7..2e04f160e00173 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -40,6 +40,7 @@ class IpcStream final ERR = 0x04 // other error }; + // The bookeeping struct used for polling on server and client structs struct IpcPollHandle { DiagnosticsIpc *pIpc; From 8adb3f9aaf598b845d3c52be95e984535c3a41fe Mon Sep 17 00:00:00 2001 From: John Salem Date: Tue, 31 Mar 2020 16:28:44 -0700 Subject: [PATCH 32/52] Fix AV in shutdown path --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 5 +++ .../debug/debug-pal/win/diagnosticsipc.cpp | 31 ++++++++++++------- src/coreclr/src/debug/inc/diagnosticsipc.h | 1 + src/coreclr/src/vm/ipcstreamfactory.cpp | 20 ++++++++---- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 649f9748f537cf..0d6349537568f7 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -313,6 +313,11 @@ void IpcStream::DiagnosticsIpc::Unlink(ErrorCallback callback) } IpcStream::~IpcStream() +{ + Close(); +} + +void IpcStream::Close(ErrorCallback) { if (_clientSocket != -1) { diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 2ba7f43c0c961a..d434f0c241ce6c 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -155,11 +155,6 @@ void IpcStream::DiagnosticsIpc::Close(ErrorCallback) const BOOL fSuccessDisconnectNamedPipe = ::DisconnectNamedPipe(_hPipe); _ASSERTE(fSuccessDisconnectNamedPipe != 0); } - - // make sure overlapped io is complete before cleaning - DWORD dwDummy = 0; - const BOOL fSuccessOverlappedComplete = ::GetOverlappedResult(_hPipe, &_oOverlap, &dwDummy, true); - _ASSERTE(fSuccessOverlappedComplete != 0); const BOOL fSuccessCloseHandle = ::CloseHandle(_hPipe); _ASSERTE(fSuccessCloseHandle != 0); @@ -180,6 +175,11 @@ IpcStream::IpcStream(HANDLE hPipe, DiagnosticsIpc::ConnectionMode mode) : } IpcStream::~IpcStream() +{ + Close(); +} + +void IpcStream::Close(ErrorCallback) { if (_hPipe != INVALID_HANDLE_VALUE) { @@ -191,10 +191,6 @@ IpcStream::~IpcStream() _ASSERTE(fSuccessDisconnectNamedPipe != 0); } - DWORD dwDummy = 0; - const BOOL fSuccessOverlappedComplete = ::GetOverlappedResult(_hPipe, &_oOverlap, &dwDummy, true); - _ASSERTE(fSuccessOverlappedComplete != 0); - const BOOL fSuccessCloseHandle = ::CloseHandle(_hPipe); _ASSERTE(fSuccessCloseHandle != 0); } @@ -228,8 +224,15 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles 0, // read 0 bytes &dwDummy, // dummy variable &rgpIpcPollHandles[i]->pStream->_oOverlap); // overlap object to use - _ASSERTE(!fSuccess && ::GetLastError() == ERROR_IO_PENDING); - pHandles[i] = rgpIpcPollHandles[i]->pStream->_oOverlap.hEvent; + if (!fSuccess && ::GetLastError() == ERROR_IO_PENDING) + pHandles[i] = rgpIpcPollHandles[i]->pStream->_oOverlap.hEvent; + else + { + if (callback != nullptr) + callback("0 byte async read on client connection failed", -1); + delete[] pHandles; + return -1; + } } } @@ -293,7 +296,13 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles if (error == ERROR_PIPE_NOT_CONNECTED) rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::HANGUP; else + { + if (callback != nullptr) + callback("Client connection error", -1); rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::ERR; + delete[] pHandles; + return -1; + } } else { diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index 2e04f160e00173..f7d4587a263d49 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -22,6 +22,7 @@ class IpcStream final bool Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead); bool Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten); bool Flush() const; + void Close(ErrorCallback callback = nullptr); class DiagnosticsIpc final { diff --git a/src/coreclr/src/vm/ipcstreamfactory.cpp b/src/coreclr/src/vm/ipcstreamfactory.cpp index 885aeb7462c544..79ad295b0d986e 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.cpp +++ b/src/coreclr/src/vm/ipcstreamfactory.cpp @@ -55,13 +55,20 @@ bool IpcStreamFactory::HasActiveConnections() void IpcStreamFactory::CloseConnections() { - while (s_rgIpcPollHandles.Size() > 0) + auto ErrorCallback = [](const char *szMessage, uint32_t code) { + STRESS_LOG2( + LF_DIAGNOSTICS_PORT, // facility + LL_ERROR, // level + "Failed to close diagnostic IPC: error (%d): %s.\n", // msg + code, // data1 + szMessage); // data2 + }; + for (uint32_t i = 0; i < (uint32_t)s_rgIpcPollHandles.Size(); i++) { - IpcStream::DiagnosticsIpc::IpcPollHandle ipcPollHandle = s_rgIpcPollHandles.Pop(); - if (ipcPollHandle.pStream != nullptr) - delete ipcPollHandle.pStream; - if (ipcPollHandle.pIpc != nullptr) - delete ipcPollHandle.pIpc; + if (s_rgIpcPollHandles[i].pStream != nullptr) + s_rgIpcPollHandles[i].pStream->Close(ErrorCallback); + if (s_rgIpcPollHandles[i].pIpc != nullptr) + s_rgIpcPollHandles[i].pIpc->Close(ErrorCallback); } } @@ -163,6 +170,7 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) } break; case IpcStream::DiagnosticsIpc::PollEvents::ERR: + return nullptr; default: // TODO: Error handling break; From 23f1d00c03187ff4d36af46ac12447433d3c99ec Mon Sep 17 00:00:00 2001 From: John Salem Date: Wed, 1 Apr 2020 12:13:42 -0700 Subject: [PATCH 33/52] Add initial test harness and sample tests --- .../src/tracing/eventpipe/common/Reverse.cs | 121 ++++++++++++++++++ .../tracing/eventpipe/common/common.csproj | 1 + .../src/tracing/eventpipe/reverse/reverse.cs | 108 ++++++++++++++++ .../tracing/eventpipe/reverse/reverse.csproj | 15 +++ 4 files changed, 245 insertions(+) create mode 100644 src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs create mode 100644 src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs create mode 100644 src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.csproj diff --git a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs new file mode 100644 index 00000000000000..f37f49b70f22d9 --- /dev/null +++ b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.IO.Pipes; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Tracing.Tests.Common +{ + /** + * ==ADVERTISE PROTOCOL== + * Before standard IPC Protocol communication can occur on a client-mode connection + * the runtime must advertise itself over the connection. ALL SUBSEQUENT COMMUNICATION + * IS STANDARD DIAGNOSTICS IPC PROTOCOL COMMUNICATION. + * + * The flow for Advertise is a one-way burst of 32 bytes consisting of + * 8 bytes - "ADVR_V1\0" (ASCII chars + null byte) + * 16 bytes - CLR Instance Cookie (little-endian) + * 8 bytes - PID (little-endian) + */ + + public class IpcAdvertise + { + public static int Size_V1 => 32; + public static byte[] Magic_V1 => System.Text.Encoding.ASCII.GetBytes("ADVR_V1" + '\0'); + public static int MagicSize_V1 => 8; + + public byte[] Magic = Magic_V1; + public UInt64 ProcessId; + public Guid RuntimeInstanceCookie; + + /// + /// + /// + /// (pid, clrInstanceId) + public static IpcAdvertise Parse(Stream stream) + { + var binaryReader = new BinaryReader(stream); + var advertise = new IpcAdvertise() + { + Magic = binaryReader.ReadBytes(Magic_V1.Length), + RuntimeInstanceCookie = new Guid(binaryReader.ReadBytes(16)), + ProcessId = binaryReader.ReadUInt64() + }; + + for (int i = 0; i < Magic_V1.Length; i++) + if (advertise.Magic[i] != Magic_V1[i]) + throw new Exception("Invalid advertise message from client connection"); + + // FUTURE: switch on incoming magic and change if version ever increments + return advertise; + } + + override public string ToString() + { + return $"{{ Magic={Magic}; ClrInstanceId={RuntimeInstanceCookie}; ProcessId={ProcessId}; }}"; + } + } + public static class ReverseServer + { + public static string MakeServerAddress() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Path.GetRandomFileName(); + } + else + { + return Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + } + } + + // Creates the server, listens, and closes the server + public static async Task CreateServerAndReceiveAdvertisement(string serverAddress) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + using var serverStream = new NamedPipeServerStream(serverAddress); + Logger.logger.Log("Waiting for connection"); + await serverStream.WaitForConnectionAsync(); + Logger.logger.Log("Got a connection"); + IpcAdvertise advertise = IpcAdvertise.Parse(serverStream); + serverStream.Disconnect(); + return advertise; + } + else + { + if (File.Exists(serverAddress)) + File.Delete(serverAddress); + var remoteEP = new UnixDomainSocketEndPoint(serverAddress); + + using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + socket.Bind(remoteEP); + socket.Listen(255); + socket.LingerState.Enabled = false; + Logger.logger.Log("Waiting for connection"); + using Socket clientSocket = await socket.AcceptAsync(); + Logger.logger.Log("Got a connection"); + using var socketStream = new NetworkStream(clientSocket); + IpcAdvertise advertise = IpcAdvertise.Parse(socketStream); + try + { + socket.Shutdown(SocketShutdown.Both); + } + finally + { + clientSocket.Close(); + socket.Close(); + if (File.Exists(serverAddress)) + File.Delete(serverAddress); + } + + return advertise; + } + } + } +} \ No newline at end of file diff --git a/src/coreclr/tests/src/tracing/eventpipe/common/common.csproj b/src/coreclr/tests/src/tracing/eventpipe/common/common.csproj index 0df38b391df232..a0b36c8336dcc6 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/common/common.csproj +++ b/src/coreclr/tests/src/tracing/eventpipe/common/common.csproj @@ -9,5 +9,6 @@ + diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs new file mode 100644 index 00000000000000..407be2c3b24616 --- /dev/null +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Tracing; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using Microsoft.Diagnostics.Tools.RuntimeClient; +using Tracing.Tests.Common; + +namespace Tracing.Tests.ReverseValidation +{ + public class ReverseValidation + { + public static async Task RunSubprocess(string serverName, Func beforeExecution = null, Func duringExecution = null, Func afterExecution = null) + { + using (var process = new Process()) + { + if (beforeExecution != null) + await beforeExecution(); + + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.Environment.Add("DOTNET_DiagnosticsMonitorAddress", serverName); + process.StartInfo.FileName = Process.GetCurrentProcess().MainModule.FileName; + process.StartInfo.Arguments = Assembly.GetExecutingAssembly().CodeBase + " 0"; + Logger.logger.Log($"running sub-process: {process.StartInfo.FileName} {process.StartInfo.Arguments}"); + bool fSuccess = process.Start(); + Logger.logger.Log($"subprocess started: {fSuccess}"); + + if (duringExecution != null) + await duringExecution(); + + process.Kill(); + + if (afterExecution != null) + await afterExecution(); + } + } + public static async Task TEST_RuntimeIsResilientToServerClosing() + { + string serverName = ReverseServer.MakeServerAddress(); + Logger.logger.Log($"Server name is '{serverName}'"); + await RunSubprocess( + serverName: serverName, + duringExecution: async () => + { + var ad1 = await ReverseServer.CreateServerAndReceiveAdvertisement(serverName); + Logger.logger.Log(ad1.ToString()); + var ad2 = await ReverseServer.CreateServerAndReceiveAdvertisement(serverName); + Logger.logger.Log(ad2.ToString()); + var ad3 = await ReverseServer.CreateServerAndReceiveAdvertisement(serverName); + Logger.logger.Log(ad3.ToString()); + var ad4 = await ReverseServer.CreateServerAndReceiveAdvertisement(serverName); + Logger.logger.Log(ad4.ToString()); + } + ); + + return true; + } + + public static async Task TEST_RuntimeConnectsToExistingServer() + { + string serverName = ReverseServer.MakeServerAddress(); + Task advertiseTask = ReverseServer.CreateServerAndReceiveAdvertisement(serverName); + Logger.logger.Log($"Server name is `{serverName}`"); + await RunSubprocess( + serverName: serverName, + duringExecution: async () => + { + IpcAdvertise advertise = await advertiseTask; + Logger.logger.Log(advertise.ToString()); + } + ); + + return true; + } + + + public static async Task Main(string[] args) + { + if (args.Length >= 1) + { + await Task.Delay(-1); // will be killed in test + return 1; + } + + bool fSuccess = true; + IEnumerable tests = typeof(ReverseValidation).GetMethods().Where(mi => mi.Name.StartsWith("TEST_")); + foreach (var test in tests) + { + Logger.logger.Log($"Running test: {test.Name}"); + bool result = await (Task)test.Invoke(null, new object[] {}); + fSuccess &= result; + Logger.logger.Log($"Test passed: {result}"); + + } + return fSuccess ? 100 : -1; + } + } +} \ No newline at end of file diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.csproj b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.csproj new file mode 100644 index 00000000000000..2c10c6ed465339 --- /dev/null +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.csproj @@ -0,0 +1,15 @@ + + + .NETCoreApp + exe + BuildAndRun + true + 0 + true + true + + + + + + \ No newline at end of file From 76b8c046e9906efc3484af6aab66f0bd05efa83e Mon Sep 17 00:00:00 2001 From: John Salem Date: Wed, 1 Apr 2020 15:40:27 -0700 Subject: [PATCH 34/52] fix test issue on mac * catch exceptions for socket shutdown * fix path to dll --- src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs | 1 + src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs index f37f49b70f22d9..7ef8c551f79ba8 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs @@ -106,6 +106,7 @@ public static async Task CreateServerAndReceiveAdvertisement(strin { socket.Shutdown(SocketShutdown.Both); } + catch (Exception e) {} finally { clientSocket.Close(); diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs index 407be2c3b24616..72b379b9b47970 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -30,7 +30,7 @@ public static async Task RunSubprocess(string serverName, Func beforeExecu process.StartInfo.CreateNoWindow = true; process.StartInfo.Environment.Add("DOTNET_DiagnosticsMonitorAddress", serverName); process.StartInfo.FileName = Process.GetCurrentProcess().MainModule.FileName; - process.StartInfo.Arguments = Assembly.GetExecutingAssembly().CodeBase + " 0"; + process.StartInfo.Arguments = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath + " 0"; Logger.logger.Log($"running sub-process: {process.StartInfo.FileName} {process.StartInfo.Arguments}"); bool fSuccess = process.Start(); Logger.logger.Log($"subprocess started: {fSuccess}"); @@ -96,7 +96,7 @@ public static async Task Main(string[] args) IEnumerable tests = typeof(ReverseValidation).GetMethods().Where(mi => mi.Name.StartsWith("TEST_")); foreach (var test in tests) { - Logger.logger.Log($"Running test: {test.Name}"); + Logger.logger.Log($"::== Running test: {test.Name}"); bool result = await (Task)test.Invoke(null, new object[] {}); fSuccess &= result; Logger.logger.Log($"Test passed: {result}"); From d651b7dfb23e6f6b6ecde622f43b810af276eabd Mon Sep 17 00:00:00 2001 From: John Salem Date: Thu, 2 Apr 2020 10:26:52 -0700 Subject: [PATCH 35/52] Fix bad access in unix diag ipc --- src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp | 6 +++--- src/coreclr/src/debug/inc/diagnosticsipc.h | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 0d6349537568f7..8a872557d25efd 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -179,7 +179,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) return nullptr; } - return new IpcStream(clientSocket, -1, ConnectionMode::CLIENT); + return new IpcStream(clientSocket, ConnectionMode::CLIENT); } int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback) @@ -254,7 +254,7 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles { sockaddr_un from; socklen_t fromlen = sizeof(from); - const int clientSocket = ::accept(rgpIpcPollHandles[i]->pStream->_serverSocket, (sockaddr *)&from, &fromlen); + const int clientSocket = ::accept(rgpIpcPollHandles[i]->pIpc->_serverSocket, (sockaddr *)&from, &fromlen); if (clientSocket == -1) { if (callback != nullptr) @@ -263,7 +263,7 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles delete[] pollfds; return -1; } - rgpIpcPollHandles[i]->pStream = new IpcStream(clientSocket, rgpIpcPollHandles[i]->pIpc->_serverSocket, rgpIpcPollHandles[i]->pIpc->mode); + rgpIpcPollHandles[i]->pStream = new IpcStream(clientSocket, rgpIpcPollHandles[i]->pIpc->mode); rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::SIGNALED; } else diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index f7d4587a263d49..3ec2b97020a956 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -124,9 +124,8 @@ class IpcStream final private: #ifdef TARGET_UNIX int _clientSocket = -1; - int _serverSocket = -1; IpcStream(int clientSocket, int serverSocket, DiagnosticsIpc::ConnectionMode mode = DiagnosticsIpc::ConnectionMode::SERVER) - : _clientSocket(clientSocket), _serverSocket(serverSocket), _mode(mode) {} + : _clientSocket(clientSocket), _mode(mode) {} #else HANDLE _hPipe = INVALID_HANDLE_VALUE; OVERLAPPED _oOverlap = {}; From 5a3fe23dfdf470b7b3834b04eda4f18846427e46 Mon Sep 17 00:00:00 2001 From: John Salem Date: Thu, 2 Apr 2020 13:52:00 -0700 Subject: [PATCH 36/52] More tests --- .../src/tracing/eventpipe/common/Reverse.cs | 136 ++++++++++--- .../src/tracing/eventpipe/reverse/reverse.cs | 181 ++++++++++++++++-- 2 files changed, 274 insertions(+), 43 deletions(-) diff --git a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs index 7ef8c551f79ba8..4629f8bf03e93f 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs @@ -60,7 +60,7 @@ override public string ToString() return $"{{ Magic={Magic}; ClrInstanceId={RuntimeInstanceCookie}; ProcessId={ProcessId}; }}"; } } - public static class ReverseServer + public class ReverseServer { public static string MakeServerAddress() { @@ -74,18 +74,16 @@ public static string MakeServerAddress() } } - // Creates the server, listens, and closes the server - public static async Task CreateServerAndReceiveAdvertisement(string serverAddress) + private object _server; // _server ::= socket | NamedPipeServerStream + private Socket _clientSocket; // only used on non-Windows + private string _serverAddress; + + public ReverseServer(string serverAddress) { + _serverAddress = serverAddress; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - using var serverStream = new NamedPipeServerStream(serverAddress); - Logger.logger.Log("Waiting for connection"); - await serverStream.WaitForConnectionAsync(); - Logger.logger.Log("Got a connection"); - IpcAdvertise advertise = IpcAdvertise.Parse(serverStream); - serverStream.Disconnect(); - return advertise; + _server = new NamedPipeServerStream(serverAddress); } else { @@ -93,30 +91,108 @@ public static async Task CreateServerAndReceiveAdvertisement(strin File.Delete(serverAddress); var remoteEP = new UnixDomainSocketEndPoint(serverAddress); - using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); socket.Bind(remoteEP); socket.Listen(255); socket.LingerState.Enabled = false; - Logger.logger.Log("Waiting for connection"); - using Socket clientSocket = await socket.AcceptAsync(); - Logger.logger.Log("Got a connection"); - using var socketStream = new NetworkStream(clientSocket); - IpcAdvertise advertise = IpcAdvertise.Parse(socketStream); - try - { - socket.Shutdown(SocketShutdown.Both); - } - catch (Exception e) {} - finally - { - clientSocket.Close(); - socket.Close(); - if (File.Exists(serverAddress)) - File.Delete(serverAddress); - } - - return advertise; + _server = socket; } } + + public async Task AcceptAsync() + { + switch (_server) + { + case NamedPipeServerStream serverStream: + await serverStream.WaitForConnectionAsync(); + return serverStream; + case Socket socket: + _clientSocket = await socket.AcceptAsync(); + return new NetworkStream(_clientSocket); + default: + throw new ArgumentException("Invalid server type"); + } + } + + public void Shutdown() + { + switch (_server) + { + case NamedPipeServerStream serverStream: + serverStream.Disconnect(); + serverStream.Dispose(); + break; + case Socket socket: + try + { + socket.Shutdown(SocketShutdown.Both); + } + catch (Exception e) {} + finally + { + _clientSocket?.Close(); + socket.Close(); + socket.Dispose(); + _clientSocket?.Dispose(); + if (File.Exists(_serverAddress)) + File.Delete(_serverAddress); + } + break; + default: + throw new ArgumentException("Invalid server type"); + } + } + + // Creates the server, listens, and closes the server + public static async Task CreateServerAndReceiveAdvertisement(string serverAddress) + { + var server = new ReverseServer(serverAddress); + Logger.logger.Log("Waiting for connection"); + Stream stream = await server.AcceptAsync(); + Logger.logger.Log("Got a connection"); + IpcAdvertise advertise = IpcAdvertise.Parse(stream); + server.Shutdown(); + return advertise; + // if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + // { + // using var serverStream = new NamedPipeServerStream(serverAddress); + // Logger.logger.Log("Waiting for connection"); + // await serverStream.WaitForConnectionAsync(); + // Logger.logger.Log("Got a connection"); + // IpcAdvertise advertise = IpcAdvertise.Parse(serverStream); + // serverStream.Disconnect(); + // return advertise; + // } + // else + // { + // if (File.Exists(serverAddress)) + // File.Delete(serverAddress); + // var remoteEP = new UnixDomainSocketEndPoint(serverAddress); + + // using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + // socket.Bind(remoteEP); + // socket.Listen(255); + // socket.LingerState.Enabled = false; + // Logger.logger.Log("Waiting for connection"); + // using Socket clientSocket = await socket.AcceptAsync(); + // Logger.logger.Log("Got a connection"); + // using var socketStream = new NetworkStream(clientSocket); + // IpcAdvertise advertise = IpcAdvertise.Parse(socketStream); + // try + // { + // socket.Shutdown(SocketShutdown.Both); + // } + // catch (Exception e) {} + // finally + // { + // clientSocket.Close(); + // socket.Close(); + // if (File.Exists(serverAddress)) + // File.Delete(serverAddress); + // } + + // return advertise; + // } + } } } \ No newline at end of file diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs index 72b379b9b47970..a2b234de9c31ca 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -5,21 +5,40 @@ using System; using System.Diagnostics.Tracing; using System.Diagnostics; -using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; using System.Reflection; -using System.Reflection.Emit; using Microsoft.Diagnostics.Tools.RuntimeClient; using Tracing.Tests.Common; +using System.Threading; +using System.IO; +using Microsoft.Diagnostics.Tracing; namespace Tracing.Tests.ReverseValidation { public class ReverseValidation { - public static async Task RunSubprocess(string serverName, Func beforeExecution = null, Func duringExecution = null, Func afterExecution = null) + // The runtime will do an exponential falloff by a factor of 2 starting at 250ms + // We can time tests out after waiting AT MOST 61,750 ms which should contain 7 attempts to connect + private static int _maxPollTimeMS = /* 250 + 500 + 1000 + 2000 + 4000 + 8000 + 16000 + 30000 = */ 61_750; + + private static async Task WaitTillTimeout(Task task, TimeSpan timeout) + { + using var cts = new CancellationTokenSource(); + var completedTask = await Task.WhenAny(task, Task.Delay(timeout, cts.Token)); + if (completedTask == task) + { + cts.Cancel(); + return await task; + } + else + { + throw new TimeoutException("Task timed out"); + } + } + + public static async Task RunSubprocess(string serverName, Func beforeExecution = null, Func duringExecution = null, Func afterExecution = null) { using (var process = new Process()) { @@ -35,8 +54,9 @@ public static async Task RunSubprocess(string serverName, Func beforeExecu bool fSuccess = process.Start(); Logger.logger.Log($"subprocess started: {fSuccess}"); + await Task.Delay(250); if (duringExecution != null) - await duringExecution(); + await duringExecution(process.Id); process.Kill(); @@ -44,21 +64,22 @@ public static async Task RunSubprocess(string serverName, Func beforeExecu await afterExecution(); } } + public static async Task TEST_RuntimeIsResilientToServerClosing() { string serverName = ReverseServer.MakeServerAddress(); Logger.logger.Log($"Server name is '{serverName}'"); await RunSubprocess( serverName: serverName, - duringExecution: async () => + duringExecution: async (_) => { - var ad1 = await ReverseServer.CreateServerAndReceiveAdvertisement(serverName); + var ad1 = await WaitTillTimeout(ReverseServer.CreateServerAndReceiveAdvertisement(serverName), TimeSpan.FromMilliseconds(_maxPollTimeMS)); Logger.logger.Log(ad1.ToString()); - var ad2 = await ReverseServer.CreateServerAndReceiveAdvertisement(serverName); + var ad2 = await WaitTillTimeout(ReverseServer.CreateServerAndReceiveAdvertisement(serverName), TimeSpan.FromMilliseconds(_maxPollTimeMS)); Logger.logger.Log(ad2.ToString()); - var ad3 = await ReverseServer.CreateServerAndReceiveAdvertisement(serverName); + var ad3 = await WaitTillTimeout(ReverseServer.CreateServerAndReceiveAdvertisement(serverName), TimeSpan.FromMilliseconds(_maxPollTimeMS)); Logger.logger.Log(ad3.ToString()); - var ad4 = await ReverseServer.CreateServerAndReceiveAdvertisement(serverName); + var ad4 = await WaitTillTimeout(ReverseServer.CreateServerAndReceiveAdvertisement(serverName), TimeSpan.FromMilliseconds(_maxPollTimeMS)); Logger.logger.Log(ad4.ToString()); } ); @@ -73,9 +94,9 @@ public static async Task TEST_RuntimeConnectsToExistingServer() Logger.logger.Log($"Server name is `{serverName}`"); await RunSubprocess( serverName: serverName, - duringExecution: async () => + duringExecution: async (_) => { - IpcAdvertise advertise = await advertiseTask; + IpcAdvertise advertise = await WaitTillTimeout(advertiseTask, TimeSpan.FromMilliseconds(_maxPollTimeMS)); Logger.logger.Log(advertise.ToString()); } ); @@ -84,6 +105,130 @@ await RunSubprocess( } + public static async Task TEST_CanConnectServerAndClientAtSameTime() + { + string serverName = ReverseServer.MakeServerAddress(); + Logger.logger.Log($"Server name is '{serverName}'"); + var server = new ReverseServer(serverName); + await RunSubprocess( + serverName: serverName, + duringExecution: async (int pid) => + { + Task reverseTask = Task.Run(async () => + { + Logger.logger.Log($"Waiting for reverse connection"); + Stream reverseStream = await server.AcceptAsync(); + Logger.logger.Log("Got reverse connection"); + IpcAdvertise advertise = IpcAdvertise.Parse(reverseStream); + Logger.logger.Log(advertise.ToString()); + }); + + Task regularTask = Task.Run(async () => + { + var config = new SessionConfiguration( + circularBufferSizeMB: 1000, + format: EventPipeSerializationFormat.NetTrace, + providers: new List { + new Provider("Microsoft-DotNETCore-SampleProfiler") + }); + Logger.logger.Log("Starting EventPipeSession over standard connection"); + using Stream stream = EventPipeClient.CollectTracing(pid, config, out var sessionId); + Logger.logger.Log($"Started EventPipeSession over standard connection with session id: 0x{sessionId:x}"); + using var source = new EventPipeEventSource(stream); + Task readerTask = Task.Run(() => source.Process()); + await Task.Delay(500); + Logger.logger.Log("Stopping EventPipeSession over standard connection"); + EventPipeClient.StopTracing(pid, sessionId); + await readerTask; + Logger.logger.Log("Stopped EventPipeSession over standard connection"); + }); + + await Task.WhenAll(reverseTask, regularTask); + } + ); + + server.Shutdown(); + + return true; + } + + public static async Task TEST_ReverseConnectionCanRecycleWhileTracing() + { + string serverName = ReverseServer.MakeServerAddress(); + Logger.logger.Log($"Server name is '{serverName}'"); + await RunSubprocess( + serverName: serverName, + duringExecution: async (int pid) => + { + Task regularTask = Task.Run(async () => + { + var config = new SessionConfiguration( + circularBufferSizeMB: 1000, + format: EventPipeSerializationFormat.NetTrace, + providers: new List { + new Provider("Microsoft-DotNETCore-SampleProfiler") + }); + Logger.logger.Log("Starting EventPipeSession over standard connection"); + using Stream stream = EventPipeClient.CollectTracing(pid, config, out var sessionId); + Logger.logger.Log($"Started EventPipeSession over standard connection with session id: 0x{sessionId:x}"); + using var source = new EventPipeEventSource(stream); + Task readerTask = Task.Run(() => source.Process()); + await Task.Delay(500); + Logger.logger.Log("Stopping EventPipeSession over standard connection"); + EventPipeClient.StopTracing(pid, sessionId); + await readerTask; + Logger.logger.Log("Stopped EventPipeSession over standard connection"); + }); + + Task reverseTask = Task.Run(async () => + { + var ad1 = await WaitTillTimeout(ReverseServer.CreateServerAndReceiveAdvertisement(serverName), TimeSpan.FromMilliseconds(_maxPollTimeMS)); + Logger.logger.Log(ad1.ToString()); + var ad2 = await WaitTillTimeout(ReverseServer.CreateServerAndReceiveAdvertisement(serverName), TimeSpan.FromMilliseconds(_maxPollTimeMS)); + Logger.logger.Log(ad2.ToString()); + var ad3 = await WaitTillTimeout(ReverseServer.CreateServerAndReceiveAdvertisement(serverName), TimeSpan.FromMilliseconds(_maxPollTimeMS)); + Logger.logger.Log(ad3.ToString()); + var ad4 = await WaitTillTimeout(ReverseServer.CreateServerAndReceiveAdvertisement(serverName), TimeSpan.FromMilliseconds(_maxPollTimeMS)); + Logger.logger.Log(ad4.ToString()); + }); + + await Task.WhenAll(reverseTask, regularTask); + } + ); + + return true; + } + + public static async Task TEST_StandardConnectionStillWorksIfReverseConnectionIsBroken() + { + string serverName = ReverseServer.MakeServerAddress(); + Logger.logger.Log($"Server name is '{serverName}'"); + await RunSubprocess( + serverName: serverName, + duringExecution: async (int pid) => + { + var config = new SessionConfiguration( + circularBufferSizeMB: 1000, + format: EventPipeSerializationFormat.NetTrace, + providers: new List { + new Provider("Microsoft-DotNETCore-SampleProfiler") + }); + Logger.logger.Log("Starting EventPipeSession over standard connection"); + using Stream stream = EventPipeClient.CollectTracing(pid, config, out var sessionId); + Logger.logger.Log($"Started EventPipeSession over standard connection with session id: 0x{sessionId:x}"); + using var source = new EventPipeEventSource(stream); + Task readerTask = Task.Run(() => source.Process()); + await Task.Delay(500); + Logger.logger.Log("Stopping EventPipeSession over standard connection"); + EventPipeClient.StopTracing(pid, sessionId); + await readerTask; + Logger.logger.Log("Stopped EventPipeSession over standard connection"); + } + ); + + return true; + } + public static async Task Main(string[] args) { if (args.Length >= 1) @@ -97,9 +242,19 @@ public static async Task Main(string[] args) foreach (var test in tests) { Logger.logger.Log($"::== Running test: {test.Name}"); - bool result = await (Task)test.Invoke(null, new object[] {}); + bool result = true; + try + { + result = await (Task)test.Invoke(null, new object[] {}); + } + catch (Exception e) + { + result = false; + Logger.logger.Log(e.ToString()); + } fSuccess &= result; Logger.logger.Log($"Test passed: {result}"); + Logger.logger.Log($""); } return fSuccess ? 100 : -1; From f44fd0151283ba16d9510eaeaa2557c91746c128 Mon Sep 17 00:00:00 2001 From: John Salem Date: Fri, 3 Apr 2020 18:25:21 -0700 Subject: [PATCH 37/52] Fix race in Windows Poll code * if overlapped read finished before WFMO we'd get stuck in an inf loop * prevented multiple overlapped io from happening on the same overlap * Race only seemed to happen in Windows checked builds for some reason --- .../debug/debug-pal/win/diagnosticsipc.cpp | 50 +++++++++++++------ src/coreclr/src/debug/inc/diagnosticsipc.h | 1 + 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index d434f0c241ce6c..2f5aa3f67297c9 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -214,24 +214,44 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles } else { - // check for data by doing an asynchronous 0 byte read. - // This will signal if the pipe closes (hangup) or the server - // sends new data + bool fSuccess = false; DWORD dwDummy = 0; - bool fSuccess = ::ReadFile( - rgpIpcPollHandles[i]->pStream->_hPipe, // handle - nullptr, // null buffer - 0, // read 0 bytes - &dwDummy, // dummy variable - &rgpIpcPollHandles[i]->pStream->_oOverlap); // overlap object to use - if (!fSuccess && ::GetLastError() == ERROR_IO_PENDING) - pHandles[i] = rgpIpcPollHandles[i]->pStream->_oOverlap.hEvent; + if (!rgpIpcPollHandles[i]->pStream->_isTestReading) + { + // check for data by doing an asynchronous 0 byte read. + // This will signal if the pipe closes (hangup) or the server + // sends new data + fSuccess = ::ReadFile( + rgpIpcPollHandles[i]->pStream->_hPipe, // handle + nullptr, // null buffer + 0, // read 0 bytes + &dwDummy, // dummy variable + &rgpIpcPollHandles[i]->pStream->_oOverlap); // overlap object to use + rgpIpcPollHandles[i]->pStream->_isTestReading = true; + if (!fSuccess) + { + DWORD error = ::GetLastError(); + switch (error) + { + case ERROR_IO_PENDING: + pHandles[i] = rgpIpcPollHandles[i]->pStream->_oOverlap.hEvent; + break; + case ERROR_PIPE_NOT_CONNECTED: + // hangup + rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::HANGUP; + delete[] pHandles; + return -1; + default: + if (callback != nullptr) + callback("0 byte async read on client connection failed", error); + delete[] pHandles; + return -1; + } + } + } else { - if (callback != nullptr) - callback("0 byte async read on client connection failed", -1); - delete[] pHandles; - return -1; + pHandles[i] = rgpIpcPollHandles[i]->pStream->_oOverlap.hEvent; } } } diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index 3ec2b97020a956..aaf4a10a94765d 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -129,6 +129,7 @@ class IpcStream final #else HANDLE _hPipe = INVALID_HANDLE_VALUE; OVERLAPPED _oOverlap = {}; + BOOL _isTestReading = false; // used to check whether we are already doing a 0-byte read to test for data IpcStream(HANDLE hPipe, DiagnosticsIpc::ConnectionMode mode = DiagnosticsIpc::ConnectionMode::SERVER); #endif /* TARGET_UNIX */ From 3a4e77e59e2a783cfe21149d4d12471453a3411c Mon Sep 17 00:00:00 2001 From: John Salem Date: Fri, 3 Apr 2020 18:26:39 -0700 Subject: [PATCH 38/52] Test updates * Make sure remote proc is always killed * set timeout to 10 minutes --- .../src/tracing/eventpipe/reverse/reverse.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs index a2b234de9c31ca..638ddc0a570877 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -55,10 +55,20 @@ public static async Task RunSubprocess(string serverName, Func beforeExecu Logger.logger.Log($"subprocess started: {fSuccess}"); await Task.Delay(250); - if (duringExecution != null) - await duringExecution(process.Id); + try + { + if (duringExecution != null) + await duringExecution(process.Id); + } + catch (Exception e) + { + throw e; + } + finally + { + process.Kill(); + } - process.Kill(); if (afterExecution != null) await afterExecution(); @@ -233,7 +243,7 @@ public static async Task Main(string[] args) { if (args.Length >= 1) { - await Task.Delay(-1); // will be killed in test + await Task.Delay(TimeSpan.FromMinutes(10)); // will be killed in test return 1; } From 469d425c659b2175fe76521f2aef7496ec2ba0f3 Mon Sep 17 00:00:00 2001 From: John Salem Date: Wed, 8 Apr 2020 09:54:11 -0700 Subject: [PATCH 39/52] fix nits from review --- src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp | 3 ++- src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 8a872557d25efd..fbb943567e7af9 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -239,6 +239,7 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles // will technically meet the requirements for POLLIN // i.e., a call to recv/read won't block rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::HANGUP; + delete[] pollfds; return -1; } else if ((pollfds[i].revents & (POLLERR|POLLNVAL))) @@ -246,6 +247,7 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles if (callback != nullptr) callback("Poll error", (uint32_t)pollfds[i].revents); rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::ERR; + delete[] pollfds; return -1; } else if (pollfds[i].revents & POLLIN) @@ -268,7 +270,6 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles } else { - // *ppStream = ppStreams[i]; rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::SIGNALED; } break; diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs index 638ddc0a570877..ba467f3463259b 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -60,10 +60,6 @@ public static async Task RunSubprocess(string serverName, Func beforeExecu if (duringExecution != null) await duringExecution(process.Id); } - catch (Exception e) - { - throw e; - } finally { process.Kill(); From d93d846432c9bd0af1d7092bd241b94a28ec144e Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 13 Apr 2020 16:33:00 -0700 Subject: [PATCH 40/52] Add ConnectionState abstraction * Adds ConnectionState class for hiding server/client diff * simplifies code for easier reading --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 62 +++--- .../debug/debug-pal/win/diagnosticsipc.cpp | 102 +++++---- src/coreclr/src/debug/inc/diagnosticsipc.h | 17 +- src/coreclr/src/vm/ipcstreamfactory.cpp | 201 ++++++++++-------- src/coreclr/src/vm/ipcstreamfactory.h | 79 ++++++- 5 files changed, 285 insertions(+), 176 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index fbb943567e7af9..329544c6aeeace 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -149,16 +149,28 @@ bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) } } -IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) +IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) { - _ASSERTE(mode == ConnectionMode::CLIENT); - if (mode != ConnectionMode::CLIENT) + _ASSERTE(mode == ConnectionMode::SERVER); + _ASSERTE(_isListening); + + sockaddr_un from; + socklen_t fromlen = sizeof(from); + const int clientSocket = ::accept(_serverSocket, (sockaddr *)&from, &fromlen); + if (clientSocket == -1) { if (callback != nullptr) - callback("Cannot call connect on a server connection", 0); + callback(strerror(errno), errno); return nullptr; } + return new IpcStream(clientSocket, mode); +} + +IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) +{ + _ASSERTE(mode == ConnectionMode::CLIENT); + sockaddr_un clientAddress{}; clientAddress.sun_family = AF_UNIX; const int clientSocket = ::socket(AF_UNIX, SOCK_STREAM, 0); @@ -182,24 +194,25 @@ IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) return new IpcStream(clientSocket, ConnectionMode::CLIENT); } -int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback) +int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *rgIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback) { // prepare the pollfd structs pollfd *pollfds = new pollfd[nHandles]; for (uint32_t i = 0; i < nHandles; i++) { - rgpIpcPollHandles[i]->revents = 0; // ignore any values in revents + rgIpcPollHandles[i].revents = 0; // ignore any values in revents int fd = -1; - if (rgpIpcPollHandles[i]->pIpc->mode == ConnectionMode::SERVER) + if (rgIpcPollHandles[i].pIpc != nullptr) { // SERVER - fd = rgpIpcPollHandles[i]->pIpc->_serverSocket; + _ASSERTE(rgIpcPollHandles[i].pIpc->mode == ConnectionMode::SERVER); + fd = rgIpcPollHandles[i].pIpc->_serverSocket; } else { // CLIENT - _ASSERTE(rgpIpcPollHandles[i]->pStream != nullptr); - fd = rgpIpcPollHandles[i]->pStream->_clientSocket; + _ASSERTE(rgIpcPollHandles[i].pStream != nullptr); + fd = rgIpcPollHandles[i].pStream->_clientSocket; } pollfds[i].fd = fd; @@ -215,7 +228,7 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles { if ((pollfds[i].revents & POLLERR) && callback != nullptr) callback(strerror(errno), errno); - rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::ERR; + rgIpcPollHandles[i].revents = (uint8_t)PollEvents::ERR; } delete[] pollfds; return -1; @@ -231,14 +244,13 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles { if (pollfds[i].revents != 0) { - bool needToAccept = rgpIpcPollHandles[i]->pIpc->mode == DiagnosticsIpc::ConnectionMode::SERVER; // error check FIRST if (pollfds[i].revents & POLLHUP) { // check for hangup first because a closed socket // will technically meet the requirements for POLLIN // i.e., a call to recv/read won't block - rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::HANGUP; + rgIpcPollHandles[i].revents = (uint8_t)PollEvents::HANGUP; delete[] pollfds; return -1; } @@ -246,32 +258,13 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles { if (callback != nullptr) callback("Poll error", (uint32_t)pollfds[i].revents); - rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::ERR; + rgIpcPollHandles[i].revents = (uint8_t)PollEvents::ERR; delete[] pollfds; return -1; } else if (pollfds[i].revents & POLLIN) { - if (needToAccept) - { - sockaddr_un from; - socklen_t fromlen = sizeof(from); - const int clientSocket = ::accept(rgpIpcPollHandles[i]->pIpc->_serverSocket, (sockaddr *)&from, &fromlen); - if (clientSocket == -1) - { - if (callback != nullptr) - callback(strerror(errno), errno); - rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::ERR; - delete[] pollfds; - return -1; - } - rgpIpcPollHandles[i]->pStream = new IpcStream(clientSocket, rgpIpcPollHandles[i]->pIpc->mode); - rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::SIGNALED; - } - else - { - rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::SIGNALED; - } + rgIpcPollHandles[i].revents = (uint8_t)PollEvents::SIGNALED; break; } } @@ -326,6 +319,7 @@ void IpcStream::Close(ErrorCallback) const int fSuccessClose = ::close(_clientSocket); _ASSERTE(fSuccessClose != -1); + _clientSocket = -1; } } diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 2f5aa3f67297c9..1bf6b0d7c7ae87 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -117,6 +117,43 @@ bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) return true; } +IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) +{ + _ASSERTE(_isListening); + _ASSERTE(mode == ConnectionMode::SERVER); + + DWORD dwDummy = 0; + bool fSuccess = GetOverlappedResult( + _hPipe, // handle + _oOverlap, // overlapped + &dwDummy, // throw-away dword + true); // wait till event signals + + if (!fSuccess) + { + if (callback != nullptr) + callback("Failed to GetOverlappedResults for NamedPipe server", ::GetLastError()); + return nullptr; + } + + // create new IpcStream using handle and reset the Server object so it can listen again + IpcStream *pStream = new IpcStream(_hPipe, ConnectionMode::SERVER); + + // reset the server + _hPipe = INVALID_HANDLE_VALUE; + _isListening = false; + ::CloseHandle(_oOverlap.hEvent); + memset(&_oOverlap, 0, sizeof(OVERLAPPED)); // clear the overlapped objects state + fSuccess = Listen(callback); + if (!fSuccess) + { + delete pStream; + return nullptr; + } + + return pStream; +} + IpcStream *IpcStream::DiagnosticsIpc::Connect(ErrorCallback callback) { _ASSERTE(mode == ConnectionMode::CLIENT); @@ -201,44 +238,47 @@ void IpcStream::Close(ErrorCallback) } } -int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback) +int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *rgIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback) { // load up an array of handles HANDLE *pHandles = new HANDLE[nHandles]; for (uint32_t i = 0; i < nHandles; i++) { - rgpIpcPollHandles[i]->revents = 0; // ignore any inputs on revents - if (rgpIpcPollHandles[i]->pIpc->mode == DiagnosticsIpc::ConnectionMode::SERVER) + rgIpcPollHandles[i].revents = 0; // ignore any inputs on revents + if (rgIpcPollHandles[i].pIpc != nullptr) { - pHandles[i] = rgpIpcPollHandles[i]->pIpc->_oOverlap.hEvent; + // SERVER + _ASSERTE(rgIpcPollHandles[i].pIpc->mode == DiagnosticsIpc::ConnectionMode::SERVER); + pHandles[i] = rgIpcPollHandles[i].pIpc->_oOverlap.hEvent; } else { + // CLIENT bool fSuccess = false; DWORD dwDummy = 0; - if (!rgpIpcPollHandles[i]->pStream->_isTestReading) + if (!rgIpcPollHandles[i].pStream->_isTestReading) { // check for data by doing an asynchronous 0 byte read. // This will signal if the pipe closes (hangup) or the server // sends new data fSuccess = ::ReadFile( - rgpIpcPollHandles[i]->pStream->_hPipe, // handle + rgIpcPollHandles[i].pStream->_hPipe, // handle nullptr, // null buffer 0, // read 0 bytes &dwDummy, // dummy variable - &rgpIpcPollHandles[i]->pStream->_oOverlap); // overlap object to use - rgpIpcPollHandles[i]->pStream->_isTestReading = true; + &rgIpcPollHandles[i].pStream->_oOverlap); // overlap object to use + rgIpcPollHandles[i].pStream->_isTestReading = true; if (!fSuccess) { DWORD error = ::GetLastError(); switch (error) { case ERROR_IO_PENDING: - pHandles[i] = rgpIpcPollHandles[i]->pStream->_oOverlap.hEvent; + pHandles[i] = rgIpcPollHandles[i].pStream->_oOverlap.hEvent; break; case ERROR_PIPE_NOT_CONNECTED: // hangup - rgpIpcPollHandles[i]->revents = (uint8_t)PollEvents::HANGUP; + rgIpcPollHandles[i].revents = (uint8_t)PollEvents::HANGUP; delete[] pHandles; return -1; default: @@ -251,7 +291,7 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles } else { - pHandles[i] = rgpIpcPollHandles[i]->pStream->_oOverlap.hEvent; + pHandles[i] = rgIpcPollHandles[i].pStream->_oOverlap.hEvent; } } } @@ -288,7 +328,7 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles DWORD abandonedIndex = dwWait - WAIT_ABANDONED_0; if (abandonedIndex > 0 || abandonedIndex < (nHandles - 1)) { - rgpIpcPollHandles[abandonedIndex]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::HANGUP; + rgIpcPollHandles[abandonedIndex].revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::HANGUP; delete[] pHandles; return -1; } @@ -302,57 +342,39 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *const * rgpIpcPollHandles } // Set revents depending on what signaled the stream - if (rgpIpcPollHandles[index]->pIpc->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) + if (rgIpcPollHandles[index].pIpc == nullptr) { + // CLIENT // check if the connection got hung up DWORD dwDummy = 0; - bool fSuccess = GetOverlappedResult(rgpIpcPollHandles[index]->pStream->_hPipe, - &rgpIpcPollHandles[index]->pStream->_oOverlap, + bool fSuccess = GetOverlappedResult(rgIpcPollHandles[index].pStream->_hPipe, + &rgIpcPollHandles[index].pStream->_oOverlap, &dwDummy, true); + rgIpcPollHandles[index].pStream->_isTestReading = false; if (!fSuccess) { DWORD error = ::GetLastError(); if (error == ERROR_PIPE_NOT_CONNECTED) - rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::HANGUP; + rgIpcPollHandles[index].revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::HANGUP; else { if (callback != nullptr) callback("Client connection error", -1); - rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::ERR; + rgIpcPollHandles[index].revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::ERR; delete[] pHandles; return -1; } } else { - rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::SIGNALED; + rgIpcPollHandles[index].revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::SIGNALED; } } else { - // complete the async listen - DWORD dwDummy = 0; - bool fSuccess = GetOverlappedResult(rgpIpcPollHandles[index]->pIpc->_hPipe, - &rgpIpcPollHandles[index]->pIpc->_oOverlap, - &dwDummy, - true); - if (!fSuccess) - { - if (callback != nullptr) - callback("Failed to GetOverlappedResults for NamedPipe server", ::GetLastError()); - rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::ERR; - delete[] pHandles; - return -1; - } - - // create new IpcStream using handle and reset the Server object so it can listen again - rgpIpcPollHandles[index]->pStream = new IpcStream(rgpIpcPollHandles[index]->pIpc->_hPipe, IpcStream::DiagnosticsIpc::ConnectionMode::SERVER); - rgpIpcPollHandles[index]->pIpc->_hPipe = INVALID_HANDLE_VALUE; - rgpIpcPollHandles[index]->pIpc->_isListening = false; - ::CloseHandle(rgpIpcPollHandles[index]->pIpc->_oOverlap.hEvent); - memset(&rgpIpcPollHandles[index]->pIpc->_oOverlap, 0, sizeof(OVERLAPPED)); // clear the overlapped objects state - rgpIpcPollHandles[index]->revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::SIGNALED; + // SERVER + rgIpcPollHandles[index].revents = (uint8_t)IpcStream::DiagnosticsIpc::PollEvents::SIGNALED; } delete[] pHandles; diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index aaf4a10a94765d..f54cb1ba84d90f 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -44,21 +44,17 @@ class IpcStream final // The bookeeping struct used for polling on server and client structs struct IpcPollHandle { + // Only one of these will be non-null, treat as a union DiagnosticsIpc *pIpc; - - // After calling Poll, will contain a usable IpcStream - // IFF (revents & (uint8_t)PollEvents::SIGNALED) != 0 - // - // DiagnosticsIpc::ConnectionMode::CLIENT connections should place a usable - // IpcStream here before calling Poll, i.e., pollHandle.pStream = pollHandle.pIpc->Connect() - // - // DiagnosticsIpc::ConnectionMode::SERVER connections can leave this null IpcStream *pStream; // contains some set of PollEvents // will be set by Poll // Any values here are ignored by Poll uint8_t revents; + + // a callback cookie assignable by upstream users for additional bookkeeping + void *pUserData; }; // Poll @@ -72,7 +68,7 @@ class IpcStream final // Check the events returned in revents for each IpcPollHandle to find the signaled handle. // Signaled handles will have usable IpcStreams in the pStream field. // The caller is responsible for cleaning up "hung up" connections. - static int32_t Poll(IpcPollHandle *const * rgpIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback = nullptr); + static int32_t Poll(IpcPollHandle *rgIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback = nullptr); ConnectionMode mode; @@ -85,6 +81,9 @@ class IpcStream final //! Re-entrant safe bool Listen(ErrorCallback callback = nullptr); + //! produces a client stream from a server-mode DiagnosticsIpc. Blocks until a connection is available. + IpcStream *Accept(ErrorCallback callback = nullptr); + //! Connect to client connection (returns a usable stream) IpcStream *Connect(ErrorCallback callback = nullptr); diff --git a/src/coreclr/src/vm/ipcstreamfactory.cpp b/src/coreclr/src/vm/ipcstreamfactory.cpp index 79ad295b0d986e..d7c326244565e3 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.cpp +++ b/src/coreclr/src/vm/ipcstreamfactory.cpp @@ -8,7 +8,73 @@ #ifdef FEATURE_PERFTRACING -CQuickArrayList IpcStreamFactory::s_rgIpcPollHandles = CQuickArrayList(); +CQuickArrayList IpcStreamFactory::s_rgpConnectionStates = CQuickArrayList(); +Volatile IpcStreamFactory::s_isShutdown = false; + +bool IpcStreamFactory::ClientConnectionState::GetIpcPollHandle(IpcStream::DiagnosticsIpc::IpcPollHandle *pIpcPollHandle, ErrorCallback callback) +{ + if (_pStream == nullptr) + { + // cache is empty, reconnect, e.g., there was a disconnect + IpcStream *pConnection = _pIpc->Connect(callback); + + if (pConnection != nullptr) + { + if (!DiagnosticsIpc::SendIpcAdvertise_V1(pConnection)) + { + if (callback != nullptr) + callback("Failed to send advertise message", -1); + delete pConnection; + return false; + } + + _pStream = pConnection; + *pIpcPollHandle = { nullptr, _pStream, 0, this }; + return true; + } + else + { + if (callback != nullptr) + callback("Failed to connect to client connection", -1); + return false; + } + } + else + { + *pIpcPollHandle = { nullptr, _pStream, 0, this }; + return true; + } +} + +IpcStream *IpcStreamFactory::ClientConnectionState::GetConnectedStream(ErrorCallback callback) +{ + IpcStream *pStream = _pStream; + _pStream = nullptr; + return pStream; +} + +void IpcStreamFactory::ClientConnectionState::Reset(ErrorCallback callback) +{ + delete _pStream; + _pStream = nullptr; +} + +bool IpcStreamFactory::ServerConnectionState::GetIpcPollHandle(IpcStream::DiagnosticsIpc::IpcPollHandle *pIpcPollHandle, ErrorCallback callback) +{ + *pIpcPollHandle = { _pIpc, nullptr, 0, this }; + return true; +} + +IpcStream *IpcStreamFactory::ServerConnectionState::GetConnectedStream(ErrorCallback callback) +{ + return _pIpc->Accept(callback); +} + +// noop for server +void IpcStreamFactory::ServerConnectionState::Reset(ErrorCallback) +{ + return; +} bool IpcStreamFactory::CreateServer(const char *const pIpcName, ErrorCallback callback) { @@ -17,8 +83,7 @@ bool IpcStreamFactory::CreateServer(const char *const pIpcName, ErrorCallback ca { if (pIpc->Listen(callback)) { - IpcStream::DiagnosticsIpc::IpcPollHandle ipcPollHandle = { pIpc, nullptr, 0 }; - s_rgIpcPollHandles.Push(ipcPollHandle); + s_rgpConnectionStates.Push(new ServerConnectionState(pIpc)); return true; } else @@ -38,8 +103,7 @@ bool IpcStreamFactory::CreateClient(const char *const pIpcName, ErrorCallback ca IpcStream::DiagnosticsIpc *pIpc = IpcStream::DiagnosticsIpc::Create(pIpcName, IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT, callback); if (pIpc != nullptr) { - IpcStream::DiagnosticsIpc::IpcPollHandle ipcPollHandle = { pIpc, nullptr, 0 }; - s_rgIpcPollHandles.Push(ipcPollHandle); + s_rgpConnectionStates.Push(new ClientConnectionState(pIpc)); return true; } else @@ -50,124 +114,79 @@ bool IpcStreamFactory::CreateClient(const char *const pIpcName, ErrorCallback ca bool IpcStreamFactory::HasActiveConnections() { - return s_rgIpcPollHandles.Size() > 0; + return !s_isShutdown && s_rgpConnectionStates.Size() > 0; } -void IpcStreamFactory::CloseConnections() +void IpcStreamFactory::CloseConnections(ErrorCallback callback) { - auto ErrorCallback = [](const char *szMessage, uint32_t code) { - STRESS_LOG2( - LF_DIAGNOSTICS_PORT, // facility - LL_ERROR, // level - "Failed to close diagnostic IPC: error (%d): %s.\n", // msg - code, // data1 - szMessage); // data2 - }; - for (uint32_t i = 0; i < (uint32_t)s_rgIpcPollHandles.Size(); i++) + s_isShutdown = true; + for (uint32_t i = 0; i < (uint32_t)s_rgpConnectionStates.Size(); i++) + s_rgpConnectionStates[i]->Close(callback); +} + +// helper function for getting timeout +int32_t IpcStreamFactory::GetNextTimeout(int32_t currentTimeoutMs) +{ + if (currentTimeoutMs == s_pollTimeoutInfinite) + { + return s_pollTimeoutMinMs; + } + else { - if (s_rgIpcPollHandles[i].pStream != nullptr) - s_rgIpcPollHandles[i].pStream->Close(ErrorCallback); - if (s_rgIpcPollHandles[i].pIpc != nullptr) - s_rgIpcPollHandles[i].pIpc->Close(ErrorCallback); + return (currentTimeoutMs >= s_pollTimeoutMaxMs) ? + s_pollTimeoutMaxMs : + (int32_t)((float)currentTimeoutMs * s_pollTimeoutFalloffFactor); } } IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) { IpcStream *pStream = nullptr; - // View of s_rgIpcPollhandles - CQuickArrayList rgpIpcPollHandles; - - // Polling timeout semantics - // If client connection is opted in - // and connection succeeds => set timeout to infinite - // and connection fails => set timeout to minimum and scale by falloff factor - // else => set timeout to -1 (infinite) - // - // If an agent closes its socket while we're still connected, - // Poll will return and let us know which connection hung up - int32_t pollTimeoutFalloffFactor = 2; - int32_t pollTimeoutInfinite = -1; - int32_t pollTimeoutMinMs = 250; - int32_t pollTimeoutMs = pollTimeoutInfinite; - int32_t pollTimeoutMaxMs = 30000; // 30s + CQuickArrayList rgIpcPollHandles; + + int32_t pollTimeoutMs = s_pollTimeoutInfinite; + int32_t nextPollTimeoutMs = 0; + bool fConnectSuccess =true; uint32_t nPollAttempts = 0; while (pStream == nullptr) { - for (uint32_t i = 0; i < (uint32_t)s_rgIpcPollHandles.Size(); i++) + fConnectSuccess = true; + for (uint32_t i = 0; i < (uint32_t)s_rgpConnectionStates.Size(); i++) { - if (s_rgIpcPollHandles[i].pIpc->mode == IpcStream::DiagnosticsIpc::ConnectionMode::CLIENT) + IpcStream::DiagnosticsIpc::IpcPollHandle pollHandle = {}; + if (s_rgpConnectionStates[i]->GetIpcPollHandle(&pollHandle, callback)) { - pollTimeoutMs = (pollTimeoutMs == pollTimeoutInfinite) ? pollTimeoutMinMs : pollTimeoutMs; - if (s_rgIpcPollHandles[i].pStream == nullptr) - { - // cache is empty, reconnect, e.g., there was a disconnect - IpcStream *pConnection = nullptr; - pConnection = s_rgIpcPollHandles[i].pIpc->Connect(callback); - - if (pConnection != nullptr) - { - if (!DiagnosticsIpc::SendIpcAdvertise_V1(pConnection)) - { - if (callback != nullptr) - callback("Failed to send advertise message", -1); - delete pConnection; - return nullptr; - } - - s_rgIpcPollHandles[i].pStream = pConnection; - pollTimeoutMs = pollTimeoutInfinite; - rgpIpcPollHandles.Push(&s_rgIpcPollHandles[i]); - } - else - { - // connection failed, increment timeout - pollTimeoutMs = (pollTimeoutMs >= pollTimeoutMaxMs) ? - pollTimeoutMaxMs : - pollTimeoutMs * pollTimeoutFalloffFactor; - } - } - else - { - // reuse the existing connection - pollTimeoutMs = pollTimeoutInfinite; - rgpIpcPollHandles.Push(&s_rgIpcPollHandles[i]); - } + rgIpcPollHandles.Push(pollHandle); } else { - bool fSuccess = s_rgIpcPollHandles[i].pIpc->Listen(); - if (!fSuccess) - { - // TODO: error check the server failing to listen - } - rgpIpcPollHandles.Push(&s_rgIpcPollHandles[i]); + fConnectSuccess = false; } } - int32_t retval = IpcStream::DiagnosticsIpc::Poll(rgpIpcPollHandles.Ptr(), (uint32_t)rgpIpcPollHandles.Size(), pollTimeoutMs, callback); + pollTimeoutMs = fConnectSuccess ? + s_pollTimeoutInfinite : + GetNextTimeout(pollTimeoutMs); + + int32_t retval = IpcStream::DiagnosticsIpc::Poll(rgIpcPollHandles.Ptr(), (uint32_t)rgIpcPollHandles.Size(), pollTimeoutMs, callback); nPollAttempts++; STRESS_LOG2(LF_DIAGNOSTICS_PORT, LL_INFO10, "IpcStreamFactory::GetNextAvailableStream - Poll attempt: %d, timeout: %dms.\n", nPollAttempts, pollTimeoutMs); if (retval != 0) { - for (uint32_t i = 0; i < (uint32_t)rgpIpcPollHandles.Size(); i++) + for (uint32_t i = 0; i < (uint32_t)rgIpcPollHandles.Size(); i++) { - switch ((IpcStream::DiagnosticsIpc::PollEvents)rgpIpcPollHandles[i]->revents) + switch ((IpcStream::DiagnosticsIpc::PollEvents)rgIpcPollHandles[i].revents) { case IpcStream::DiagnosticsIpc::PollEvents::HANGUP: - delete rgpIpcPollHandles[i]->pStream; - rgpIpcPollHandles[i]->pStream = nullptr; // clear the cache of the hung up connection; will trigger a reconnect poll + ((ConnectionState*)(rgIpcPollHandles[i].pUserData))->Reset(callback); STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_INFO10, "IpcStreamFactory::GetNextAvailableStream - Poll attempt: %d, connection hung up.\n", nPollAttempts); - pollTimeoutMs = pollTimeoutMinMs; + pollTimeoutMs = s_pollTimeoutMinMs; break; case IpcStream::DiagnosticsIpc::PollEvents::SIGNALED: if (pStream == nullptr) // only use first signaled stream; will get others on subsequent calls - { - pStream = rgpIpcPollHandles[i]->pStream; - rgpIpcPollHandles[i]->pStream = nullptr; // pass ownership to caller so we aren't caching the connection anymore - } + pStream = ((ConnectionState*)(rgIpcPollHandles[i].pUserData))->GetConnectedStream(callback); break; case IpcStream::DiagnosticsIpc::PollEvents::ERR: return nullptr; @@ -179,8 +198,8 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) } // clear the view - while (rgpIpcPollHandles.Size() > 0) - rgpIpcPollHandles.Pop(); + while (rgIpcPollHandles.Size() > 0) + rgIpcPollHandles.Pop(); } return pStream; diff --git a/src/coreclr/src/vm/ipcstreamfactory.h b/src/coreclr/src/vm/ipcstreamfactory.h index 5a27d1bb99fc3a..1720a426a6fdb5 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.h +++ b/src/coreclr/src/vm/ipcstreamfactory.h @@ -12,13 +12,88 @@ class IpcStreamFactory { public: + struct ConnectionState + { + public: + ConnectionState(IpcStream::DiagnosticsIpc *pIpc) : + _pIpc(pIpc), + _pStream(nullptr) + { } + + // returns a pollable handle and performs any preparation required + // e.g., as a side-effect, will connect and advertise on reverse connections + virtual bool GetIpcPollHandle(IpcStream::DiagnosticsIpc::IpcPollHandle *pIpcPollHandle, ErrorCallback callback = nullptr) = 0; + + // Returns the signaled stream in a usable state + virtual IpcStream *GetConnectedStream(ErrorCallback callback = nullptr) = 0; + + // Resets the connection in the event of a hangup + virtual void Reset(ErrorCallback callback = nullptr) = 0; + + // closes the underlying connections + void Close(ErrorCallback callback = nullptr) + { + if (_pIpc != nullptr) + _pIpc->Close(callback); + if (_pStream != nullptr) + _pStream->Close(callback); + } + + protected: + IpcStream::DiagnosticsIpc *_pIpc; + IpcStream *_pStream; + }; + + struct ClientConnectionState : public ConnectionState + { + ClientConnectionState(IpcStream::DiagnosticsIpc *pIpc) : ConnectionState(pIpc) { } + + // returns a pollable handle and performs any preparation required + bool GetIpcPollHandle(IpcStream::DiagnosticsIpc::IpcPollHandle *pIpcPollHandle, ErrorCallback callback = nullptr) override; + + // Returns the signaled stream in a usable state + IpcStream *GetConnectedStream(ErrorCallback callback = nullptr) override; + + // Resets the connection in the event of a hangup + void Reset(ErrorCallback callback = nullptr) override; + }; + + struct ServerConnectionState : public ConnectionState + { + ServerConnectionState(IpcStream::DiagnosticsIpc *pIpc) : ConnectionState(pIpc) { } + + // returns a pollable handle and performs any preparation required + bool GetIpcPollHandle(IpcStream::DiagnosticsIpc::IpcPollHandle *pIpcPollHandle, ErrorCallback callback = nullptr) override; + + // Returns the signaled stream in a usable state + IpcStream *GetConnectedStream(ErrorCallback callback = nullptr) override; + + // Resets the connection in the event of a hangup + void Reset(ErrorCallback callback = nullptr) override; + }; + static bool CreateServer(const char *const pIpcName, ErrorCallback = nullptr); static bool CreateClient(const char *const pIpcName, ErrorCallback = nullptr); static IpcStream *GetNextAvailableStream(ErrorCallback = nullptr); static bool HasActiveConnections(); - static void CloseConnections(); + static void CloseConnections(ErrorCallback callback = nullptr); private: - static CQuickArrayList s_rgIpcPollHandles; + static CQuickArrayList s_rgpConnectionStates; + static Volatile s_isShutdown; + + // Polling timeout semantics + // If client connection is opted in + // and connection succeeds => set timeout to infinite + // and connection fails => set timeout to minimum and scale by falloff factor + // else => set timeout to -1 (infinite) + // + // If an agent closes its socket while we're still connected, + // Poll will return and let us know which connection hung up + static int32_t GetNextTimeout(int32_t currentTimeoutMs); + constexpr static float s_pollTimeoutFalloffFactor = 2; + constexpr static int32_t s_pollTimeoutInfinite = -1; + constexpr static int32_t s_pollTimeoutMinMs = 250; + constexpr static int32_t s_pollTimeoutMaxMs = 30000; }; #endif // FEATURE_PERFTRACING From 0fce7bc43c45b700a8d20d04bc0be3c00ff20e5f Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 13 Apr 2020 16:45:08 -0700 Subject: [PATCH 41/52] Add test case --- .../src/tracing/eventpipe/reverse/reverse.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs index ba467f3463259b..9ce1d80ec9188c 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -158,6 +158,44 @@ await RunSubprocess( return true; } + public static async Task TEST_ServerWorksIfClientDoesntAccept() + { + string serverName = ReverseServer.MakeServerAddress(); + Logger.logger.Log($"Server name is '{serverName}'"); + var server = new ReverseServer(serverName); + await RunSubprocess( + serverName: serverName, + duringExecution: async (int pid) => + { + Task regularTask = Task.Run(async () => + { + var config = new SessionConfiguration( + circularBufferSizeMB: 1000, + format: EventPipeSerializationFormat.NetTrace, + providers: new List { + new Provider("Microsoft-DotNETCore-SampleProfiler") + }); + Logger.logger.Log("Starting EventPipeSession over standard connection"); + using Stream stream = EventPipeClient.CollectTracing(pid, config, out var sessionId); + Logger.logger.Log($"Started EventPipeSession over standard connection with session id: 0x{sessionId:x}"); + using var source = new EventPipeEventSource(stream); + Task readerTask = Task.Run(() => source.Process()); + await Task.Delay(500); + Logger.logger.Log("Stopping EventPipeSession over standard connection"); + EventPipeClient.StopTracing(pid, sessionId); + await readerTask; + Logger.logger.Log("Stopped EventPipeSession over standard connection"); + }); + + await regularTask; + } + ); + + server.Shutdown(); + + return true; + } + public static async Task TEST_ReverseConnectionCanRecycleWhileTracing() { string serverName = ReverseServer.MakeServerAddress(); From 6e2f642a83c211b50db7d5ab67baf307146a8f7b Mon Sep 17 00:00:00 2001 From: John Salem Date: Mon, 13 Apr 2020 17:58:28 -0700 Subject: [PATCH 42/52] Fix windows build --- src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 1bf6b0d7c7ae87..4920dc4eb481b2 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -125,7 +125,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) DWORD dwDummy = 0; bool fSuccess = GetOverlappedResult( _hPipe, // handle - _oOverlap, // overlapped + &_oOverlap, // overlapped &dwDummy, // throw-away dword true); // wait till event signals From 18c99591c0e230679349ccf5d21371260507aa8d Mon Sep 17 00:00:00 2001 From: John Salem Date: Wed, 15 Apr 2020 18:24:15 -0700 Subject: [PATCH 43/52] Fix test * test was creating a pipe with a 0 buffer * runtime needs to handle a 0 buffer namedpipe --- .../debug/debug-pal/win/diagnosticsipc.cpp | 4 +- src/coreclr/src/vm/ipcstreamfactory.cpp | 3 +- .../src/tracing/eventpipe/common/Reverse.cs | 66 ++++++------------- .../src/tracing/eventpipe/reverse/reverse.cs | 39 +++++------ 4 files changed, 41 insertions(+), 71 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 4920dc4eb481b2..5b337351d50cbe 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -386,7 +386,7 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt _ASSERTE(lpBuffer != nullptr); DWORD nNumberOfBytesRead = 0; - LPOVERLAPPED overlap = const_cast(&_oOverlap); + LPOVERLAPPED overlap = &_oOverlap; bool fSuccess = ::ReadFile( _hPipe, // handle to pipe lpBuffer, // buffer to receive data @@ -416,7 +416,7 @@ bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32 _ASSERTE(lpBuffer != nullptr); DWORD nNumberOfBytesWritten = 0; - LPOVERLAPPED overlap = const_cast(&_oOverlap); + LPOVERLAPPED overlap = &_oOverlap; bool fSuccess = ::WriteFile( _hPipe, // handle to pipe lpBuffer, // buffer to write from diff --git a/src/coreclr/src/vm/ipcstreamfactory.cpp b/src/coreclr/src/vm/ipcstreamfactory.cpp index d7c326244565e3..288f12e7ea19fe 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.cpp +++ b/src/coreclr/src/vm/ipcstreamfactory.cpp @@ -145,8 +145,7 @@ IpcStream *IpcStreamFactory::GetNextAvailableStream(ErrorCallback callback) CQuickArrayList rgIpcPollHandles; int32_t pollTimeoutMs = s_pollTimeoutInfinite; - int32_t nextPollTimeoutMs = 0; - bool fConnectSuccess =true; + bool fConnectSuccess = true; uint32_t nPollAttempts = 0; while (pStream == nullptr) diff --git a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs index 4629f8bf03e93f..b2b20e09291cb2 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs @@ -66,11 +66,11 @@ public static string MakeServerAddress() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return Path.GetRandomFileName(); + return "DOTNET_TRACE_TESTS_" + Path.GetRandomFileName(); } else { - return Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + return Path.Combine(Path.GetTempPath(), "DOTNET_TRACE_TESTS_" + Path.GetRandomFileName()); } } @@ -83,7 +83,14 @@ public ReverseServer(string serverAddress) _serverAddress = serverAddress; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - _server = new NamedPipeServerStream(serverAddress); + _server = new NamedPipeServerStream( + serverAddress, + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Byte, + PipeOptions.None, + 16 * 1024, + 16 * 1024); } else { @@ -119,15 +126,22 @@ public void Shutdown() switch (_server) { case NamedPipeServerStream serverStream: - serverStream.Disconnect(); - serverStream.Dispose(); + try + { + serverStream.Disconnect(); + } + catch {} + finally + { + serverStream.Dispose(); + } break; case Socket socket: try { socket.Shutdown(SocketShutdown.Both); } - catch (Exception e) {} + catch {} finally { _clientSocket?.Close(); @@ -153,46 +167,6 @@ public static async Task CreateServerAndReceiveAdvertisement(strin IpcAdvertise advertise = IpcAdvertise.Parse(stream); server.Shutdown(); return advertise; - // if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - // { - // using var serverStream = new NamedPipeServerStream(serverAddress); - // Logger.logger.Log("Waiting for connection"); - // await serverStream.WaitForConnectionAsync(); - // Logger.logger.Log("Got a connection"); - // IpcAdvertise advertise = IpcAdvertise.Parse(serverStream); - // serverStream.Disconnect(); - // return advertise; - // } - // else - // { - // if (File.Exists(serverAddress)) - // File.Delete(serverAddress); - // var remoteEP = new UnixDomainSocketEndPoint(serverAddress); - - // using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); - // socket.Bind(remoteEP); - // socket.Listen(255); - // socket.LingerState.Enabled = false; - // Logger.logger.Log("Waiting for connection"); - // using Socket clientSocket = await socket.AcceptAsync(); - // Logger.logger.Log("Got a connection"); - // using var socketStream = new NetworkStream(clientSocket); - // IpcAdvertise advertise = IpcAdvertise.Parse(socketStream); - // try - // { - // socket.Shutdown(SocketShutdown.Both); - // } - // catch (Exception e) {} - // finally - // { - // clientSocket.Close(); - // socket.Close(); - // if (File.Exists(serverAddress)) - // File.Delete(serverAddress); - // } - - // return advertise; - // } } } } \ No newline at end of file diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs index 9ce1d80ec9188c..64f89faea794f6 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -167,27 +167,24 @@ await RunSubprocess( serverName: serverName, duringExecution: async (int pid) => { - Task regularTask = Task.Run(async () => - { - var config = new SessionConfiguration( - circularBufferSizeMB: 1000, - format: EventPipeSerializationFormat.NetTrace, - providers: new List { - new Provider("Microsoft-DotNETCore-SampleProfiler") - }); - Logger.logger.Log("Starting EventPipeSession over standard connection"); - using Stream stream = EventPipeClient.CollectTracing(pid, config, out var sessionId); - Logger.logger.Log($"Started EventPipeSession over standard connection with session id: 0x{sessionId:x}"); - using var source = new EventPipeEventSource(stream); - Task readerTask = Task.Run(() => source.Process()); - await Task.Delay(500); - Logger.logger.Log("Stopping EventPipeSession over standard connection"); - EventPipeClient.StopTracing(pid, sessionId); - await readerTask; - Logger.logger.Log("Stopped EventPipeSession over standard connection"); - }); - - await regularTask; + var config = new SessionConfiguration( + circularBufferSizeMB: 10, + format: EventPipeSerializationFormat.NetTrace, + providers: new List { + new Provider("Microsoft-DotNETCore-SampleProfiler") + }); + Logger.logger.Log("Starting EventPipeSession over standard connection"); + // await Task.Delay(10000); + using Stream stream = EventPipeClient.CollectTracing(pid, config, out var sessionId); + Logger.logger.Log($"Started EventPipeSession over standard connection with session id: 0x{sessionId:x}"); + using var source = new EventPipeEventSource(stream); + Task readerTask = Task.Run(() => source.Process()); + await Task.Delay(500); + Logger.logger.Log("Stopping EventPipeSession over standard connection"); + EventPipeClient.StopTracing(pid, sessionId); + await readerTask; + Logger.logger.Log("Stopped EventPipeSession over standard connection"); + // await Task.Delay(60000); } ); From 91d6d0e9aed09c9d7e85eb9f4f4b3643e6f7315a Mon Sep 17 00:00:00 2001 From: John Salem Date: Thu, 16 Apr 2020 12:22:28 -0700 Subject: [PATCH 44/52] Introduce a timeout to read/write * makes advertisement not block for more than 100 ms * TODO: implement on non-windows --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 6 +- .../debug/debug-pal/win/diagnosticsipc.cpp | 65 ++++++++++++++++--- src/coreclr/src/debug/inc/diagnosticsipc.h | 5 +- src/coreclr/src/vm/diagnosticsprotocol.h | 2 +- .../src/tracing/eventpipe/common/Reverse.cs | 6 +- .../src/tracing/eventpipe/reverse/reverse.cs | 37 ++++++++++- 6 files changed, 103 insertions(+), 18 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 329544c6aeeace..7d0e26dd779169 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -323,10 +323,11 @@ void IpcStream::Close(ErrorCallback) } } -bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) +bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead, const int32_t timeoutMs) { _ASSERTE(lpBuffer != nullptr); + // TODO: use Timeout const ssize_t ssize = ::recv(_clientSocket, lpBuffer, nBytesToRead, 0); const bool fSuccess = ssize != -1; @@ -339,10 +340,11 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt return fSuccess; } -bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) +bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten, const int32_t timeoutMs) { _ASSERTE(lpBuffer != nullptr); + // TODO: use timeout const ssize_t ssize = ::send(_clientSocket, lpBuffer, nBytesToWrite, 0); const bool fSuccess = ssize != -1; diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 5b337351d50cbe..2e76dc16684a6d 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -381,7 +381,7 @@ int32_t IpcStream::DiagnosticsIpc::Poll(IpcPollHandle *rgIpcPollHandles, uint32_ return 1; } -bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead) +bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead, const int32_t timeoutMs) { _ASSERTE(lpBuffer != nullptr); @@ -396,14 +396,38 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt if (!fSuccess) { - DWORD dwError = GetLastError(); - if (dwError == ERROR_IO_PENDING) + if (timeoutMs == InfiniteTimeout) { fSuccess = GetOverlappedResult(_hPipe, overlap, &nNumberOfBytesRead, true) != 0; } + else + { + DWORD dwError = GetLastError(); + if (dwError == ERROR_IO_PENDING) + { + DWORD dwWait = WaitForSingleObject(_oOverlap.hEvent, (DWORD)timeoutMs); + if (dwWait == WAIT_OBJECT_0) + { + // get the result + fSuccess = GetOverlappedResult(_hPipe, + overlap, + &nNumberOfBytesRead, + true) != 0; + } + else + { + // cancel IO and ensure the cancel happened + if (CancelIo(_hPipe)) + { + // check if the async write beat the cancellation + fSuccess = GetOverlappedResult(_hPipe, overlap, &nNumberOfBytesRead, true) != 0; + } + } + } + } // TODO: Add error handling. } @@ -411,7 +435,7 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt return fSuccess; } -bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten) +bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten, const int32_t timeoutMs) { _ASSERTE(lpBuffer != nullptr); @@ -429,10 +453,35 @@ bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32 DWORD dwError = GetLastError(); if (dwError == ERROR_IO_PENDING) { - fSuccess = GetOverlappedResult(_hPipe, - overlap, - &nNumberOfBytesWritten, - true) != 0; + if (timeoutMs == InfiniteTimeout) + { + // if we're waiting infinitely, don't bother with extra kernel call + fSuccess = GetOverlappedResult(_hPipe, + overlap, + &nNumberOfBytesWritten, + true) != 0; + } + else + { + DWORD dwWait = WaitForSingleObject(_oOverlap.hEvent, (DWORD)timeoutMs); + if (dwWait == WAIT_OBJECT_0) + { + // get the result + fSuccess = GetOverlappedResult(_hPipe, + overlap, + &nNumberOfBytesWritten, + true) != 0; + } + else + { + // cancel IO and ensure the cancel happened + if (CancelIo(_hPipe)) + { + // check if the async write beat the cancellation + fSuccess = GetOverlappedResult(_hPipe, overlap, &nNumberOfBytesWritten, true) != 0; + } + } + } } // TODO: Add error handling. } diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index f54cb1ba84d90f..b03a0aac6b7d44 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -18,9 +18,10 @@ typedef void (*ErrorCallback)(const char *szMessage, uint32_t code); class IpcStream final { public: + static constexpr int32_t InfiniteTimeout = -1; ~IpcStream(); - bool Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead); - bool Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten); + bool Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nBytesRead, const int32_t timeoutMs = IpcStream::InfiniteTimeout); + bool Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32_t &nBytesWritten, const int32_t timeoutMs = IpcStream::InfiniteTimeout); bool Flush() const; void Close(ErrorCallback callback = nullptr); diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index 6e109a317ddeb5..593490c67ced3b 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -146,7 +146,7 @@ namespace DiagnosticsIpc buffer[3] = VAL64(pid); uint32_t nBytesWritten = 0; - if (!pStream->Write(advertiseBuffer, sizeof(advertiseBuffer), nBytesWritten)) + if (!pStream->Write(advertiseBuffer, sizeof(advertiseBuffer), nBytesWritten, 100 /* ms */)) return false; _ASSERTE(nBytesWritten == sizeof(advertiseBuffer)); diff --git a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs index b2b20e09291cb2..cda65b6d30db5b 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs @@ -78,7 +78,7 @@ public static string MakeServerAddress() private Socket _clientSocket; // only used on non-Windows private string _serverAddress; - public ReverseServer(string serverAddress) + public ReverseServer(string serverAddress, int bufferSize = 16 * 1024) { _serverAddress = serverAddress; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -89,8 +89,8 @@ public ReverseServer(string serverAddress) NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.None, - 16 * 1024, - 16 * 1024); + bufferSize, + bufferSize); } else { diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs index 64f89faea794f6..7fb12badec7818 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -174,7 +174,6 @@ await RunSubprocess( new Provider("Microsoft-DotNETCore-SampleProfiler") }); Logger.logger.Log("Starting EventPipeSession over standard connection"); - // await Task.Delay(10000); using Stream stream = EventPipeClient.CollectTracing(pid, config, out var sessionId); Logger.logger.Log($"Started EventPipeSession over standard connection with session id: 0x{sessionId:x}"); using var source = new EventPipeEventSource(stream); @@ -184,7 +183,41 @@ await RunSubprocess( EventPipeClient.StopTracing(pid, sessionId); await readerTask; Logger.logger.Log("Stopped EventPipeSession over standard connection"); - // await Task.Delay(60000); + } + ); + + server.Shutdown(); + + return true; + } + + public static async Task TEST_ServerIsResilientToNoBufferAgent() + { + // N.B. - this test is only testing behavior on Windows since Unix Domain Sockets get their buffer size from the + // system configuration and isn't set here. Tests passing on Windows should indicate it would pass on Unix systems as well. + string serverName = ReverseServer.MakeServerAddress(); + Logger.logger.Log($"Server name is '{serverName}'"); + var server = new ReverseServer(serverName, 0); + await RunSubprocess( + serverName: serverName, + duringExecution: async (int pid) => + { + var config = new SessionConfiguration( + circularBufferSizeMB: 10, + format: EventPipeSerializationFormat.NetTrace, + providers: new List { + new Provider("Microsoft-DotNETCore-SampleProfiler") + }); + Logger.logger.Log("Starting EventPipeSession over standard connection"); + using Stream stream = EventPipeClient.CollectTracing(pid, config, out var sessionId); + Logger.logger.Log($"Started EventPipeSession over standard connection with session id: 0x{sessionId:x}"); + using var source = new EventPipeEventSource(stream); + Task readerTask = Task.Run(() => source.Process()); + await Task.Delay(500); + Logger.logger.Log("Stopping EventPipeSession over standard connection"); + EventPipeClient.StopTracing(pid, sessionId); + await readerTask; + Logger.logger.Log("Stopped EventPipeSession over standard connection"); } ); From d4bf4c10e06e9e5fbdaeb0937d197e731c439816 Mon Sep 17 00:00:00 2001 From: John Salem Date: Thu, 16 Apr 2020 15:04:11 -0700 Subject: [PATCH 45/52] Implement timeout read/write for unix --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 30 +++++++++++++++++-- .../src/tracing/eventpipe/common/Reverse.cs | 3 ++ .../src/tracing/eventpipe/reverse/reverse.cs | 3 +- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 7d0e26dd779169..3c9a9be8a8153e 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -327,7 +327,20 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt { _ASSERTE(lpBuffer != nullptr); - // TODO: use Timeout + if (timeoutMs != InfiniteTimeout) + { + pollfd pfd; + pfd.fd = _clientSocket; + pfd.events = POLLIN; + int retval = poll(&pfd, 1, timeoutMs); + if (retval <= 0 || pfd.revents != POLLIN) + { + // timeout or error + return false; + } + // else fallthrough + } + const ssize_t ssize = ::recv(_clientSocket, lpBuffer, nBytesToRead, 0); const bool fSuccess = ssize != -1; @@ -344,7 +357,20 @@ bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32 { _ASSERTE(lpBuffer != nullptr); - // TODO: use timeout + if (timeoutMs != InfiniteTimeout) + { + pollfd pfd; + pfd.fd = _clientSocket; + pfd.events = POLLOUT; + int retval = poll(&pfd, 1, timeoutMs); + if (retval <= 0 || pfd.revents != POLLOUT) + { + // timeout or error + return false; + } + // else fallthrough + } + const ssize_t ssize = ::send(_clientSocket, lpBuffer, nBytesToWrite, 0); const bool fSuccess = ssize != -1; diff --git a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs index cda65b6d30db5b..066aa66681c1b6 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs @@ -99,6 +99,9 @@ public ReverseServer(string serverAddress, int bufferSize = 16 * 1024) var remoteEP = new UnixDomainSocketEndPoint(serverAddress); var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + // socket(7) states that SO_RCVBUF has a minimum of 128 and SO_SNDBUF has minimum of 1024 + socket.SendBufferSize = Math.Max(bufferSize, 1024); + socket.ReceiveBufferSize = Math.Max(bufferSize, 128); socket.Bind(remoteEP); socket.Listen(255); socket.LingerState.Enabled = false; diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs index 7fb12badec7818..90c4633238492b 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -54,7 +54,8 @@ public static async Task RunSubprocess(string serverName, Func beforeExecu bool fSuccess = process.Start(); Logger.logger.Log($"subprocess started: {fSuccess}"); - await Task.Delay(250); + while (!EventPipeClient.ListAvailablePorts().Contains(process.Id)) + await Task.Delay(100); try { if (duringExecution != null) From 9311238e91ddd3fe43c2f599f07852331c2d5392 Mon Sep 17 00:00:00 2001 From: John Salem Date: Thu, 16 Apr 2020 15:04:41 -0700 Subject: [PATCH 46/52] adjust poll timeout to be more aggressive --- src/coreclr/src/vm/ipcstreamfactory.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/src/vm/ipcstreamfactory.h b/src/coreclr/src/vm/ipcstreamfactory.h index 1720a426a6fdb5..f39aa92f46cd21 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.h +++ b/src/coreclr/src/vm/ipcstreamfactory.h @@ -90,10 +90,10 @@ class IpcStreamFactory // If an agent closes its socket while we're still connected, // Poll will return and let us know which connection hung up static int32_t GetNextTimeout(int32_t currentTimeoutMs); - constexpr static float s_pollTimeoutFalloffFactor = 2; + constexpr static float s_pollTimeoutFalloffFactor = 1.25; constexpr static int32_t s_pollTimeoutInfinite = -1; - constexpr static int32_t s_pollTimeoutMinMs = 250; - constexpr static int32_t s_pollTimeoutMaxMs = 30000; + constexpr static int32_t s_pollTimeoutMinMs = 10; + constexpr static int32_t s_pollTimeoutMaxMs = 500; }; #endif // FEATURE_PERFTRACING From b0e1218921b1ba2c95faeaea97c7de5933a47976 Mon Sep 17 00:00:00 2001 From: John Salem Date: Sun, 19 Apr 2020 18:41:01 -0700 Subject: [PATCH 47/52] Fix nits from review --- .../debug/debug-pal/win/diagnosticsipc.cpp | 13 +++++++++- src/coreclr/src/debug/inc/diagnosticsipc.h | 25 ++++++++++--------- src/coreclr/src/vm/diagnosticserver.cpp | 2 +- .../src/tracing/eventpipe/reverse/reverse.cs | 6 ++--- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp index 2e76dc16684a6d..81c325f2d867fc 100644 --- a/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -88,7 +88,16 @@ bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) return false; } - _oOverlap.hEvent = CreateEvent(NULL, true, false, NULL); + HANDLE hOverlapEvent = CreateEvent(NULL, true, false, NULL); + if (hOverlapEvent == NULL) + { + if (callback != nullptr) + callback("Failed to create overlap event", ::GetLastError()); + ::CloseHandle(_hPipe); + _hPipe = INVALID_HANDLE_VALUE; + return false; + } + _oOverlap.hEvent = hOverlapEvent; BOOL fSuccess = ::ConnectNamedPipe(_hPipe, &_oOverlap) != 0; if (!fSuccess) @@ -108,7 +117,9 @@ bool IpcStream::DiagnosticsIpc::Listen(ErrorCallback callback) if (callback != nullptr) callback("A client process failed to connect.", errorCode); ::CloseHandle(_hPipe); + _hPipe = INVALID_HANDLE_VALUE; ::CloseHandle(_oOverlap.hEvent); + _oOverlap.hEvent = INVALID_HANDLE_VALUE; return false; } } diff --git a/src/coreclr/src/debug/inc/diagnosticsipc.h b/src/coreclr/src/debug/inc/diagnosticsipc.h index b03a0aac6b7d44..94e817fa70fd67 100644 --- a/src/coreclr/src/debug/inc/diagnosticsipc.h +++ b/src/coreclr/src/debug/inc/diagnosticsipc.h @@ -54,20 +54,21 @@ class IpcStream final // Any values here are ignored by Poll uint8_t revents; - // a callback cookie assignable by upstream users for additional bookkeeping + // a cookie assignable by upstream users for additional bookkeeping void *pUserData; }; // Poll // Paramters: - // - IpcPollHandle *const * rgpIpcPollHandles: Array of pointers to IpcPollHandles to poll - // - uint32_t nHandles: The number of streams to poll + // - IpcPollHandle * rgpIpcPollHandles: Array of IpcPollHandles to poll + // - uint32_t nHandles: The number of handles to poll // - int32_t timeoutMs: The timeout in milliseconds for the poll (-1 == infinite) // Returns: // int32_t: -1 on error, 0 on timeout, >0 on successful poll // Remarks: // Check the events returned in revents for each IpcPollHandle to find the signaled handle. - // Signaled handles will have usable IpcStreams in the pStream field. + // Signaled DiagnosticsIpcs can call Accept() without blocking. + // Signaled IpcStreams can call Read(...) without blocking. // The caller is responsible for cleaning up "hung up" connections. static int32_t Poll(IpcPollHandle *rgIpcPollHandles, uint32_t nHandles, int32_t timeoutMs, ErrorCallback callback = nullptr); @@ -75,20 +76,20 @@ class IpcStream final ~DiagnosticsIpc(); - //! Creates an IPC object + // Creates an IPC object static DiagnosticsIpc *Create(const char *const pIpcName, ConnectionMode mode, ErrorCallback callback = nullptr); - //! puts the DiagnosticsIpc into Listening Mode - //! Re-entrant safe + // puts the DiagnosticsIpc into Listening Mode + // Re-entrant safe bool Listen(ErrorCallback callback = nullptr); - //! produces a client stream from a server-mode DiagnosticsIpc. Blocks until a connection is available. + // produces a connected stream from a server-mode DiagnosticsIpc. Blocks until a connection is available. IpcStream *Accept(ErrorCallback callback = nullptr); - //! Connect to client connection (returns a usable stream) + // Connect to a server and returns a connected stream IpcStream *Connect(ErrorCallback callback = nullptr); - //! Closes an open IPC. + //!Closes an open IPC. void Close(ErrorCallback callback = nullptr); private: @@ -100,8 +101,8 @@ class IpcStream final DiagnosticsIpc(const int serverSocket, sockaddr_un *const pServerAddress, ConnectionMode mode = ConnectionMode::SERVER); - //! Used to unlink the socket so it can be removed from the filesystem - //! when the last reference to it is closed. + // Used to unlink the socket so it can be removed from the filesystem + // when the last reference to it is closed. void Unlink(ErrorCallback callback = nullptr); #else static const uint32_t MaxNamedPipeNameLength = 256; diff --git a/src/coreclr/src/vm/diagnosticserver.cpp b/src/coreclr/src/vm/diagnosticserver.cpp index fcd1eca52506b5..cb01fce5b4bbef 100644 --- a/src/coreclr/src/vm/diagnosticserver.cpp +++ b/src/coreclr/src/vm/diagnosticserver.cpp @@ -147,7 +147,7 @@ bool DiagnosticServer::Initialize() assert(nCharactersWritten != 0); } - // Create the clint mode connection + // Create the client mode connection fSuccess &= IpcStreamFactory::CreateClient(address, ErrorCallback); } diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs index 90c4633238492b..05ce0d0462bf15 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -19,9 +19,9 @@ namespace Tracing.Tests.ReverseValidation { public class ReverseValidation { - // The runtime will do an exponential falloff by a factor of 2 starting at 250ms - // We can time tests out after waiting AT MOST 61,750 ms which should contain 7 attempts to connect - private static int _maxPollTimeMS = /* 250 + 500 + 1000 + 2000 + 4000 + 8000 + 16000 + 30000 = */ 61_750; + // The runtime will do an exponential falloff by a factor of 1.25 starting at 10ms with a max of 500ms + // We can time tests out after waiting 30s which should have sufficient attempts + private static int _maxPollTimeMS = 30_000; private static async Task WaitTillTimeout(Task task, TimeSpan timeout) { From 1cd8f29497fa66193e29c212146103301add6a5e Mon Sep 17 00:00:00 2001 From: John Salem Date: Sun, 19 Apr 2020 18:44:33 -0700 Subject: [PATCH 48/52] simplify branching in GetIpcPollHandle --- src/coreclr/src/vm/ipcstreamfactory.cpp | 31 +++++++++---------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/coreclr/src/vm/ipcstreamfactory.cpp b/src/coreclr/src/vm/ipcstreamfactory.cpp index 288f12e7ea19fe..07e9d4f17ff2d5 100644 --- a/src/coreclr/src/vm/ipcstreamfactory.cpp +++ b/src/coreclr/src/vm/ipcstreamfactory.cpp @@ -17,33 +17,24 @@ bool IpcStreamFactory::ClientConnectionState::GetIpcPollHandle(IpcStream::Diagno { // cache is empty, reconnect, e.g., there was a disconnect IpcStream *pConnection = _pIpc->Connect(callback); - - if (pConnection != nullptr) + if (pConnection == nullptr) { - if (!DiagnosticsIpc::SendIpcAdvertise_V1(pConnection)) - { - if (callback != nullptr) - callback("Failed to send advertise message", -1); - delete pConnection; - return false; - } - - _pStream = pConnection; - *pIpcPollHandle = { nullptr, _pStream, 0, this }; - return true; + if (callback != nullptr) + callback("Failed to connect to client connection", -1); + return false; } - else + if (!DiagnosticsIpc::SendIpcAdvertise_V1(pConnection)) { if (callback != nullptr) - callback("Failed to connect to client connection", -1); + callback("Failed to send advertise message", -1); + delete pConnection; return false; } + + _pStream = pConnection; } - else - { - *pIpcPollHandle = { nullptr, _pStream, 0, this }; - return true; - } + *pIpcPollHandle = { nullptr, _pStream, 0, this }; + return true; } IpcStream *IpcStreamFactory::ClientConnectionState::GetConnectedStream(ErrorCallback callback) From 3530a2506d3bc6606388f299728ea94955da487c Mon Sep 17 00:00:00 2001 From: John Salem Date: Sun, 19 Apr 2020 18:53:06 -0700 Subject: [PATCH 49/52] Add 2 byte field to advertise --- src/coreclr/src/vm/diagnosticsprotocol.h | 6 +++++- .../tests/src/tracing/eventpipe/common/Reverse.cs | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index 593490c67ced3b..e6b646d4187edb 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -115,11 +115,12 @@ namespace DiagnosticsIpc * 8 bytes - "ADVR_V1\0" (ASCII chars + null byte) * 16 bytes - random 128 bit number cookie (little-endian) * 8 bytes - PID (little-endian) + * 2 bytes - unused 2 byte field for futureproofing */ const uint8_t AdvertiseMagic_V1[8] = "ADVR_V1"; - const uint32_t AdvertiseSize = 32; + const uint32_t AdvertiseSize = 34; static GUID AdvertiseCookie_V1 = GUID_NULL; @@ -145,6 +146,9 @@ namespace DiagnosticsIpc buffer[2] = *(uint64_t*)cookie.Data4; buffer[3] = VAL64(pid); + // zero out unused field + *((uint16_t*)advertiseBuffer[32]) = VAL16(0); + uint32_t nBytesWritten = 0; if (!pStream->Write(advertiseBuffer, sizeof(advertiseBuffer), nBytesWritten, 100 /* ms */)) return false; diff --git a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs index 066aa66681c1b6..c4f1c9464e6096 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/common/Reverse.cs @@ -21,17 +21,19 @@ namespace Tracing.Tests.Common * 8 bytes - "ADVR_V1\0" (ASCII chars + null byte) * 16 bytes - CLR Instance Cookie (little-endian) * 8 bytes - PID (little-endian) + * 2 bytes - unused for futureproofing */ public class IpcAdvertise { - public static int Size_V1 => 32; + public static int Size_V1 => 34; public static byte[] Magic_V1 => System.Text.Encoding.ASCII.GetBytes("ADVR_V1" + '\0'); public static int MagicSize_V1 => 8; public byte[] Magic = Magic_V1; public UInt64 ProcessId; public Guid RuntimeInstanceCookie; + public UInt16 Unused; /// /// @@ -44,7 +46,8 @@ public static IpcAdvertise Parse(Stream stream) { Magic = binaryReader.ReadBytes(Magic_V1.Length), RuntimeInstanceCookie = new Guid(binaryReader.ReadBytes(16)), - ProcessId = binaryReader.ReadUInt64() + ProcessId = binaryReader.ReadUInt64(), + Unused = binaryReader.ReadUInt16() }; for (int i = 0; i < Magic_V1.Length; i++) @@ -57,7 +60,7 @@ public static IpcAdvertise Parse(Stream stream) override public string ToString() { - return $"{{ Magic={Magic}; ClrInstanceId={RuntimeInstanceCookie}; ProcessId={ProcessId}; }}"; + return $"{{ Magic={Magic}; ClrInstanceId={RuntimeInstanceCookie}; ProcessId={ProcessId}; Unused={Unused}; }}"; } } public class ReverseServer From 67acd210ef9ff4512f825635846f92e7edffe83b Mon Sep 17 00:00:00 2001 From: John Salem Date: Sun, 19 Apr 2020 21:20:25 -0700 Subject: [PATCH 50/52] Fix cast for advertize --- src/coreclr/src/vm/diagnosticsprotocol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/src/vm/diagnosticsprotocol.h b/src/coreclr/src/vm/diagnosticsprotocol.h index e6b646d4187edb..e6bd3d4e89a8d3 100644 --- a/src/coreclr/src/vm/diagnosticsprotocol.h +++ b/src/coreclr/src/vm/diagnosticsprotocol.h @@ -147,7 +147,7 @@ namespace DiagnosticsIpc buffer[3] = VAL64(pid); // zero out unused field - *((uint16_t*)advertiseBuffer[32]) = VAL16(0); + ((uint16_t*)advertiseBuffer)[16] = VAL16(0); uint32_t nBytesWritten = 0; if (!pStream->Write(advertiseBuffer, sizeof(advertiseBuffer), nBytesWritten, 100 /* ms */)) From 8c83f226e68fddd7a82df078df522f91ead6ed98 Mon Sep 17 00:00:00 2001 From: John Salem Date: Sun, 19 Apr 2020 21:20:51 -0700 Subject: [PATCH 51/52] additional logging in tests --- src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs index 05ce0d0462bf15..5fdc7ca11a235d 100644 --- a/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs +++ b/src/coreclr/tests/src/tracing/eventpipe/reverse/reverse.cs @@ -53,6 +53,7 @@ public static async Task RunSubprocess(string serverName, Func beforeExecu Logger.logger.Log($"running sub-process: {process.StartInfo.FileName} {process.StartInfo.Arguments}"); bool fSuccess = process.Start(); Logger.logger.Log($"subprocess started: {fSuccess}"); + Logger.logger.Log($"subprocess PID: {process.Id}"); while (!EventPipeClient.ListAvailablePorts().Contains(process.Id)) await Task.Delay(100); From 1cbb7205e0e2660f79ff9e3fbfd35791b283948c Mon Sep 17 00:00:00 2001 From: John Salem Date: Sun, 19 Apr 2020 21:21:31 -0700 Subject: [PATCH 52/52] Loops unix send/recv to handle partial io --- .../debug/debug-pal/unix/diagnosticsipc.cpp | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp index 3c9a9be8a8153e..7d3b6b55fda1ef 100644 --- a/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/coreclr/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -341,15 +341,26 @@ bool IpcStream::Read(void *lpBuffer, const uint32_t nBytesToRead, uint32_t &nByt // else fallthrough } - const ssize_t ssize = ::recv(_clientSocket, lpBuffer, nBytesToRead, 0); - const bool fSuccess = ssize != -1; + uint8_t *lpBufferCursor = (uint8_t*)lpBuffer; + ssize_t currentBytesRead = 0; + ssize_t totalBytesRead = 0; + bool fSuccess = true; + while (fSuccess && nBytesToRead - totalBytesRead > 0) + { + currentBytesRead = ::recv(_clientSocket, lpBufferCursor, nBytesToRead - totalBytesRead, 0); + fSuccess = currentBytesRead != 0; + if (!fSuccess) + break; + totalBytesRead += currentBytesRead; + lpBufferCursor += currentBytesRead; + } if (!fSuccess) { // TODO: Add error handling. } - nBytesRead = static_cast(ssize); + nBytesRead = static_cast(totalBytesRead); return fSuccess; } @@ -371,15 +382,26 @@ bool IpcStream::Write(const void *lpBuffer, const uint32_t nBytesToWrite, uint32 // else fallthrough } - const ssize_t ssize = ::send(_clientSocket, lpBuffer, nBytesToWrite, 0); - const bool fSuccess = ssize != -1; + uint8_t *lpBufferCursor = (uint8_t*)lpBuffer; + ssize_t currentBytesWritten = 0; + ssize_t totalBytesWritten = 0; + bool fSuccess = true; + while (fSuccess && nBytesToWrite - totalBytesWritten > 0) + { + currentBytesWritten = ::send(_clientSocket, lpBufferCursor, nBytesToWrite - totalBytesWritten, 0); + fSuccess = currentBytesWritten != -1; + if (!fSuccess) + break; + lpBufferCursor += currentBytesWritten; + totalBytesWritten += currentBytesWritten; + } if (!fSuccess) { // TODO: Add error handling. } - nBytesWritten = static_cast(ssize); + nBytesWritten = static_cast(totalBytesWritten); return fSuccess; }