From 4497df34cbe3d8bf7a21828714a3cf764da16e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rivero?= Date: Mon, 18 Mar 2019 10:23:30 -0700 Subject: [EventPipe] Adding an IPC server to handle out of process communication. (#23106) This is the first commit to enable a "diagnostic port" using IPC (Named Pipe on Windows and Unix Domain Socket on other platforms). This change currently enable EventPipe to be enabled/disabled without the use of a file drop. - Split the DiagnosticsIpc into (DiagnosticsIpc/IpcStream) - DiagnosticsIpc (IPC listener) is meant to be used by the Diagnostic server. - IpcStream (IPC channel) is meant to be use to communicate with the connected client. - Change the FastSerializer dependency from `CFileStream` to `StreamWriter` This abstraction is meant decouple the writing of objects in order to extend its usability. The main objective is to reuse FastSerializer to stream data through the open IPC channel. - Moved the EventPipeSessionProvider* classes to their own file. - Added a more streamlined parsing achievable by defining a simpler binary protocol (by noahfalk). 1. Only one allocation is needed for the EventPipeProviderConfiguration array, no allocations or copies are needed for strings because we can refer to them directly out of the incoming command buffer 2. No change to the EventPipe API for enable is required. EventPipeProviderConfiguration retains its current behavior of not deleting the string pointers it holds. 3. No leaks happen because the command buffer owns the string memory and ensures that it stays alive for the duration of the Enable() call. --- src/vm/diagnosticserver.cpp | 171 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/vm/diagnosticserver.cpp (limited to 'src/vm/diagnosticserver.cpp') diff --git a/src/vm/diagnosticserver.cpp b/src/vm/diagnosticserver.cpp new file mode 100644 index 0000000000..c0e11d100f --- /dev/null +++ b/src/vm/diagnosticserver.cpp @@ -0,0 +1,171 @@ +// 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 "diagnosticserver.h" +#include "diagnosticsipc.h" +#include "eventpipeprotocolhelper.h" + +#ifdef FEATURE_PAL +#include "pal.h" +#endif // FEATURE_PAL + +#ifdef FEATURE_PERFTRACING + +static DWORD WINAPI DiagnosticsServerThread(LPVOID lpThreadParameter) +{ + CONTRACTL + { + // TODO: Maybe this should not throw. + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(lpThreadParameter != nullptr); + } + CONTRACTL_END; + + auto pIpc = reinterpret_cast(lpThreadParameter); + if (pIpc == nullptr) + { + STRESS_LOG0(LF_STARTUP, LL_ERROR,"Diagnostics IPC listener was undefined\n"); + return 1; + } + +#ifdef _DEBUG + ErrorCallback LoggingCallback = [](const char *szMessage, uint32_t code) { + LOG((LF_REMOTING, LL_WARNING, "warning (%d): %s.\n", code, szMessage)); + }; +#else + ErrorCallback LoggingCallback = nullptr; +#endif + + while (true) + { + // FIXME: Ideally this would be something like a std::shared_ptr + IpcStream *pStream = pIpc->Accept(LoggingCallback); + if (pStream == nullptr) + continue; + + // TODO: Read operation should happen in a loop. + uint32_t nNumberOfBytesRead = 0; + MessageHeader header; + bool fSuccess = pStream->Read(&header, sizeof(header), nNumberOfBytesRead); + if (!fSuccess || nNumberOfBytesRead != sizeof(header)) + { + delete pStream; + continue; + } + + // TODO: Dispatch thread worker. + switch (header.RequestType) + { + case DiagnosticMessageType::EnableEventPipe: + EventPipeProtocolHelper::EnableFileTracingEventHandler(pStream); + break; + + case DiagnosticMessageType::DisableEventPipe: + EventPipeProtocolHelper::DisableTracingEventHandler(pStream); + break; + + default: + LOG((LF_REMOTING, LL_WARNING, "Received unknow request type (%d)\n", header.RequestType)); + break; + } + } + + return 0; +} + +bool DiagnosticServer::Initialize() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + bool fSuccess = false; + + EX_TRY + { + auto ErrorCallback = [](const char *szMessage, uint32_t code) { + STRESS_LOG2( + LF_STARTUP, // facility + LL_ERROR, // level + "Failed to create diagnostic IPC: error (%d): %s.\n", // msg + code, // data1 + szMessage); // data2 + }; + IpcStream::DiagnosticsIpc *pIpc = IpcStream::DiagnosticsIpc::Create( + "dotnetcore-diagnostic", ErrorCallback); + + if (pIpc != nullptr) + { + DWORD dwThreadId = 0; + HANDLE hThread = ::CreateThread( // TODO: Is it correct to have this "lower" level call here? + nullptr, // no security attribute + 0, // default stack size + DiagnosticsServerThread, // thread proc + (LPVOID)pIpc, // thread parameter + 0, // not suspended + &dwThreadId); // returns thread ID + + if (hThread == nullptr) + { + // Failed to create IPC thread. + STRESS_LOG1( + LF_STARTUP, // facility + LL_ERROR, // level + "Failed to create diagnostic server thread (%d).\n", // msg + ::GetLastError()); // data1 + } + else + { + // FIXME: Maybe hold on to the thread to abort/cleanup at exit? + ::CloseHandle(hThread); + + // TODO: Add error handling? + fSuccess = true; + } + } + } + EX_CATCH + { + // TODO: Should we log anything here? + } + EX_END_CATCH(SwallowAllExceptions); + + return fSuccess; +} + +bool DiagnosticServer::Shutdown() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + bool fSuccess = false; + + EX_TRY + { + // FIXME: Stop IPC server thread? + fSuccess = true; + } + EX_CATCH + { + fSuccess = false; + // TODO: Should we log anything here? + } + EX_END_CATCH(SwallowAllExceptions); + + return fSuccess; +} + +#endif // FEATURE_PERFTRACING -- cgit v1.2.3