Răsfoiți Sursa

Merge topic 'debugger-pipe-connections'

8b1257e7bf Debugger: Replace libuv with platform-specific connection code

Acked-by: Kitware Robot <[email protected]>
Acked-by: buildbot <[email protected]>
Merge-request: !8711
Brad King 2 ani în urmă
părinte
comite
df0a32f48e

+ 15 - 3
Source/CMakeLists.txt

@@ -783,8 +783,6 @@ if(CMake_ENABLE_DEBUGGER)
       cmDebuggerBreakpointManager.h
       cmDebuggerExceptionManager.cxx
       cmDebuggerExceptionManager.h
-      cmDebuggerPipeConnection.cxx
-      cmDebuggerPipeConnection.h
       cmDebuggerProtocol.cxx
       cmDebuggerProtocol.h
       cmDebuggerSourceBreakpoint.cxx
@@ -802,6 +800,21 @@ if(CMake_ENABLE_DEBUGGER)
       cmDebuggerVariablesManager.cxx
       cmDebuggerVariablesManager.h
     )
+  if(WIN32)
+    target_sources(
+    CMakeLib
+    PRIVATE
+      cmDebuggerWindowsPipeConnection.cxx
+      cmDebuggerWindowsPipeConnection.h
+    )
+  else()
+    target_sources(
+    CMakeLib
+    PRIVATE
+      cmDebuggerPosixPipeConnection.cxx
+      cmDebuggerPosixPipeConnection.h
+    )
+  endif()
   target_link_libraries(CMakeLib PUBLIC cppdap::cppdap)
 endif()
 
@@ -953,7 +966,6 @@ if(CMake_BUILD_PCH)
     "$<$<COMPILE_LANGUAGE:CXX>:cmArgumentParser.h>"
     "$<$<COMPILE_LANGUAGE:CXX>:cmake.h>"
     "$<$<COMPILE_LANGUAGE:CXX>:cmCMakePath.h>"
