diff --git a/src/ray/common/status.h b/src/ray/common/status.h index c7bd759352b37..29ac2ddf1ccf4 100644 --- a/src/ray/common/status.h +++ b/src/ray/common/status.h @@ -313,6 +313,8 @@ class RAY_EXPORT Status { // Returns the string "OK" for success. std::string ToString() const; + std::string StatusString() const { return ToString(); } + // Return a string representation of the status code, without the message // text or posix code information. std::string CodeAsString() const; diff --git a/src/ray/common/status_or.h b/src/ray/common/status_or.h index 7b0cc0d65a769..88eb99a7a3864 100644 --- a/src/ray/common/status_or.h +++ b/src/ray/common/status_or.h @@ -151,6 +151,8 @@ class StatusOr { ABSL_MUST_USE_RESULT std::string message() const { return status_.message(); } + std::string StatusString() const { return status_.StatusString(); } + // Returns a reference to the current `ray::Status` contained within the // `ray::StatusOr`. If `ray::StatusOr` contains a `T`, then this // function returns `ray::Ok()`. diff --git a/src/ray/common/test/testing.h b/src/ray/common/test/testing.h index 9a699bacf1452..53162a4391ae0 100644 --- a/src/ray/common/test/testing.h +++ b/src/ray/common/test/testing.h @@ -16,6 +16,6 @@ #pragma once -#define RAY_EXPECT_OK(s) EXPECT_TRUE((s).ok()) +#define RAY_EXPECT_OK(s) EXPECT_TRUE((s).ok()) << s.StatusString() -#define RAY_ASSERT_OK(s) ASSERT_TRUE((s).ok()) +#define RAY_ASSERT_OK(s) ASSERT_TRUE((s).ok()) << s.StatusString() diff --git a/src/ray/util/BUILD b/src/ray/util/BUILD index 7e8af43039878..65f34c637aff8 100644 --- a/src/ray/util/BUILD +++ b/src/ray/util/BUILD @@ -325,3 +325,16 @@ ray_cc_library( "@com_google_absl//absl/synchronization", ], ) + +ray_cc_library( + name = "dup2_wrapper", + hdrs = ["dup2_wrapper.h"], + srcs = select({ + "@platforms//os:windows": ["dup2_wrapper_windows.cc"], + "//conditions:default": ["dup2_wrapper_posix.cc"], + }), + deps = [ + ":compat", + ":logging", + ], +) diff --git a/src/ray/util/dup2_wrapper.h b/src/ray/util/dup2_wrapper.h new file mode 100644 index 0000000000000..18cfb39274006 --- /dev/null +++ b/src/ray/util/dup2_wrapper.h @@ -0,0 +1,43 @@ +// Copyright 2025 The Ray Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "ray/util/compat.h" + +namespace ray { + +class Dup2Wrapper { + public: + // Duplicate [oldfd] to [newfd], same semantics with syscall `dup2`. + static std::unique_ptr New(MEMFD_TYPE_NON_UNIQUE oldfd, + MEMFD_TYPE_NON_UNIQUE newfd); + + Dup2Wrapper(const Dup2Wrapper &) = delete; + Dup2Wrapper &operator=(const Dup2Wrapper &) = delete; + + // Restore oldfd. + ~Dup2Wrapper(); + + private: + Dup2Wrapper(MEMFD_TYPE_NON_UNIQUE oldfd, MEMFD_TYPE_NON_UNIQUE restorefd) + : oldfd_(oldfd), restorefd_(restorefd) {} + + MEMFD_TYPE_NON_UNIQUE oldfd_; + MEMFD_TYPE_NON_UNIQUE restorefd_; +}; + +} // namespace ray diff --git a/src/ray/util/dup2_wrapper_posix.cc b/src/ray/util/dup2_wrapper_posix.cc new file mode 100644 index 0000000000000..4393a84fd56a3 --- /dev/null +++ b/src/ray/util/dup2_wrapper_posix.cc @@ -0,0 +1,42 @@ +// Copyright 2025 The Ray Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "ray/util/dup2_wrapper.h" +#include "ray/util/logging.h" + +namespace ray { + +/*static*/ std::unique_ptr Dup2Wrapper::New(int oldfd, int newfd) { + const int restorefd = dup(oldfd); + RAY_CHECK_NE(restorefd, -1) << "Fails to duplicate oldfd " << oldfd << " because " + << strerror(errno); + + const int ret = dup2(oldfd, newfd); + RAY_CHECK_NE(ret, -1) << "Fails to duplicate oldfd " << oldfd << " to " << newfd + << " because " << strerror(errno); + + return std::unique_ptr(new Dup2Wrapper(oldfd, restorefd)); +} + +Dup2Wrapper::~Dup2Wrapper() { + const int ret = dup2(oldfd_, restorefd_); + RAY_CHECK_NE(ret, -1) << "Fails to duplicate oldfd " << oldfd_ << " to " << restorefd_ + << " because " << strerror(errno); +} + +} // namespace ray diff --git a/src/ray/util/dup2_wrapper_windows.cc b/src/ray/util/dup2_wrapper_windows.cc new file mode 100644 index 0000000000000..fc729ec86443e --- /dev/null +++ b/src/ray/util/dup2_wrapper_windows.cc @@ -0,0 +1,53 @@ +// Copyright 2025 The Ray Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ray/util/dup2_wrapper.h" + +namespace ray { + +/*static*/ std::unique_ptr Dup2Wrapper::New(HANDLE oldfd, HANLE newfd) { + HANDLE restorefd = NULL; + BOOL success = DuplicateHandle(GetCurrentProcess(), + oldfd, + GetCurrentProcess(), + &restorefd, + 0, + FALSE, + DUPLICATE_SAME_ACCESS); + RAY_CHECK(success); + + success = DuplicateHandle(GetCurrentProcess(), + oldfd, + GetCurrentProcess(), + &newfd, + 0, + FALSE, + DUPLICATE_SAME_ACCESS); + RAY_CHECK(success); + + return std::unique_ptr(new Dup2Wrapper(oldfd, restorefd)); +} + +Dup2Wrapper::~Dup2Wrapper() { + BOOL success = DuplicateHandle(GetCurrentProcess(), + oldfd_, + GetCurrentProcess(), + &restorefd_, + 0, + FALSE, + DUPLICATE_SAME_ACCESS); + RAY_CHECK(success); +} + +} // namespace ray diff --git a/src/ray/util/tests/BUILD b/src/ray/util/tests/BUILD index 43cf6ca11e28a..d3a5d7d0f8185 100644 --- a/src/ray/util/tests/BUILD +++ b/src/ray/util/tests/BUILD @@ -323,3 +323,16 @@ ray_cc_test( size = "small", tags = ["team:core"], ) + +ray_cc_test( + name = "dup2_wrapper_test", + srcs = ["dup2_wrapper_test.cc"], + deps = [ + "//src/ray/common/test:testing", + "//src/ray/util:compat", + "//src/ray/util:dup2_wrapper", + "//src/ray/util:filesystem", + "//src/ray/util:temporary_directory", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/ray/util/tests/dup2_wrapper_test.cc b/src/ray/util/tests/dup2_wrapper_test.cc new file mode 100644 index 0000000000000..ea3f1c9f36abe --- /dev/null +++ b/src/ray/util/tests/dup2_wrapper_test.cc @@ -0,0 +1,79 @@ +// Copyright 2025 The Ray Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ray/util/dup2_wrapper.h" + +#include +#include +#include + +#include + +#include "ray/common/test/testing.h" +#include "ray/util/compat.h" +#include "ray/util/filesystem.h" +#include "ray/util/temporary_directory.h" + +namespace ray { + +namespace { + +constexpr std::string_view kContent = "helloworld\n"; + +TEST(Dup2WrapperTest, BasicTest) { + ScopedTemporaryDirectory temp_dir; + const auto dir = temp_dir.GetDirectory(); + const auto path = dir / "test_file"; + const std::string path_string = path.string(); + +#if defined(__APPLE__) || defined(__linux__) + int fd = open(path_string.data(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + ASSERT_NE(fd, -1); +#elif defined(_WIN32) + HANDLE fd = CreateFile(path_string, // File name + GENERIC_READ | GENERIC_WRITE, // Access mode: read/write + 0, // No sharing + NULL, // Default security attributes + OPEN_ALWAYS, // Open file if it exists, create if it doesn't + FILE_ATTRIBUTE_NORMAL, // File attributes + NULL); // No template file + + // Check if the file was successfully opened or created + ASSERT_NE(fd, INVALID_HANDLE_VALUE); +#endif + + { + auto dup2_wrapper = Dup2Wrapper::New(/*oldfd=*/fd, /*newfd=*/GetStderrHandle()); + + // Write to stdout should appear in file. + std::cerr << kContent << std::flush; + const auto actual_content = ReadEntireFile(path_string); + RAY_ASSERT_OK(actual_content); + EXPECT_EQ(*actual_content, kContent); + } + + testing::internal::CaptureStderr(); + std::cerr << kContent << std::flush; + const std::string stderr_content = testing::internal::GetCapturedStderr(); + EXPECT_EQ(stderr_content, kContent); + + // Not changed since last write. + const auto actual_content = ReadEntireFile(path_string); + RAY_ASSERT_OK(actual_content); + EXPECT_EQ(*actual_content, kContent); +} + +} // namespace + +} // namespace ray