[llvm] [orc-rt] Add SocketHandle, NativeSocketHandle APIs (PR #223114)

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 11 18:55:24 PDT 2026


https://github.com/lhames created https://github.com/llvm/llvm-project/pull/223114

SocketHandle is an owning wrapper for a NativeSocketHandle value, and closes the native socket on destruction.

NativeSocketHandle is a typedef for the platform's native socket handle type.

Only POSIX is implemented in this patch (in sys/posix/SocketHandle.cpp). Windows support will be added later.

This will be used for an upcoming SimpleRemote-based ControllerAccess implementation that uses sockets for transport (SocketSimpleRemoteCA).

>From dceebc4227ddaa8ee42f9b16c3c79b0dd23cfb39 Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Fri, 11 Sep 2026 18:25:50 +1000
Subject: [PATCH] [orc-rt] Add SocketHandle, NativeSocketHandle APIs

SocketHandle is an owning wrapper for a NativeSocketHandle value,
and closes the native socket on destruction.

NativeSocketHandle is a typedef for the platform's native socket
handle type.

Only POSIX is implemented in this patch (in sys/posix/SocketHandle.cpp).
Windows support will be added later.

This will be used for an upcoming SimpleRemote-based ControllerAccess
implementation that uses sockets for transport (SocketSimpleRemoteCA).
---
 orc-rt/include/CMakeLists.txt                 |   1 +
 orc-rt/include/orc-rt/bedrock/SocketHandle.h  |  90 ++++++++++++++
 orc-rt/lib/bedrock/CMakeLists.txt             |   4 +-
 orc-rt/lib/bedrock/sys/posix/SocketHandle.cpp |  37 ++++++
 orc-rt/test/unit/CMakeLists.txt               |  17 +++
 orc-rt/test/unit/bedrock/SocketHandleTest.cpp | 116 ++++++++++++++++++
 orc-rt/test/unit/bedrock/SocketTestUtils.h    |  35 ++++++
 .../bedrock/sys/posix/SocketTestUtils.cpp     |  36 ++++++
 8 files changed, 335 insertions(+), 1 deletion(-)
 create mode 100644 orc-rt/include/orc-rt/bedrock/SocketHandle.h
 create mode 100644 orc-rt/lib/bedrock/sys/posix/SocketHandle.cpp
 create mode 100644 orc-rt/test/unit/bedrock/SocketHandleTest.cpp
 create mode 100644 orc-rt/test/unit/bedrock/SocketTestUtils.h
 create mode 100644 orc-rt/test/unit/bedrock/sys/posix/SocketTestUtils.cpp

diff --git a/orc-rt/include/CMakeLists.txt b/orc-rt/include/CMakeLists.txt
index 60b004f8a1aa5..b2ae5a7a226a1 100644
--- a/orc-rt/include/CMakeLists.txt
+++ b/orc-rt/include/CMakeLists.txt
@@ -19,6 +19,7 @@ set(ORC_RT_HEADERS
     orc-rt/bedrock/Session.h
     orc-rt/bedrock/SimpleNativeMemoryMap.h
     orc-rt/bedrock/SimpleSymbolTable.h
+    orc-rt/bedrock/SocketHandle.h
     orc-rt/bedrock/StandaloneMachOUnwindInfoRegistrar.h
     orc-rt/bedrock/TaskGroup.h
     orc-rt/bedrock/ThreadPoolRunner.h
diff --git a/orc-rt/include/orc-rt/bedrock/SocketHandle.h b/orc-rt/include/orc-rt/bedrock/SocketHandle.h
new file mode 100644
index 0000000000000..0f157ff51c779
--- /dev/null
+++ b/orc-rt/include/orc-rt/bedrock/SocketHandle.h
@@ -0,0 +1,90 @@
+//===- SocketHandle.h - An owning socket handle -----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// The system's socket type, and an owner for one.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_BEDROCK_SOCKETHANDLE_H
+#define ORC_RT_BEDROCK_SOCKETHANDLE_H
+
+#include <cstdint>
+#include <utility>
+
+namespace orc_rt {
+
+#if defined(_WIN32)
+
+/// Winsock's SOCKET, spelled as the integer it is a typedef for so that this
+/// header does not pull in <winsock2.h>. The two are the same type.
+///
+/// TODO: Untested. There is no Windows reset() yet, and Winsock also needs
+/// WSAStartup called somewhere.
+using NativeSocketHandle = uintptr_t;
+
+/// Winsock's INVALID_SOCKET.
+inline constexpr NativeSocketHandle InvalidNativeSocketHandle =
+    ~static_cast<NativeSocketHandle>(0);
+
+#else
+
+using NativeSocketHandle = int;
+
+inline constexpr NativeSocketHandle InvalidNativeSocketHandle = -1;
+
+#endif
+
+/// Owns a socket, closing it on destruction.
+///
+/// Ownership only: says nothing about the socket's family or protocol, or
+/// whether it is connected.
+class SocketHandle {
+public:
+  SocketHandle() = default;
+
+  /// Adopts H. Adopting InvalidNativeSocketHandle gives an empty handle, so the
+  /// result of a socket call can be adopted before it is checked.
+  explicit SocketHandle(NativeSocketHandle H) noexcept : H(H) {}
+
+  SocketHandle(const SocketHandle &) = delete;
+  SocketHandle &operator=(const SocketHandle &) = delete;
+
+  SocketHandle(SocketHandle &&Other) noexcept : H(Other.release()) {}
+  SocketHandle &operator=(SocketHandle &&Other) noexcept {
+    // Take before closing: the other order closes the socket on a self-move.
+    NativeSocketHandle Incoming = Other.release();
+    reset();
+    H = Incoming;
+    return *this;
+  }
+
+  ~SocketHandle() { reset(); }
+
+  explicit operator bool() const noexcept {
+    return H != InvalidNativeSocketHandle;
+  }
+
+  /// The socket, which remains this handle's to close.
+  NativeSocketHandle get() const noexcept { return H; }
+
+  /// Surrenders the socket to the caller, who must close it.
+  [[nodiscard]] NativeSocketHandle release() noexcept {
+    return std::exchange(H, InvalidNativeSocketHandle);
+  }
+
+  /// Closes the socket, if any. A failed close is logged rather than reported:
+  /// the socket is gone either way.
+  void reset() noexcept;
+
+private:
+  NativeSocketHandle H = InvalidNativeSocketHandle;
+};
+
+} // namespace orc_rt
+
+#endif // ORC_RT_BEDROCK_SOCKETHANDLE_H
diff --git a/orc-rt/lib/bedrock/CMakeLists.txt b/orc-rt/lib/bedrock/CMakeLists.txt
index 640210a4d1ff1..d9435d67e7d9b 100644
--- a/orc-rt/lib/bedrock/CMakeLists.txt
+++ b/orc-rt/lib/bedrock/CMakeLists.txt
@@ -41,6 +41,7 @@ set(ORC_RT_BEDROCK_POSIX_SOURCES
   sys/posix/DynamicLibrary.cpp
   sys/posix/Memory.cpp
   sys/posix/PageSize.cpp
+  sys/posix/SocketHandle.cpp
 )
 
 set(ORC_RT_BEDROCK_DARWIN_SOURCES
@@ -54,7 +55,8 @@ set(ORC_RT_BEDROCK_LINUX_SOURCES
 )
 
 # TODO: Windows has no counterparts for the sys/posix/ sources yet
-# (DynamicLibrary, Memory, PageSize), so Windows builds are incomplete.
+# (DynamicLibrary, Memory, PageSize, SocketHandle), so Windows builds are
+# incomplete.
 set(ORC_RT_BEDROCK_WINDOWS_SOURCES
   sys/windows/CPUFeatures.cpp
   sys/windows/TargetTriple.cpp
diff --git a/orc-rt/lib/bedrock/sys/posix/SocketHandle.cpp b/orc-rt/lib/bedrock/sys/posix/SocketHandle.cpp
new file mode 100644
index 0000000000000..7d61dd2f3e1e7
--- /dev/null
+++ b/orc-rt/lib/bedrock/sys/posix/SocketHandle.cpp
@@ -0,0 +1,37 @@
+//===- SocketHandle.cpp - POSIX SocketHandle implementation -----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// POSIX implementation of orc-rt/bedrock/SocketHandle.h.
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/bedrock/SocketHandle.h"
+
+#include "orc-rt-c/support/Logging.h"
+#include "orc-rt-internal/support/sys/Errno.h"
+
+#include <cerrno>
+#include <string>
+#include <unistd.h>
+
+namespace orc_rt {
+
+void SocketHandle::reset() noexcept {
+  if (H == InvalidNativeSocketHandle)
+    return;
+  // Not retried on EINTR: close releases the descriptor before the steps that
+  // can fail, so a retry could close one that another thread has since been
+  // given.
+  if (::close(H) != 0 && errno != EINTR) {
+    [[maybe_unused]] std::string Msg = sys::strError(errno);
+    ORC_RT_LOG(Info, General, "SocketHandle: close failed: %s", Msg.c_str());
+  }
+  H = InvalidNativeSocketHandle;
+}
+
+} // namespace orc_rt
diff --git a/orc-rt/test/unit/CMakeLists.txt b/orc-rt/test/unit/CMakeLists.txt
index bfcca8df3a385..dd4303a01386d 100644
--- a/orc-rt/test/unit/CMakeLists.txt
+++ b/orc-rt/test/unit/CMakeLists.txt
@@ -69,6 +69,20 @@ add_orc_rt_unittest(SupportTests
   LINK_LIBS orc-rt-support-objects
   )
 
+# Per-system test helpers, composed the way lib/ composes its per-system
+# implementations; see the note in lib/bedrock/CMakeLists.txt. Tests of portable
+# APIs stay in the shared list and reach the system through these.
+set(ORC_RT_BEDROCK_TEST_POSIX_SOURCES
+  bedrock/sys/posix/SocketTestUtils.cpp
+)
+
+if (APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
+  set(ORC_RT_BEDROCK_TEST_SYS_SOURCES ${ORC_RT_BEDROCK_TEST_POSIX_SOURCES})
+else()
+  # No list yet, so tests needing a socket will fail to link.
+  set(ORC_RT_BEDROCK_TEST_SYS_SOURCES)
+endif()
+
 add_orc_rt_unittest(BedrockTests
   bedrock/BootstrapInfoTest.cpp
   bedrock/ExecutorProcessInfoTest.cpp
@@ -78,6 +92,7 @@ add_orc_rt_unittest(BedrockTests
   bedrock/SessionTest.cpp
   bedrock/SimpleNativeMemoryMapTest.cpp
   bedrock/SimpleSymbolTableTest.cpp
+  bedrock/SocketHandleTest.cpp
   bedrock/StandaloneMachOUnwindInfoRegistrarTest.cpp
   bedrock/TaskGroupTest.cpp
   bedrock/ThreadPoolRunnerTest.cpp
@@ -91,6 +106,8 @@ add_orc_rt_unittest(BedrockTests
   bedrock/sys/CPUFeaturesTest.cpp
   bedrock/sys/TargetTripleTest.cpp
 
+  ${ORC_RT_BEDROCK_TEST_SYS_SOURCES}
+
   LINK_LIBS orc-rt-bedrock-objects orc-rt-support-objects
   )
 
diff --git a/orc-rt/test/unit/bedrock/SocketHandleTest.cpp b/orc-rt/test/unit/bedrock/SocketHandleTest.cpp
new file mode 100644
index 0000000000000..eb609d932aa44
--- /dev/null
+++ b/orc-rt/test/unit/bedrock/SocketHandleTest.cpp
@@ -0,0 +1,116 @@
+//===- SocketHandleTest.cpp -----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// These cover the ownership bookkeeping in SocketHandle.h, which every platform
+// compiles verbatim, so they are shared rather than written per system.
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/bedrock/SocketHandle.h"
+#include "gtest/gtest.h"
+
+#include "SocketTestUtils.h"
+
+#include <utility>
+
+using namespace orc_rt;
+
+namespace {
+
+/// A socket for a test to own, failing the test if the system refuses one.
+NativeSocketHandle testSocket() {
+  auto H = makeNativeSocket();
+  EXPECT_TRUE(H.has_value()) << "could not create a socket for the test";
+  return H.value_or(InvalidNativeSocketHandle);
+}
+
+TEST(SocketHandleTest, DefaultConstructedOwnsNothing) {
+  SocketHandle H;
+  EXPECT_FALSE(H);
+  EXPECT_EQ(H.get(), InvalidNativeSocketHandle);
+  // Must not try to close the sentinel.
+  H.reset();
+  EXPECT_FALSE(H);
+  EXPECT_EQ(H.release(), InvalidNativeSocketHandle);
+}
+
+TEST(SocketHandleTest, AdoptingAFailedSocketCallGivesAnEmptyHandle) {
+  // So that a socket call's result can be adopted before it is checked.
+  SocketHandle H(InvalidNativeSocketHandle);
+  EXPECT_FALSE(H);
+}
+
+TEST(SocketHandleTest, ResetCloses) {
+  NativeSocketHandle Raw = testSocket();
+  SocketHandle H(Raw);
+  ASSERT_TRUE(isNativeSocketOpen(Raw));
+
+  H.reset();
+  EXPECT_FALSE(H);
+  EXPECT_FALSE(isNativeSocketOpen(Raw));
+}
+
+TEST(SocketHandleTest, DestructorCloses) {
+  NativeSocketHandle Raw = testSocket();
+  {
+    SocketHandle H(Raw);
+    ASSERT_TRUE(isNativeSocketOpen(Raw));
+  }
+  EXPECT_FALSE(isNativeSocketOpen(Raw));
+}
+
+TEST(SocketHandleTest, ReleaseGivesUpOwnership) {
+  NativeSocketHandle Raw = testSocket();
+  {
+    SocketHandle H(Raw);
+    EXPECT_EQ(H.release(), Raw);
+    EXPECT_FALSE(H);
+  }
+  EXPECT_TRUE(isNativeSocketOpen(Raw));
+  closeNativeSocket(Raw);
+}
+
+TEST(SocketHandleTest, MoveConstructionTransfersOwnership) {
+  NativeSocketHandle Raw = testSocket();
+  SocketHandle Source(Raw);
+
+  SocketHandle Moved(std::move(Source));
+  EXPECT_FALSE(Source) << "moved-from handle still owns a socket";
+  ASSERT_TRUE(Moved);
+  EXPECT_EQ(Moved.get(), Raw);
+  EXPECT_TRUE(isNativeSocketOpen(Raw));
+}
+
+TEST(SocketHandleTest, MoveAssignmentClosesTheOldSocket) {
+  NativeSocketHandle Replaced = testSocket();
+  NativeSocketHandle Kept = testSocket();
+
+  SocketHandle Target(Replaced);
+  SocketHandle Source(Kept);
+  Target = std::move(Source);
+
+  EXPECT_FALSE(isNativeSocketOpen(Replaced)) << "overwritten socket was leaked";
+  EXPECT_TRUE(isNativeSocketOpen(Kept));
+  EXPECT_EQ(Target.get(), Kept);
+  EXPECT_FALSE(Source);
+}
+
+TEST(SocketHandleTest, SelfMoveAssignmentKeepsTheSocket) {
+  NativeSocketHandle Raw = testSocket();
+  SocketHandle H(Raw);
+
+  // Through a reference because -Wself-move rejects the direct form, which is
+  // what a caller reaching this via a template or a swap would write.
+  SocketHandle &Alias = H;
+  H = std::move(Alias);
+
+  EXPECT_TRUE(isNativeSocketOpen(Raw));
+  EXPECT_EQ(H.get(), Raw);
+}
+
+} // namespace
diff --git a/orc-rt/test/unit/bedrock/SocketTestUtils.h b/orc-rt/test/unit/bedrock/SocketTestUtils.h
new file mode 100644
index 0000000000000..b127af0507bb9
--- /dev/null
+++ b/orc-rt/test/unit/bedrock/SocketTestUtils.h
@@ -0,0 +1,35 @@
+//===- SocketTestUtils.h --------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Sockets for tests of the socket APIs, defined once per system under
+// sys/<system>/ so that a test needing one stays portable.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_UNITTEST_BEDROCK_SOCKETTESTUTILS_H
+#define ORC_RT_UNITTEST_BEDROCK_SOCKETTESTUTILS_H
+
+#include "orc-rt/bedrock/SocketHandle.h"
+
+#include <optional>
+
+/// Creates a socket for a test to own, or nullopt if the system refuses one.
+/// The socket is neither bound nor connected.
+std::optional<orc_rt::NativeSocketHandle> makeNativeSocket();
+
+/// True if H names a socket this process still has open.
+///
+/// Only meaningful while nothing else in the process is opening sockets: a
+/// closed handle's value can be reissued to the next caller, which is
+/// indistinguishable from the original still being open.
+bool isNativeSocketOpen(orc_rt::NativeSocketHandle H);
+
+/// Closes H, which must be open and owned by no SocketHandle.
+void closeNativeSocket(orc_rt::NativeSocketHandle H);
+
+#endif // ORC_RT_UNITTEST_BEDROCK_SOCKETTESTUTILS_H
diff --git a/orc-rt/test/unit/bedrock/sys/posix/SocketTestUtils.cpp b/orc-rt/test/unit/bedrock/sys/posix/SocketTestUtils.cpp
new file mode 100644
index 0000000000000..2e1a60a6968d0
--- /dev/null
+++ b/orc-rt/test/unit/bedrock/sys/posix/SocketTestUtils.cpp
@@ -0,0 +1,36 @@
+//===- SocketTestUtils.cpp ------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// POSIX definitions of bedrock/SocketTestUtils.h.
+//
+//===----------------------------------------------------------------------===//
+
+#include "bedrock/SocketTestUtils.h"
+
+#include <cerrno>
+#include <sys/socket.h>
+#include <unistd.h>
+
+using namespace orc_rt;
+
+std::optional<NativeSocketHandle> makeNativeSocket() {
+  // Unbound, so this needs no network, peer or filesystem entry.
+  NativeSocketHandle H = ::socket(AF_UNIX, SOCK_STREAM, 0);
+  if (H == InvalidNativeSocketHandle)
+    return std::nullopt;
+  return H;
+}
+
+bool isNativeSocketOpen(NativeSocketHandle H) {
+  // A zero-length send moves no data and needs no peer. An unconnected socket
+  // refuses it with ENOTCONN, which still says the descriptor is there; only a
+  // closed one reports EBADF.
+  return ::send(H, "", 0, 0) != -1 || errno != EBADF;
+}
+
+void closeNativeSocket(NativeSocketHandle H) { ::close(H); }



More information about the llvm-commits mailing list