-    "$<$<COMPILE_LANGUAGE:CXX>:cmDebuggerPipeConnection.h>"
     "$<$<COMPILE_LANGUAGE:CXX>:cmCurl.h>")
 
     set_source_files_properties(

+ 0 - 293
Source/cmDebuggerPipeConnection.cxx

@@ -1,293 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#include "cmDebuggerPipeConnection.h"
-
-#include <algorithm>
-#include <cassert>
-#include <cstring>
-#include <stdexcept>
-#include <utility>
-
-namespace cmDebugger {
-
-struct write_req_t
-{
-  uv_write_t req;
-  uv_buf_t buf;
-};
-
-cmDebuggerPipeBase::cmDebuggerPipeBase(std::string name)
-  : PipeName(std::move(name))
-{
-  Loop.init();
-  LoopExit.init(
-    *Loop, [](uv_async_t* handle) { uv_stop((uv_loop_t*)handle->data); },
-    Loop);
-  WriteEvent.init(
-    *Loop,
-    [](uv_async_t* handle) {
-      auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data);
-      conn->WriteInternal();
-    },
-    this);
-  PipeClose.init(
-    *Loop,
-    [](uv_async_t* handle) {
-      auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data);
-      if (conn->Pipe.get()) {
-        conn->Pipe->data = nullptr;
-        conn->Pipe.reset();
-      }
-    },
-    this);
-}
-
-void cmDebuggerPipeBase::WaitForConnection()
-{
-  std::unique_lock<std::mutex> lock(Mutex);
-  Connected.wait(lock, [this] { return isOpen() || FailedToOpen; });
-  if (FailedToOpen) {
-    throw std::runtime_error("Failed to open debugger connection.");
-  }
-}
-
-void cmDebuggerPipeBase::close()
-{
-  std::unique_lock<std::mutex> lock(Mutex);
-
-  CloseConnection();
-  PipeClose.send();
-  lock.unlock();
-  ReadReady.notify_all();
-}
-
-size_t cmDebuggerPipeBase::read(void* buffer, size_t n)
-{
-  std::unique_lock<std::mutex> lock(Mutex);
-  ReadReady.wait(lock, [this] { return !isOpen() || !ReadBuffer.empty(); });
-
-  if (!isOpen() && ReadBuffer.empty()) {
-    return 0;
-  }
-
-  auto size = std::min(n, ReadBuffer.size());
-  memcpy(buffer, ReadBuffer.data(), size);
-  ReadBuffer.erase(0, size);
-  return size;
-}
-
-bool cmDebuggerPipeBase::write(const void* buffer, size_t n)
-{
-  std::unique_lock<std::mutex> lock(Mutex);
-  WriteBuffer.append(static_cast<const char*>(buffer), n);
-  lock.unlock();
-  WriteEvent.send();
-
-  lock.lock();
-  WriteComplete.wait(lock, [this] { return WriteBuffer.empty(); });
-  return true;
-}
-
-void cmDebuggerPipeBase::StopLoop()
-{
-  LoopExit.send();
-
-  if (LoopThread.joinable()) {
-    LoopThread.join();
-  }
-}
-
-void cmDebuggerPipeBase::BufferData(const std::string& data)
-{
-  std::unique_lock<std::mutex> lock(Mutex);
-  ReadBuffer += data;
-  lock.unlock();
-  ReadReady.notify_all();
-}
-
-void cmDebuggerPipeBase::WriteInternal()
-{
-  std::unique_lock<std::mutex> lock(Mutex);
-  auto n = WriteBuffer.length();
-  assert(this->Pipe.get());
-  write_req_t* req = new write_req_t;
-  req->req.data = &WriteComplete;
-  char* rawBuffer = new char[n];
-  req->buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(n));
-  memcpy(req->buf.base, WriteBuffer.data(), n);
-  WriteBuffer.clear();
-  lock.unlock();
-
-  uv_write(
-    reinterpret_cast<uv_write_t*>(req), this->Pipe, &req->buf, 1,
-    [](uv_write_t* cb_req, int status) {
-      (void)status; // We need to free memory even if the write failed.
-      write_req_t* wr = reinterpret_cast<write_req_t*>(cb_req);
-      reinterpret_cast<std::condition_variable*>(wr->req.data)->notify_all();
-      delete[] (wr->buf.base);
-      delete wr;
-    });
-
-#ifdef __clang_analyzer__
-  // Tell clang-analyzer that 'rawBuffer' does not leak.
-  // We pass ownership to the closure.
-  delete[] rawBuffer;
-#endif
-}
-
-cmDebuggerPipeConnection::cmDebuggerPipeConnection(std::string name)
-  : cmDebuggerPipeBase(std::move(name))
-{
-  ServerPipeClose.init(
-    *Loop,
-    [](uv_async_t* handle) {
-      auto* conn = static_cast<cmDebuggerPipeConnection*>(handle->data);
-      if (conn->ServerPipe.get()) {
-        conn->ServerPipe->data = nullptr;
-        conn->ServerPipe.reset();
-      }
-    },
-    this);
-}
-
-cmDebuggerPipeConnection::~cmDebuggerPipeConnection()
-{
-  StopLoop();
-}
-
-bool cmDebuggerPipeConnection::StartListening(std::string& errorMessage)
-{
-  this->ServerPipe.init(*Loop, 0,
-                        static_cast<cmDebuggerPipeConnection*>(this));
-
-  int r;
-  if ((r = uv_pipe_bind(this->ServerPipe, this->PipeName.c_str())) != 0) {
-    errorMessage =
-      "Internal Error with " + this->PipeName + ": " + uv_err_name(r);
-    return false;
-  }
-
-  r = uv_listen(this->ServerPipe, 1, [](uv_stream_t* stream, int status) {
-    if (status >= 0) {
-      auto* conn = static_cast<cmDebuggerPipeConnection*>(stream->data);
-      if (conn) {
-        conn->Connect(stream);
-      }
-    }
-  });
-
-  if (r != 0) {
-    errorMessage =
-      "Internal Error listening on " + this->PipeName + ": " + uv_err_name(r);
-    return false;
-  }
-
-  // Start the libuv event loop thread so that a client can connect.
-  LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); });
-
-  StartedListening.set_value();
-
-  return true;
-}
-
-std::shared_ptr<dap::Reader> cmDebuggerPipeConnection::GetReader()
-{
-  return std::static_pointer_cast<dap::Reader>(shared_from_this());
-}
-
-std::shared_ptr<dap::Writer> cmDebuggerPipeConnection::GetWriter()
-{
-  return std::static_pointer_cast<dap::Writer>(shared_from_this());
-}
-
-bool cmDebuggerPipeConnection::isOpen()
-{
-  return this->Pipe.get() != nullptr;
-}
-
-void cmDebuggerPipeConnection::CloseConnection()
-{
-  ServerPipeClose.send();
-}
-
-void cmDebuggerPipeConnection::Connect(uv_stream_t* server)
-{
-  if (this->Pipe.get()) {
-    // Accept and close all pipes but the first:
-    cm::uv_pipe_ptr rejectPipe;
-
-    rejectPipe.init(*Loop, 0);
-    uv_accept(server, rejectPipe);
-
-    return;
-  }
-
-  cm::uv_pipe_ptr ClientPipe;
-  ClientPipe.init(*Loop, 0, static_cast<cmDebuggerPipeConnection*>(this));
-
-  if (uv_accept(server, ClientPipe) != 0) {
-    return;
-  }
-
-  StartReading<cmDebuggerPipeConnection>(ClientPipe);
-
-  std::unique_lock<std::mutex> lock(Mutex);
-  Pipe = std::move(ClientPipe);
-  lock.unlock();
-  Connected.notify_all();
-}
-
-cmDebuggerPipeClient::~cmDebuggerPipeClient()
-{
-  StopLoop();
-}
-
-void cmDebuggerPipeClient::Start()
-{
-  this->Pipe.init(*Loop, 0, static_cast<cmDebuggerPipeClient*>(this));
-
-  uv_connect_t* connect = new uv_connect_t;
-  connect->data = this;
-  uv_pipe_connect(
-    connect, Pipe, PipeName.c_str(), [](uv_connect_t* cb_connect, int status) {
-      auto* conn = static_cast<cmDebuggerPipeClient*>(cb_connect->data);
-      if (status >= 0) {
-        conn->Connect();
-      } else {
-        conn->FailConnection();
-      }
-      delete cb_connect;
-    });
-
-  // Start the libuv event loop so that the pipe can connect.
-  LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); });
-}
-
-bool cmDebuggerPipeClient::isOpen()
-{
-  return IsConnected;
-}
-
-void cmDebuggerPipeClient::CloseConnection()
-{
-  IsConnected = false;
-}
-
-void cmDebuggerPipeClient::Connect()
-{
-  StartReading<cmDebuggerPipeClient>(Pipe);
-  std::unique_lock<std::mutex> lock(Mutex);
-  IsConnected = true;
-  lock.unlock();
-  Connected.notify_all();
-}
-
-void cmDebuggerPipeClient::FailConnection()
-{
-  std::unique_lock<std::mutex> lock(Mutex);
-  FailedToOpen = true;
-  lock.unlock();
-  Connected.notify_all();
-}
-
-} // namespace cmDebugger

