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

chenBright 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 7c524731 Cache AgentId in native bvar recorders and reducers (#3709)
7c524731 is described below

commit 7c5247319076c1395e39545f38d8a2e305c64fd3
Author: Bright Chen <[email protected]>
AuthorDate: Wed Nov 4 21:46:29 2026 +0800

    Cache AgentId in native bvar recorders and reducers (#3709)
---
 src/bvar/detail/combiner.h             |   9 ++
 src/bvar/recorder.h                    |  16 ++--
 src/bvar/reducer.h                     |  12 ++-
 test/bvar_cached_agent_id_unittest.cpp | 159 +++++++++++++++++++++++++++++++++
 4 files changed, 188 insertions(+), 8 deletions(-)

diff --git a/src/bvar/detail/combiner.h b/src/bvar/detail/combiner.h
index 7f531d94..8fdf8e82 100644
--- a/src/bvar/detail/combiner.h
+++ b/src/bvar/detail/combiner.h
@@ -340,6 +340,15 @@ friend class GlobalValue<self_type>;
         call_op_returning_void(_op, _global_result, prev);
     }
 
+    AgentId agent_id() const { return _id; }
+
+    // The caller must keep the combiner owning id alive. An allocated TLS
+    // slot may still be unbound, or belong to an expired combiner after reuse.
+    inline static Agent* get_bound_tls_agent(AgentId id) {
+        Agent* agent = AgentGroup::get_tls_agent(id);
+        return agent && !agent->combiner.expired() ? agent : nullptr;
+    }
+
     // We need this function to be as fast as possible.
     Agent* get_or_create_tls_agent() {
         Agent* agent = AgentGroup::get_tls_agent(_id);
diff --git a/src/bvar/recorder.h b/src/bvar/recorder.h
index 147fcb72..6220d553 100644
--- a/src/bvar/recorder.h
+++ b/src/bvar/recorder.h
@@ -124,7 +124,7 @@ public:
     typedef typename combiner_type::self_shared_type shared_combiner_type;
     typedef combiner_type::Agent agent_type;
 
-    IntRecorder() : _combiner(std::make_shared<combiner_type>()), 
_sampler(nullptr) {}
+    IntRecorder() = default;
 
     IntRecorder(const butil::StringPiece& name) : IntRecorder() {
         expose(name);
@@ -263,8 +263,10 @@ private:
     }
 
 private:
-    shared_combiner_type    _combiner;
-    sampler_type* _sampler;
+    shared_combiner_type _combiner{std::make_shared<combiner_type>()};
+    // `_combiner` keeps this ID allocated for the lifetime of the recorder.
+    const detail::AgentId _agent_id{_combiner->agent_id()};
+    sampler_type* _sampler{nullptr};
     std::string _debug_name;
 };
 
@@ -291,8 +293,12 @@ inline IntRecorder& IntRecorder::operator<<(int64_t 
sample) {
                        << (void*)this << ") " << reason;
         }
     }
