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

guangmingchen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new da4e33a6 Support a more safe read function and variadic functions of 
DoublyBufferedData (#2898)
da4e33a6 is described below

commit da4e33a6ee44d107bc0dd9632b2db289aa815cdd
Author: Bright Chen <chenguangmin...@foxmail.com>
AuthorDate: Fri Mar 21 23:42:43 2025 +0800

    Support a more safe read function and variadic functions of 
DoublyBufferedData (#2898)
    
    * Add a convenient and safe DoublyBufferedData::Read
    
    * Optimize and add a UT
    
    * Support variadic functions of DoublyBufferedData
    
    * Fix Fn copy
---
 src/butil/containers/doubly_buffered_data.h | 138 +++++++---------------------
 src/butil/type_traits.h                     |   6 +-
 test/brpc_load_balancer_unittest.cpp        |  25 ++++-
 3 files changed, 60 insertions(+), 109 deletions(-)

diff --git a/src/butil/containers/doubly_buffered_data.h 
b/src/butil/containers/doubly_buffered_data.h
index 62f67ead..5c97038b 100644
--- a/src/butil/containers/doubly_buffered_data.h
+++ b/src/butil/containers/doubly_buffered_data.h
@@ -123,83 +123,26 @@ public:
     // Returns 0 on success, -1 otherwise.
     int Read(ScopedPtr* ptr);
 
+    // `fn(const T&)' will be called with foreground instance.
+    // This function is not blocked by Read() and Modify() in other threads.
+    // Returns 0 on success, otherwise on error.
+    template<typename Fn>
+    int Read(Fn&& fn);
+
     // Modify background and foreground instances. fn(T&, ...) will be called
     // twice. Modify() from different threads are exclusive from each other.
     // NOTE: Call same series of fn to different equivalent instances should
     // result in equivalent instances, otherwise foreground and background
     // instance will be inconsistent.
-    template <typename Fn> size_t Modify(Fn& fn);
-    template <typename Fn, typename Arg1> size_t Modify(Fn& fn, const Arg1&);
-    template <typename Fn, typename Arg1, typename Arg2>
-    size_t Modify(Fn& fn, const Arg1&, const Arg2&);
+    template <typename Fn, typename... Args>
+    size_t Modify(Fn&& fn, Args&&... args);
 
     // fn(T& background, const T& foreground, ...) will be called to background
     // and foreground instances respectively.
-    template <typename Fn> size_t ModifyWithForeground(Fn& fn);
-    template <typename Fn, typename Arg1>
-    size_t ModifyWithForeground(Fn& fn, const Arg1&);
-    template <typename Fn, typename Arg1, typename Arg2>
-    size_t ModifyWithForeground(Fn& fn, const Arg1&, const Arg2&);
+    template <typename Fn, typename... Args>
+    size_t ModifyWithForeground(Fn&& fn, Args&&... args);
     
 private:
-    template <typename Fn>
-    struct WithFG0 {
-        WithFG0(Fn& fn, T* data) : _fn(fn), _data(data) { }
-        size_t operator()(T& bg) {
-            return _fn(bg, (const T&)_data[&bg == _data]);
-        }
-    private:
-        Fn& _fn;
-        T* _data;
-    };
-
-    template <typename Fn, typename Arg1>
-    struct WithFG1 {
-        WithFG1(Fn& fn, T* data, const Arg1& arg1)
-            : _fn(fn), _data(data), _arg1(arg1) {}
-        size_t operator()(T& bg) {
-            return _fn(bg, (const T&)_data[&bg == _data], _arg1);
-        }
-    private:
-        Fn& _fn;
-        T* _data;
-        const Arg1& _arg1;
-    };
-
-    template <typename Fn, typename Arg1, typename Arg2>
-    struct WithFG2 {
-        WithFG2(Fn& fn, T* data, const Arg1& arg1, const Arg2& arg2)
-            : _fn(fn), _data(data), _arg1(arg1), _arg2(arg2) {}
-        size_t operator()(T& bg) {
-            return _fn(bg, (const T&)_data[&bg == _data], _arg1, _arg2);
-        }
-    private:
-        Fn& _fn;
-        T* _data;
-        const Arg1& _arg1;
-        const Arg2& _arg2;
-    };
-
-    template <typename Fn, typename Arg1>
-    struct Closure1 {
-        Closure1(Fn& fn, const Arg1& arg1) : _fn(fn), _arg1(arg1) {}
-        size_t operator()(T& bg) { return _fn(bg, _arg1); }
-    private:
-        Fn& _fn;
-        const Arg1& _arg1;
-    };
-
-    template <typename Fn, typename Arg1, typename Arg2>
-    struct Closure2 {
-        Closure2(Fn& fn, const Arg1& arg1, const Arg2& arg2)
-            : _fn(fn), _arg1(arg1), _arg2(arg2) {}
-        size_t operator()(T& bg) { return _fn(bg, _arg1, _arg2); }
-    private:
-        Fn& _fn;
-        const Arg1& _arg1;
-        const Arg2& _arg2;
-    };
-
     const T* UnsafeRead() const {
         return _data + _index.load(butil::memory_order_acquire);
     }
@@ -253,7 +196,8 @@ template <typename T, typename TLS, bool 
AllowBthreadSuspended>
 class DoublyBufferedData<T, TLS, AllowBthreadSuspended>::WrapperTLSGroup {
 public:
     const static size_t RAW_BLOCK_SIZE = 4096;
-    const static size_t ELEMENTS_PER_BLOCK = RAW_BLOCK_SIZE / sizeof(Wrapper) 
> 0 ? RAW_BLOCK_SIZE / sizeof(Wrapper) : 1;
+    const static size_t ELEMENTS_PER_BLOCK =
+        RAW_BLOCK_SIZE / sizeof(Wrapper) > 0 ? RAW_BLOCK_SIZE / 
sizeof(Wrapper) : 1;
 
     struct BAIDU_CACHELINE_ALIGNMENT ThreadBlock {
         inline DoublyBufferedData::Wrapper* at(size_t offset) {
@@ -572,7 +516,20 @@ int DoublyBufferedData<T, TLS, 
AllowBthreadSuspended>::Read(
 
 template <typename T, typename TLS, bool AllowBthreadSuspended>
 template <typename Fn>
-size_t DoublyBufferedData<T, TLS, AllowBthreadSuspended>::Modify(Fn& fn) {
+int DoublyBufferedData<T, TLS, AllowBthreadSuspended>::Read(Fn&& fn) {
+    BAIDU_CASSERT((is_result_void<Fn, const T&>::value),
+                  "Fn must accept `const T&' and return void");
+    ScopedPtr ptr;
+    if (Read(&ptr) != 0) {
+        return -1;
+    }
+    fn(*ptr);
+    return 0;
+}
+
+template <typename T, typename TLS, bool AllowBthreadSuspended>
+template <typename Fn, typename... Args>
+size_t DoublyBufferedData<T, TLS, AllowBthreadSuspended>::Modify(Fn&& fn, 
Args&&... args) {
     // _modify_mutex sequences modifications. Using a separate mutex rather
     // than _wrappers_mutex is to avoid blocking threads calling
     // AddWrapper() or RemoveWrapper() too long. Most of the time, 
modifications
@@ -581,7 +538,7 @@ size_t DoublyBufferedData<T, TLS, 
AllowBthreadSuspended>::Modify(Fn& fn) {
     int bg_index = !_index.load(butil::memory_order_relaxed);
     // background instance is not accessed by other threads, being safe to
     // modify.
-    const size_t ret = fn(_data[bg_index]);
+    const size_t ret = fn(_data[bg_index], std::forward<Args>(args)...);
     if (!ret) {
         return 0;
     }
@@ -607,46 +564,17 @@ size_t DoublyBufferedData<T, TLS, 
AllowBthreadSuspended>::Modify(Fn& fn) {
         }
     }
 
-    const size_t ret2 = fn(_data[bg_index]);
+    const size_t ret2 = fn(_data[bg_index], std::forward<Args>(args)...);
     CHECK_EQ(ret2, ret) << "index=" << 
_index.load(butil::memory_order_relaxed);
     return ret2;
 }
 
 template <typename T, typename TLS, bool AllowBthreadSuspended>
-template <typename Fn, typename Arg1>
-size_t DoublyBufferedData<T, TLS, AllowBthreadSuspended>::Modify(Fn& fn, const 
Arg1& arg1) {
-    Closure1<Fn, Arg1> c(fn, arg1);
-    return Modify(c);
-}
-
-template <typename T, typename TLS, bool AllowBthreadSuspended>
-template <typename Fn, typename Arg1, typename Arg2>
-size_t DoublyBufferedData<T, TLS, AllowBthreadSuspended>::Modify(
-    Fn& fn, const Arg1& arg1, const Arg2& arg2) {
-    Closure2<Fn, Arg1, Arg2> c(fn, arg1, arg2);
-    return Modify(c);
-}
-
-template <typename T, typename TLS, bool AllowBthreadSuspended>
-template <typename Fn>
-size_t DoublyBufferedData<T, TLS, 
AllowBthreadSuspended>::ModifyWithForeground(Fn& fn) {
-    WithFG0<Fn> c(fn, _data);
-    return Modify(c);
-}
-
-template <typename T, typename TLS, bool AllowBthreadSuspended>
-template <typename Fn, typename Arg1>
-size_t DoublyBufferedData<T, TLS, 
AllowBthreadSuspended>::ModifyWithForeground(Fn& fn, const Arg1& arg1) {
-    WithFG1<Fn, Arg1> c(fn, _data, arg1);
-    return Modify(c);
-}
-
-template <typename T, typename TLS, bool AllowBthreadSuspended>
-template <typename Fn, typename Arg1, typename Arg2>
-size_t DoublyBufferedData<T, TLS, AllowBthreadSuspended>::ModifyWithForeground(
-    Fn& fn, const Arg1& arg1, const Arg2& arg2) {
-    WithFG2<Fn, Arg1, Arg2> c(fn, _data, arg1, arg2);
-    return Modify(c);
+template <typename Fn, typename... Args>
+size_t DoublyBufferedData<T, TLS, 
AllowBthreadSuspended>::ModifyWithForeground(Fn&& fn, Args&&... args) {
+    return Modify([this, &fn](T& bg, Args&&... args) {
+        return fn(bg, (const T&)_data[&bg == _data], 
std::forward<Args>(args)...);
+    }, std::forward<Args>(args)...);
 }
 
 }  // namespace butil
diff --git a/src/butil/type_traits.h b/src/butil/type_traits.h
index 0ae91135..735e1e55 100644
--- a/src/butil/type_traits.h
+++ b/src/butil/type_traits.h
@@ -99,7 +99,7 @@ template <class T> struct is_pod
                            std::is_trivial<T>::value)> {};
 #else
 template <class T> struct is_pod : std::is_pod<T> {};
-#endif
+#endif // __cplusplus
 
 #else
 // We can't get is_pod right without compiler help, so fail conservatively.
@@ -334,7 +334,7 @@ template<typename T>
 struct remove_cvref {
     typedef typename remove_cv<typename remove_reference<T>::type>::type type;
 };
-#endif
+#endif // __cplusplus
 
 // is_reference is false except for reference types.
 template<typename T> struct is_reference : false_type {};
@@ -378,7 +378,7 @@ template <typename F>
 using result_of = std::result_of<F>;
 #else
 #error Only C++11 or later is supported.
-#endif
+#endif // __cplusplus
 
 template <typename F>
 using result_of_t = typename result_of<F>::type;
diff --git a/test/brpc_load_balancer_unittest.cpp 
b/test/brpc_load_balancer_unittest.cpp
index 509456e7..2abb869e 100644
--- a/test/brpc_load_balancer_unittest.cpp
+++ b/test/brpc_load_balancer_unittest.cpp
@@ -80,6 +80,16 @@ bool AddN(Foo& f, int n) {
     return true;
 }
 
+void read_cb(const Foo& f) {
+    ASSERT_EQ(0, f.x);
+}
+
+struct CallableObj {
+    void operator()(const Foo& f) {
+        ASSERT_EQ(0, f.x);
+    }
+};
+
 template <typename DBD>
 void test_doubly_buffered_data() {
     // test doubly_buffered_data TLS limits
@@ -97,6 +107,15 @@ void test_doubly_buffered_data() {
         ASSERT_EQ(0, d.Read(&ptr));
         ASSERT_EQ(0, ptr->x);
     }
+    {
+        ASSERT_EQ(0, d.Read([](const Foo& f) {
+            ASSERT_EQ(0, f.x);
+        }));
+        ASSERT_EQ(0, d.Read(read_cb));
+        ASSERT_EQ(0, d.Read(CallableObj()));
+        CallableObj co;
+        ASSERT_EQ(0, d.Read(co));
+    }
     {
         typename DBD::ScopedPtr ptr;
         ASSERT_EQ(0, d.Read(&ptr));
@@ -104,10 +123,14 @@ void test_doubly_buffered_data() {
     }
 
     d.Modify(AddN, 10);
+    d.Modify([](Foo& f, int n) -> size_t {
+        f.x += n;
+        return 1;
+    }, 10);
     {
         typename DBD::ScopedPtr ptr;
         ASSERT_EQ(0, d.Read(&ptr));
-        ASSERT_EQ(10, ptr->x);
+        ASSERT_EQ(20, ptr->x);
     }
 }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@brpc.apache.org
For additional commands, e-mail: dev-h...@brpc.apache.org

Reply via email to