+ 0 - 139
Source/cmDebuggerPipeConnection.h

@@ -1,139 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#pragma once
-
-#include "cmConfigure.h" // IWYU pragma: keep
-
-#include <condition_variable>
-#include <cstddef>
-#include <future>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <thread>
-
-#include <cm3p/cppdap/io.h>
-#include <cm3p/uv.h>
-
-#include "cmDebuggerAdapter.h"
-#include "cmUVHandlePtr.h"
-
-namespace cmDebugger {
-
-class cmDebuggerPipeBase : public dap::ReaderWriter
-{
-public:
-  cmDebuggerPipeBase(std::string name);
-
-  void WaitForConnection();
-
-  // dap::ReaderWriter implementation
-
-  void close() final;
-  size_t read(void* buffer, size_t n) final;
-  bool write(const void* buffer, size_t n) final;
-
-protected:
-  virtual void CloseConnection(){};
-  template <typename T>
-  void StartReading(uv_stream_t* stream)
-  {
-    uv_read_start(
-      stream,
-      // alloc_cb
-      [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
-        (void)handle;
-        char* rawBuffer = new char[suggested_size];
-        *buf =
-          uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size));
-      },
-      // read_cb
-      [](uv_stream_t* readStream, ssize_t nread, const uv_buf_t* buf) {
-        auto conn = static_cast<T*>(readStream->data);
-        if (conn) {
-          if (nread >= 0) {
-            conn->BufferData(std::string(buf->base, buf->base + nread));
-          } else {
-            conn->close();
-          }
-        }
-        delete[] (buf->base);
-      });
-  }
-  void StopLoop();
-
-  const std::string PipeName;
-  std::thread LoopThread;
-  cm::uv_loop_ptr Loop;
-  cm::uv_pipe_ptr Pipe;
-  std::mutex Mutex;
-  std::condition_variable Connected;
-  bool FailedToOpen = false;
-
-private:
-  void BufferData(const std::string& data);
-  void WriteInternal();
-
-  cm::uv_async_ptr LoopExit;
-  cm::uv_async_ptr WriteEvent;
-  cm::uv_async_ptr PipeClose;
-  std::string WriteBuffer;
-  std::string ReadBuffer;
-  std::condition_variable ReadReady;
-  std::condition_variable WriteComplete;
-};
-
-class cmDebuggerPipeConnection
-  : public cmDebuggerPipeBase
-  , public cmDebuggerConnection
-  , public std::enable_shared_from_this<cmDebuggerPipeConnection>
-{
-public:
-  cmDebuggerPipeConnection(std::string name);
-  ~cmDebuggerPipeConnection() override;
-
-  void WaitForConnection() override
-  {
-    cmDebuggerPipeBase::WaitForConnection();
-  }
-
-  bool StartListening(std::string& errorMessage) override;
-  std::shared_ptr<dap::Reader> GetReader() override;
-  std::shared_ptr<dap::Writer> GetWriter() override;
-
-  // dap::ReaderWriter implementation
-
-  bool isOpen() override;
-
-  // Used for unit test synchronization
-  std::promise<void> StartedListening;
-
-private:
-  void CloseConnection() override;
-  void Connect(uv_stream_t* server);
-
-  cm::uv_pipe_ptr ServerPipe;
-  cm::uv_async_ptr ServerPipeClose;
-};
-
-class cmDebuggerPipeClient : public cmDebuggerPipeBase
-{
-public:
-  using cmDebuggerPipeBase::cmDebuggerPipeBase;
-  ~cmDebuggerPipeClient() override;
-
-  void Start();
-
-  // dap::ReaderWriter implementation
-
-  bool isOpen() override;
-
-private:
-  void CloseConnection() override;
-  void Connect();
-  void FailConnection();
-
-  bool IsConnected = false;
-};
-
-} // namespace cmDebugger