-    agent_type* agent = _combiner->get_or_create_tls_agent();
-    if (BAIDU_UNLIKELY(!agent)) {
+
+    agent_type* agent = combiner_type::get_bound_tls_agent(_agent_id);
+    if (BAIDU_UNLIKELY(agent == nullptr)) {
+        agent = _combiner->get_or_create_tls_agent();
+    }
+    if (BAIDU_UNLIKELY(agent == nullptr)) {
         LOG(FATAL) << "Fail to create agent";
         return *this;
     }
diff --git a/src/bvar/reducer.h b/src/bvar/reducer.h
index c49d1e4c..a7d51f49 100644
--- a/src/bvar/reducer.h
+++ b/src/bvar/reducer.h
@@ -250,7 +250,8 @@ public:
     explicit Reducer(typename butil::add_cr_non_integral<T>::type identity = 
T(),
                      const Op& op = Op(), const InvOp& inv_op = InvOp())
         : _combiner(std::make_shared<combiner_type>(identity, identity, op))
-        , _sampler(nullptr) , _series_sampler(nullptr) , _inv_op(inv_op) {}
+        , _agent_id(_combiner->agent_id()) , _sampler(nullptr)
+        , _series_sampler(nullptr) , _inv_op(inv_op) {}
 
     ~Reducer() override {
         // Calling hide() manually is a MUST required by Variable.
@@ -351,6 +352,8 @@ protected:
 
 private:
     shared_combiner_type _combiner;
+    // `_combiner` keeps this ID allocated for the lifetime of the reducer.
+    const detail::AgentId _agent_id;
     sampler_type* _sampler;
     SeriesSampler* _series_sampler;
     InvOp _inv_op;
@@ -360,8 +363,11 @@ template <typename T, typename Op, typename InvOp>
 inline Reducer<T, Op, InvOp>& Reducer<T, Op, InvOp>::operator<<(
     typename butil::add_cr_non_integral<T>::type value) {
     // It's wait-free for most time
-    agent_type* agent = _combiner->get_or_create_tls_agent();
-    if (__builtin_expect(!agent, 0)) {
+    agent_type* agent = combiner_type::get_bound_tls_agent(_agent_id);
+    if (BAIDU_UNLIKELY(agent == nullptr)) {
+        agent = _combiner->get_or_create_tls_agent();
+    }
+    if (BAIDU_UNLIKELY(agent == nullptr)) {
         LOG(FATAL) << "Fail to create agent";
         return *this;
     }
diff --git a/test/bvar_cached_agent_id_unittest.cpp 
b/test/bvar_cached_agent_id_unittest.cpp
new file mode 100644
index 00000000..9e08222c
--- /dev/null
+++ b/test/bvar_cached_agent_id_unittest.cpp
@@ -0,0 +1,159 @@
+// 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 <algorithm>
+#include <limits>
+#include <memory>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+#include "bvar/recorder.h"
+#include "bvar/reducer.h"
+
+namespace {
+
+struct SelectExtremum {
+    explicit SelectExtremum(bool select_min) : minimum(select_min) {}
+    void operator()(int& lhs, int rhs) const {
+        lhs = minimum ? std::min(lhs, rhs) : std::max(lhs, rhs);
+    }
+    bool minimum;
+};
+
+TEST(CachedAgentIdTest, unbound_slot_and_stateful_operator) {
+    typedef bvar::Reducer<int, SelectExtremum> Reducer;
+    typedef Reducer::combiner_type Combiner;
+    Reducer first(std::numeric_limits<int>::max(), SelectExtremum(true));
+    Reducer second(std::numeric_limits<int>::lowest(), SelectExtremum(false));
+    const bvar::detail::AgentId id = first.share_combiner()->agent_id();
+    ASSERT_EQ(nullptr, Combiner::get_bound_tls_agent(-1));
+    ASSERT_NE(nullptr, Combiner::AgentGroup::get_or_create_tls_agent(id));
+    ASSERT_EQ(nullptr, Combiner::get_bound_tls_agent(id));
+    first << 12 << 42;
+    second << 12 << 42;
+    ASSERT_NE(nullptr, Combiner::get_bound_tls_agent(id));
+    ASSERT_EQ(12, first.get_value());
+    ASSERT_EQ(42, second.get_value());
+    ASSERT_EQ(12, first.reset());
+    first << 3;
+    ASSERT_EQ(3, first.get_value());
+}
+
+TEST(CachedAgentIdTest, reducer_reuses_expired_slot) {
+    typedef bvar::Reducer<int, SelectExtremum> Reducer;
+    typedef Reducer::combiner_type Combiner;
+    bvar::detail::AgentId id;
+    {
+        Reducer old(std::numeric_limits<int>::lowest(), SelectExtremum(false));
+        id = old.share_combiner()->agent_id();
+        old << 80;
+        ASSERT_EQ(80, old.get_value());
+    }
+    Reducer fresh(std::numeric_limits<int>::max(), SelectExtremum(true));
+    ASSERT_EQ(id, fresh.share_combiner()->agent_id());
+    ASSERT_NE(nullptr, Combiner::AgentGroup::get_tls_agent(id));
+    ASSERT_EQ(nullptr, Combiner::get_bound_tls_agent(id));
+    fresh << 100;
+    ASSERT_EQ(100, fresh.get_value());
+    fresh << -20;
+    ASSERT_EQ(-20, fresh.get_value());
+}
+
+TEST(CachedAgentIdTest, retained_reducer_combiner_keeps_id_alive) {
+    typedef bvar::Reducer<int, SelectExtremum> Reducer;
+    typedef Reducer::combiner_type Combiner;
+    Combiner::self_shared_type held;
+    bvar::detail::AgentId id;
+    {
+        Reducer old(std::numeric_limits<int>::lowest(), SelectExtremum(false));
+        held = old.share_combiner();
+        id = held->agent_id();
+        old << 80;
+    }
+    Reducer other(std::numeric_limits<int>::max(), SelectExtremum(true));
+    ASSERT_NE(id, other.share_combiner()->agent_id());
+    other << 5;
+    ASSERT_EQ(80, held->combine_agents());
+    ASSERT_EQ(5, other.get_value());
+    held.reset();
+    Reducer fresh(std::numeric_limits<int>::lowest(), SelectExtremum(false));
+    ASSERT_EQ(id, fresh.share_combiner()->agent_id());
+    fresh << 6;
+    ASSERT_EQ(6, fresh.get_value());
+}
+
+#if !WITH_BABYLON_COUNTER
+TEST(CachedAgentIdTest, recorder_reuses_slot_after_last_owner_releases) {
+    typedef bvar::IntRecorder::combiner_type Combiner;
+    Combiner::self_shared_type held;
+    bvar::detail::AgentId id;
+    {
+        bvar::IntRecorder old;
+        held = old.share_combiner();
+        id = held->agent_id();
+        old << 80;
+    }
+    bvar::IntRecorder other;
+    ASSERT_NE(id, other.share_combiner()->agent_id());
+    other << 5;
+    ASSERT_EQ(80, held->combine_agents().sum);
+    ASSERT_EQ(1, held->combine_agents().num);
+    held.reset();
+    bvar::IntRecorder fresh;
+    ASSERT_EQ(id, fresh.share_combiner()->agent_id());
+    ASSERT_NE(nullptr, Combiner::AgentGroup::get_tls_agent(id));
+    ASSERT_EQ(nullptr, Combiner::get_bound_tls_agent(id));
+    fresh << 6 << 7;
+    ASSERT_EQ(13, fresh.get_value().sum);
+    ASSERT_EQ(2, fresh.get_value().num);
+    ASSERT_EQ(13, fresh.reset().sum);
+    fresh << -4;
+    ASSERT_EQ(-4, fresh.get_value().sum);
+    ASSERT_EQ(1, fresh.get_value().num);
+}
+
+TEST(CachedAgentIdTest, fresh_threads_initialize_and_commit_on_exit) {
+    typedef bvar::IntRecorder::combiner_type RecorderCombiner;
+    typedef bvar::Adder<int64_t>::combiner_type AdderCombiner;
+    bvar::IntRecorder recorder;
+    bvar::Adder<int64_t> adder;
+    const auto recorder_id = recorder.share_combiner()->agent_id();
+    const auto adder_id = adder.share_combiner()->agent_id();
+    std::thread workers[8];
+    for (auto& worker : workers) {
+        worker = std::thread([&] {
+            EXPECT_EQ(nullptr,
+                      RecorderCombiner::get_bound_tls_agent(recorder_id));
+            EXPECT_EQ(nullptr,
+                      AdderCombiner::get_bound_tls_agent(adder_id));
+            for (int i = 0; i < 10000; ++i) {
+                recorder << 3;
+                adder << 2;
+            }
+        });
+    }
+    for (auto& worker : workers) {
+        worker.join();
+    }
+    ASSERT_EQ(80000, recorder.get_value().num);
+    ASSERT_EQ(240000, recorder.get_value().sum);
+    ASSERT_EQ(160000, adder.get_value());
+}
+#endif  // !WITH_BABYLON_COUNTER
+
+}  // namespace


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to