This is an automated email from the ASF dual-hosted git repository.

jihuayu pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new d8aeed41c fix(common): correct UniqueFD move and reset ownership 
semantics (#3692)
d8aeed41c is described below

commit d8aeed41c386b40a97dce278277b76d2a1726a67
Author: nkysg <[email protected]>
AuthorDate: Mon Oct 19 15:08:24 2026 +0800

    fix(common): correct UniqueFD move and reset ownership semantics (#3692)
    
    ## Summary
    
    Fix ownership-transfer issues in `UniqueFD`.
    
    - Close the currently owned FD before move assignment takes ownership.
    - Make self-move assignment safe.
    - Prevent `Reset(Get())` from closing the FD and retaining a stale
    descriptor value.
    - Use `Release()` to centralize ownership transfer semantics.
    
    These cases could otherwise lead to FD leaks, double-closes, or closing
    an unrelated reused descriptor.
    
    ## Tests
    
    - Added focused unit tests for move assignment, self-move assignment,
    reset with the same FD, and release.
    - `./x.py check format`
    - `./x.py build --unittest`
    - `./x.py test cpp` — 663 passed, 1 skipped
---
 src/common/unique_fd.h          | 21 ++++-------
 tests/cppunit/unique_fd_test.cc | 83 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 91 insertions(+), 13 deletions(-)

diff --git a/src/common/unique_fd.h b/src/common/unique_fd.h
index 57fb46e0b..f59aed224 100644
--- a/src/common/unique_fd.h
+++ b/src/common/unique_fd.h
@@ -36,28 +36,23 @@ struct UniqueFD {
   }
 
   UniqueFD(const UniqueFD&) = delete;
-  UniqueFD(UniqueFD&& f) : fd_(f.fd_) { f.fd_ = NullFD; }
+  UniqueFD(UniqueFD&& f) noexcept : fd_(f.Release()) {}
 
   UniqueFD& operator=(const UniqueFD&) = delete;
-  UniqueFD& operator=(UniqueFD&& f) {
-    fd_ = f.fd_;
-    f.fd_ = NullFD;
+  UniqueFD& operator=(UniqueFD&& f) noexcept {
+    if (this != &f) Reset(f.Release());
     return *this;
   }
 
-  int Release() {
-    int fd = fd_;
-    fd_ = NullFD;
-    return fd;
-  }
+  int Release() noexcept { return std::exchange(fd_, NullFD); }
 
-  void Reset(int fd = NullFD) {
-    int old_fd = fd_;
-    fd_ = fd;
+  void Reset(int fd = NullFD) noexcept {
+    if (fd == fd_) return;
+    const int old_fd = std::exchange(fd_, fd);
     if (old_fd != NullFD) close(old_fd);
   }
 
-  void Close() { Reset(); }
+  void Close() noexcept { Reset(); }
 
   void Swap(UniqueFD& other) { std::swap(fd_, other.fd_); }
 
diff --git a/tests/cppunit/unique_fd_test.cc b/tests/cppunit/unique_fd_test.cc
new file mode 100644
index 000000000..63c68c6a4
--- /dev/null
+++ b/tests/cppunit/unique_fd_test.cc
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 "common/unique_fd.h"
+
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+#include <cerrno>
+#include <utility>
+
+TEST(UniqueFD, MoveAssignmentClosesPreviousDescriptor) {
+  int first_pipe[2];
+  int second_pipe[2];
+  ASSERT_EQ(pipe(first_pipe), 0);
+  ASSERT_EQ(pipe(second_pipe), 0);
+
+  const int previous_fd = first_pipe[0];
+  UniqueFD target(previous_fd);
+  UniqueFD source(second_pipe[0]);
+  target = std::move(source);
+
+  errno = 0;
+  EXPECT_EQ(fcntl(previous_fd, F_GETFD), -1);
+  EXPECT_EQ(errno, EBADF);
+  EXPECT_EQ(target.Get(), second_pipe[0]);
+  EXPECT_FALSE(source);  // NOLINT(bugprone-use-after-move): moved-from state 
is under test.
+
+  close(first_pipe[1]);
+  close(second_pipe[1]);
+}
+
+TEST(UniqueFD, SelfMovePreservesDescriptor) {
+  int pipe_fds[2];
+  ASSERT_EQ(pipe(pipe_fds), 0);
+
+  UniqueFD fd(pipe_fds[0]);
+  fd = std::move(fd);
+
+  EXPECT_EQ(fcntl(fd.Get(), F_GETFD), 0);
+  close(pipe_fds[1]);
+}
+
+TEST(UniqueFD, ResetWithSameDescriptorDoesNotCloseIt) {
+  int pipe_fds[2];
+  ASSERT_EQ(pipe(pipe_fds), 0);
+
+  UniqueFD fd(pipe_fds[0]);
+  fd.Reset(fd.Get());
+
+  EXPECT_EQ(fcntl(fd.Get(), F_GETFD), 0);
+  close(pipe_fds[1]);
+}
+
+TEST(UniqueFD, ReleaseTransfersOwnership) {
+  int pipe_fds[2];
+  ASSERT_EQ(pipe(pipe_fds), 0);
+
+  UniqueFD fd(pipe_fds[0]);
+  const int released_fd = fd.Release();
+
+  EXPECT_FALSE(fd);
+  EXPECT_EQ(fcntl(released_fd, F_GETFD), 0);
+  EXPECT_EQ(close(released_fd), 0);
+  close(pipe_fds[1]);
+}

Reply via email to