+ 205 - 0
Source/cmDebuggerPosixPipeConnection.cxx

@@ -0,0 +1,205 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmDebuggerPosixPipeConnection.h"
+
+#include <cerrno>
+#include <cstring>
+#include <stdexcept>
+#include <utility>
+
+#include <unistd.h>
+
+#include <sys/socket.h>
+
+namespace cmDebugger {
+
+#ifndef _WIN32
+
+cmDebuggerPipeConnection_POSIX::cmDebuggerPipeConnection_POSIX(
+  std::string name)
+  : PipeName(std::move(name))
+{
+  addr.sun_path[0] = '\0';
+}
+
+cmDebuggerPipeConnection_POSIX::~cmDebuggerPipeConnection_POSIX()
+{
+  if (isOpen()) {
+    close();
+  }
+}
+
+bool cmDebuggerPipeConnection_POSIX::StartListening(std::string& errorMessage)
+{
+  listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+  if (listen_fd < 0) {
+    errorMessage = "Failed to create socket: ";
+    errorMessage += strerror(errno);
+    return false;
+  }
+
+  addr.sun_family = AF_UNIX;
+  strncpy(addr.sun_path, PipeName.c_str(), sizeof(addr.sun_path));
+  addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
+  if (bind(listen_fd, (sockaddr*)&addr, sizeof(addr)) == -1) {
+    errorMessage = "Failed to bind name '";
+    errorMessage += addr.sun_path;
+    errorMessage += "' to socket: ";
+    errorMessage += strerror(errno);
+    close_listen();
+    return false;
+  }
+
+  if (listen(listen_fd, 1) == -1) {
+    errorMessage = "Failed to listen on socket: ";
+    errorMessage += strerror(errno);
+    close_listen();
+    return false;
+  }
+
+  StartedListening.set_value();
+  return true;
+}
+
+std::shared_ptr<dap::Reader> cmDebuggerPipeConnection_POSIX::GetReader()
+{
+  return std::static_pointer_cast<dap::Reader>(shared_from_this());
+}
+
+std::shared_ptr<dap::Writer> cmDebuggerPipeConnection_POSIX::GetWriter()
+{
+  return std::static_pointer_cast<dap::Writer>(shared_from_this());
+}
+
+bool cmDebuggerPipeConnection_POSIX::isOpen()
+{
+  return rw_pipe >= 0;
+}
+
+void cmDebuggerPipeConnection_POSIX::close()
+{
+  close_listen();
+  ::close(rw_pipe);
+  rw_pipe = -1;
+}
+
+void cmDebuggerPipeConnection_POSIX::close_listen()
+{
+  if (strlen(addr.sun_path) > 0) {
+    unlink(addr.sun_path);
+    addr.sun_path[0] = '\0';
+  }
+  ::close(listen_fd);
+  listen_fd = -1;
+}
+
+void cmDebuggerPipeConnection_POSIX::WaitForConnection()
+{
+  sockaddr_un laddr;
+  socklen_t len = sizeof(laddr);
+  rw_pipe = accept(listen_fd, (sockaddr*)&laddr, &len);
+  if (rw_pipe < 0) {
+    close();
+    return;
+  }
+
+  close_listen(); // no longer need the listen resources
+}
+
+size_t cmDebuggerPipeConnection_POSIX::read(void* buffer, size_t n)
+{
+  size_t result = 0;
+  if (rw_pipe >= 0) {
+    result = ::read(rw_pipe, buffer, n);
+    if (result == 0) {
+      close();
+    }
+  }
+
+  return result;
+}
+
+bool cmDebuggerPipeConnection_POSIX::write(void const* buffer, size_t n)
+{
+  bool result = false;
+  if (rw_pipe >= 0) {
+    result = ::write(rw_pipe, buffer, n) >= 0;
+    if (!result) {
+      close();
+    }
+  }
+
+  return result;
+}
+
+cmDebuggerPipeClient_POSIX::cmDebuggerPipeClient_POSIX(std::string name)
+  : PipeName(std::move(name))
+{
+}
+
+cmDebuggerPipeClient_POSIX::~cmDebuggerPipeClient_POSIX()
+{
+  close();
+}
+
+void cmDebuggerPipeClient_POSIX::WaitForConnection()
+{
+  rw_pipe = socket(AF_UNIX, SOCK_STREAM, 0);
+  if (rw_pipe < 0) {
+    throw std::runtime_error(std::string("Failed to create socket: ") +
+                             strerror(errno));
+  }
+
+  sockaddr_un addr;
+  addr.sun_family = AF_UNIX;
+  strncpy(addr.sun_path, PipeName.c_str(), sizeof(addr.sun_path));
+  addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
+  if (connect(rw_pipe, (sockaddr*)&addr, sizeof(addr)) == -1) {
+    close();
+    throw std::runtime_error(
+      std::string("Failed to connect path to socket: ") + strerror(errno));
+  }
+}
+
+bool cmDebuggerPipeClient_POSIX::isOpen()
+{
+  return rw_pipe >= 0;
+}
+
+void cmDebuggerPipeClient_POSIX::close()
+{
+  if (isOpen()) {
+    ::close(rw_pipe);
+    rw_pipe = -1;
+  }
+}
+
+size_t cmDebuggerPipeClient_POSIX::read(void* buffer, size_t n)
+{
+  int count = 0;
+  if (isOpen()) {
+    count = static_cast<int>(::read(rw_pipe, buffer, n));
+    if (count == 0) {
+      close();
+    }
+  }
+
+  return count;
+}
+
+bool cmDebuggerPipeClient_POSIX::write(void const* buffer, size_t n)
+{
+  int count = 0;
+  if (isOpen()) {
+    count = static_cast<int>(::write(rw_pipe, buffer, n));
+    if (count < 0) {
+      close();
+    }
+  }
+
+  return count > 0;
+}
+
+#endif // !_WIN32
+
+} // namespace cmDebugger

+ 81 - 0
Source/cmDebuggerPosixPipeConnection.h

@@ -0,0 +1,81 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <future>
+#include <memory>
+#include <string>
+
+#include <cm3p/cppdap/io.h>
+
+#include <sys/un.h>
+
+#include "cmDebuggerAdapter.h"
+
+namespace cmDebugger {
+
+#ifndef _WIN32
+
+class cmDebuggerPipeConnection_POSIX
+  : public dap::ReaderWriter
+  , public cmDebuggerConnection
+  , public std::enable_shared_from_this<cmDebuggerPipeConnection_POSIX>
+{
+public:
+  cmDebuggerPipeConnection_POSIX(std::string name);
+  ~cmDebuggerPipeConnection_POSIX() override;
+
+  void WaitForConnection() override;
+
+  bool StartListening(std::string& errorMessage) override;
+  std::shared_ptr<dap::Reader> GetReader() override;
+  std::shared_ptr<dap::Writer> GetWriter() override;
+
+  // dap::ReaderWriter implementation
+
+  bool isOpen() override;
+  void close() override;
+  size_t read(void* buffer, size_t n) override;
+  bool write(void const* buffer, size_t n) override;
+
+  // Used for unit test synchronization
+  std::promise<void> StartedListening;
+
+private:
+  void close_listen(); // release listen resources
+
+  std::string const PipeName;
+  sockaddr_un addr;
+  int listen_fd = -1; // listen fd
+  int rw_pipe = -1;   // rw fd
+};
+
+using cmDebuggerPipeConnection = cmDebuggerPipeConnection_POSIX;
+
+class cmDebuggerPipeClient_POSIX
+  : public dap::ReaderWriter
+  , public std::enable_shared_from_this<cmDebuggerPipeClient_POSIX>
+{
+public:
+  cmDebuggerPipeClient_POSIX(std::string name);
+  ~cmDebuggerPipeClient_POSIX() override;
+  void WaitForConnection();
+
+  bool isOpen() override;
+  void close() override;
+  size_t read(void* buffer, size_t n) override;
+  bool write(void const* buffer, size_t n) override;
+
+private:
+  std::string const PipeName;
+  int rw_pipe = -1;
+};
+
+using cmDebuggerPipeClient = cmDebuggerPipeClient_POSIX;
+
+#endif // !_WIN32
+
+} // namespace cmDebugger

+ 272 - 0
Source/cmDebuggerWindowsPipeConnection.cxx

@@ -0,0 +1,272 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmDebuggerWindowsPipeConnection.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <stdexcept>
+#include <utility>
+
+namespace cmDebugger {
+
+#ifdef _WIN32
+
+DuplexPipe_WIN32::DuplexPipe_WIN32(HANDLE pipe)
+  : hPipe(pipe)
+{
+  readOp.Offset = readOp.OffsetHigh = 0;
+  readOp.hEvent = CreateEvent(NULL, true, false, NULL);
+  writeOp.Offset = readOp.OffsetHigh = 0;
+  writeOp.hEvent = CreateEvent(NULL, true, false, NULL);
+}
+
+DuplexPipe_WIN32::~DuplexPipe_WIN32()
+{
+  close();
+}
+
+size_t DuplexPipe_WIN32::read(void* buffer, size_t n)
+{
+  if (hPipe != INVALID_HANDLE_VALUE) {
+    readOp.Offset = readOp.OffsetHigh = 0;
+    ResetEvent(readOp.hEvent);
+    auto r = ReadFile(hPipe, buffer, n, NULL, &readOp);
+    auto err = GetLastError();
+    if (r || err == ERROR_IO_PENDING) {
+      DWORD nRead = 0;
+      if (GetOverlappedResult(hPipe, &readOp, &nRead, true)) {
+        return nRead;
+      }
+    }
+  }
+
+  return 0;
+}
+
+bool DuplexPipe_WIN32::write(void const* buffer, size_t n)
+{
+  if (hPipe != INVALID_HANDLE_VALUE) {
+    writeOp.Offset = writeOp.OffsetHigh = 0;
+    ResetEvent(writeOp.hEvent);
+    auto w = WriteFile(hPipe, buffer, n, NULL, &writeOp);
+    auto err = GetLastError();
+    if (w || err == ERROR_IO_PENDING) {
+      DWORD nWrite = 0;
+      if (GetOverlappedResult(hPipe, &writeOp, &nWrite, true)) {
+        return n == nWrite;
+      }
+    }
+  }
+
+  return false;
+}
+
+void DuplexPipe_WIN32::close()
+{
+  CloseHandle(hPipe);
+  hPipe = INVALID_HANDLE_VALUE;
+  CloseHandle(readOp.hEvent);
+  CloseHandle(writeOp.hEvent);
+  readOp.hEvent = writeOp.hEvent = INVALID_HANDLE_VALUE;
+}
+
+bool DuplexPipe_WIN32::WaitForConnection()
+{
+  auto connect = ConnectNamedPipe(hPipe, &readOp);
+  auto err = GetLastError();
+  if (!connect && err == ERROR_IO_PENDING) {
+    DWORD ignored;
+    if (GetOverlappedResult(hPipe, &readOp, &ignored, true)) {
+      return true;
+    }
+  }
+
+  return connect || err == ERROR_PIPE_CONNECTED;
+}
+
+cmDebuggerPipeConnection_WIN32::cmDebuggerPipeConnection_WIN32(
+  std::string name)
+  : PipeName(std::move(name))
+  , pipes(nullptr)
+{
+}
+
+cmDebuggerPipeConnection_WIN32::~cmDebuggerPipeConnection_WIN32()
+{
+  if (isOpen()) {
+    pipes = nullptr;
+  }
+}
+
+bool cmDebuggerPipeConnection_WIN32::StartListening(std::string& errorMessage)
+{
+  bool result = true;
+
+  auto hPipe = CreateNamedPipeA(
+    PipeName.c_str(),
+    PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
+    PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS, 1,
+    1024 * 16, 1024 * 16, NMPWAIT_USE_DEFAULT_WAIT, NULL);
+
+  if (hPipe == INVALID_HANDLE_VALUE) {
+    auto err = GetLastError();
+    errorMessage = GetErrorMessage(err);
+    result = false;
+  }
+
+  if (result) {
+    pipes = std::make_unique<DuplexPipe_WIN32>(hPipe);
+  }
+
+  StartedListening.set_value();
+  return result;
+}
+
+std::string cmDebuggerPipeConnection_WIN32::GetErrorMessage(DWORD errorCode)
+{
+  LPSTR message = nullptr;
+  DWORD size = FormatMessageA(
+    FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+      FORMAT_MESSAGE_IGNORE_INSERTS,
+    nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+    (LPSTR)&message, 0, nullptr);
+  std::string errorMessage = "Internal Error with " + this->PipeName + ": " +
+    std::string(message, size);
+  LocalFree(message);
+  return errorMessage;
+}
+
+std::shared_ptr<dap::Reader> cmDebuggerPipeConnection_WIN32::GetReader()
+{
+  return std::static_pointer_cast<dap::Reader>(shared_from_this());
+}
+
+std::shared_ptr<dap::Writer> cmDebuggerPipeConnection_WIN32::GetWriter()
+{
+  return std::static_pointer_cast<dap::Writer>(shared_from_this());
+}
+
+bool cmDebuggerPipeConnection_WIN32::isOpen()
+{
+  return pipes != nullptr;
+}
+
+void cmDebuggerPipeConnection_WIN32::close()
+{
+  CloseConnection();
+}
+
+void cmDebuggerPipeConnection_WIN32::CloseConnection()
+{
+  if (isOpen()) {
+    pipes->close();
+    pipes = nullptr;
+  }
+}
+
+void cmDebuggerPipeConnection_WIN32::WaitForConnection()
+{
+  if (!isOpen()) {
+    return;
+  }
+
+  if (pipes->WaitForConnection()) {
+    return;
+  }
+
+  CloseConnection();
+}
+
+size_t cmDebuggerPipeConnection_WIN32::read(void* buffer, size_t n)
+{
+  size_t result = 0;
+  if (isOpen()) {
+    result = pipes->read(buffer, n);
+    if (result == 0) {
+      CloseConnection();
+    }
+  }
+
+  return result;
+}
+
+bool cmDebuggerPipeConnection_WIN32::write(void const* buffer, size_t n)
+{
+  bool result = false;
+  if (isOpen()) {
+    result = pipes->write(buffer, n);
+    if (!result) {
+      CloseConnection();
+    }
+  }
+
+  return result;
+}
+
+cmDebuggerPipeClient_WIN32::cmDebuggerPipeClient_WIN32(std::string name)
+  : PipeName(std::move(name))
+{
+}
+
+cmDebuggerPipeClient_WIN32::~cmDebuggerPipeClient_WIN32()
+{
+  close();
+}
+
+void cmDebuggerPipeClient_WIN32::WaitForConnection()
+{
+  if (!isOpen()) {
+    auto hPipe = CreateFileA(PipeName.c_str(), GENERIC_READ | GENERIC_WRITE, 0,
+                             NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+    if (hPipe == INVALID_HANDLE_VALUE) {
+      auto err = GetLastError();
+      throw std::runtime_error("CreateFile failed with " + err);
+    }
+
+    pipes = std::make_unique<DuplexPipe_WIN32>(hPipe);
+  }
+}
+
+bool cmDebuggerPipeClient_WIN32::isOpen()
+{
+  return pipes != nullptr;
+}
+
+void cmDebuggerPipeClient_WIN32::close()
+{
+  if (isOpen()) {
+    pipes->close();
+    pipes = nullptr;
+  }
+}
+
+size_t cmDebuggerPipeClient_WIN32::read(void* buffer, size_t n)
+{
+  size_t result = 0;
+  if (isOpen()) {
+    result = pipes->read(buffer, n);
+    if (result == 0) {
+      close();
+    }
+  }
+
+  return result;
+}
+
+bool cmDebuggerPipeClient_WIN32::write(void const* buffer, size_t n)
+{
+  bool result = false;
+  if (isOpen()) {
+    result = pipes->write(buffer, n);
+    if (!result) {
+      close();
+    }
+  }
+
+  return result;
+}
+
+#endif // _WIN32
+
+} // namespace cmDebugger

+ 101 - 0
Source/cmDebuggerWindowsPipeConnection.h

@@ -0,0 +1,101 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <condition_variable>
+#include <cstddef>
+#include <future>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+
+#include <windows.h>
+
+#include <cm3p/cppdap/io.h>
+
+#include "cmDebuggerAdapter.h"
+
+namespace cmDebugger {
+
+#ifdef _WIN32
+
+class DuplexPipe_WIN32
+{
+public:
+  DuplexPipe_WIN32(HANDLE read);
+  ~DuplexPipe_WIN32();
+
+  void close();
+  size_t read(void* buffer, size_t n);
+  bool write(void const* buffer, size_t n);
+
+  bool WaitForConnection();
+
+private:
+  HANDLE hPipe;
+  OVERLAPPED readOp;
+  OVERLAPPED writeOp;
+};
+
+class cmDebuggerPipeConnection_WIN32
+  : public dap::ReaderWriter
+  , public cmDebuggerConnection
+  , public std::enable_shared_from_this<cmDebuggerPipeConnection_WIN32>
+{
+public:
+  cmDebuggerPipeConnection_WIN32(std::string name);
+  ~cmDebuggerPipeConnection_WIN32() override;
+
+  void WaitForConnection() override;
+
+  bool StartListening(std::string& errorMessage) override;
+  std::shared_ptr<dap::Reader> GetReader() override;
+  std::shared_ptr<dap::Writer> GetWriter() override;
+
+  // dap::ReaderWriter implementation
+
+  bool isOpen() override;
+  void close() override;
+  size_t read(void* buffer, size_t n) override;
+  bool write(void const* buffer, size_t n) override;
+
+  // Used for unit test synchronization
+  std::promise<void> StartedListening;
+
+private:
+  void CloseConnection();
+  std::string GetErrorMessage(DWORD errorCode);
+
+  std::string const PipeName;
+  std::unique_ptr<DuplexPipe_WIN32> pipes;
+};
+
+using cmDebuggerPipeConnection = cmDebuggerPipeConnection_WIN32;
+
+class cmDebuggerPipeClient_WIN32
+  : public dap::ReaderWriter
+  , public std::enable_shared_from_this<cmDebuggerPipeClient_WIN32>
+{
+public:
+  cmDebuggerPipeClient_WIN32(std::string name);
+  ~cmDebuggerPipeClient_WIN32();
+  void WaitForConnection();
+
+  bool isOpen() override;
+  void close() override;
+  size_t read(void* buffer, size_t n) override;
+  bool write(void const* buffer, size_t n) override;
+
+private:
+  std::string const PipeName;
+  std::unique_ptr<DuplexPipe_WIN32> pipes;
+};
+
+using cmDebuggerPipeClient = cmDebuggerPipeClient_WIN32;
+
+#endif // _WIN32
+
+} // namespace cmDebugger

+ 5 - 1
Source/cmake.cxx

@@ -37,7 +37,11 @@
 #include "cmCommands.h"
 #ifdef CMake_ENABLE_DEBUGGER
 #  include "cmDebuggerAdapter.h"
-#  include "cmDebuggerPipeConnection.h"
+#  ifdef _WIN32
+#    include "cmDebuggerWindowsPipeConnection.h"
+#  else //!_WIN32
+#    include "cmDebuggerPosixPipeConnection.h"
+#  endif //_WIN32
 #endif
 #include "cmDocumentation.h"
 #include "cmDocumentationEntry.h"

+ 4 - 2
Tests/CMakeLib/testDebuggerAdapterPipe.cxx

@@ -19,13 +19,15 @@
 #include <cm3p/cppdap/types.h>
 
 #include "cmDebuggerAdapter.h"
-#include "cmDebuggerPipeConnection.h"
 #include "cmDebuggerProtocol.h"
 #include "cmVersionConfig.h"
 
 #ifdef _WIN32
 #  include "cmCryptoHash.h"
+#  include "cmDebuggerWindowsPipeConnection.h"
 #  include "cmSystemTools.h"
+#else
+#  include "cmDebuggerPosixPipeConnection.h"
 #endif
 
 #include "testCommon.h"
@@ -128,7 +130,7 @@ bool testProtocolWithPipes()
 
   auto client2Debugger =
     std::make_shared<cmDebugger::cmDebuggerPipeClient>(namedPipe);
-  client2Debugger->Start();
+
   client2Debugger->WaitForConnection();
   client->bind(client2Debugger, client2Debugger);
 

+ 7 - 2
Tests/CMakeLib/testDebuggerNamedPipe.cxx

@@ -16,7 +16,12 @@
 
 #include "cmsys/RegularExpression.hxx"
 
-#include "cmDebuggerPipeConnection.h"
+#ifdef _WIN32
+#  include "cmDebuggerWindowsPipeConnection.h"
+#else
+#  include "cmDebuggerPosixPipeConnection.h"
+#endif
+
 #include "cmSystemTools.h"
 
 #ifdef _WIN32
@@ -104,7 +109,7 @@ int runTest(int argc, char* argv[])
       attempt++;
       try {
         client = std::make_shared<cmDebugger::cmDebuggerPipeClient>(namedPipe);
-        client->Start();
+
         client->WaitForConnection();
         std::cout << "cmDebuggerPipeClient connected.\n";
         break;