Skip to content

Commit 1372b5e

Browse files
authored
Fix records_yaml startup lookup race (#13538)
The records_yaml AuTest intermittently reported that proxy.config.diags.output.note was missing when a configuration callback ran while startup was still registering metrics. Metrics::find() returned the then-current end iterator for a miss, but RecLookupRecord() compared it with a new end iterator. An intervening registration made them differ and misread the missing string record as a numeric metric. This patch addresses the race by using the direct metrics lookup API, which reports a miss without comparing two moving end positions. It also adds a concurrent registration test that exercises string record lookups during metric creation.
1 parent 2119ac9 commit 1372b5e

2 files changed

Lines changed: 43 additions & 8 deletions

File tree

src/records/RecCore.cc

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -516,18 +516,20 @@ RecGetRecordCounter(const char *name, bool lock)
516516
RecErrT
517517
RecLookupRecord(const char *name, void (*callback)(const RecRecord *, void *), void *data, bool lock)
518518
{
519-
RecErrT err = REC_ERR_FAIL;
520-
ts::Metrics &metrics = ts::Metrics::instance();
521-
auto it = metrics.find(name);
519+
RecErrT err = REC_ERR_FAIL;
520+
ts::Metrics &metrics = ts::Metrics::instance();
521+
ts::Metrics::IdType metric_id;
522522

523-
if (it != metrics.end()) {
523+
// A metric's storage is stable after creation. Avoid find()/end() here because end() is the current insertion position and
524+
// can advance between those two calls while another thread registers a metric.
525+
if (auto *metric = metrics.lookup(name, &metric_id); metric != nullptr) {
524526
RecRecord r{};
525-
auto &&[name, type, val] = *it;
526527

527528
r.rec_type = RECT_PLUGIN;
528-
r.data_type = type == ts::Metrics::MetricType::COUNTER ? RECD_COUNTER : RECD_INT;
529-
r.name = name.data();
530-
r.data.rec_int = val;
529+
r.data_type = metrics.type(metric_id) == ts::Metrics::MetricType::COUNTER ? RECD_COUNTER : RECD_INT;
530+
r.name = name;
531+
r.data.rec_int = metric->load();
532+
r.registered = true;
531533

532534
callback(&r, data);
533535
err = REC_ERR_OKAY;

src/records/unit_tests/test_RecRegister.cc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@
2222
#include "iocore/eventsystem/EventSystem.h"
2323
#include "iocore/eventsystem/RecProcess.h"
2424
#include "tscore/Layout.h"
25+
#include "tsutil/Metrics.h"
2526
#include "test_Diags.h"
2627

28+
#include <atomic>
29+
#include <thread>
30+
2731
TEST_CASE("RecRegisterConfig - Type Dispatch", "[librecords][RecConfig]")
2832
{
2933
SECTION("RecRegisterConfigInt")
@@ -87,3 +91,32 @@ TEST_CASE("RecRegisterStat - Type Dispatch", "[librecords][RecStat]")
8791
REQUIRE(value == 500);
8892
}
8993
}
94+
95+
TEST_CASE("RecLookupRecord - Concurrent metric registration", "[librecords][RecLookup]")
96+
{
97+
constexpr char record_name[] = "proxy.test.concurrent.string_value";
98+
constexpr char record_value[] = "stable";
99+
100+
REQUIRE(RecRegisterConfigString(RECT_CONFIG, record_name, record_value, RECU_DYNAMIC, RECC_NULL, nullptr, REC_SOURCE_NULL) ==
101+
REC_ERR_OKAY);
102+
103+
std::atomic<bool> finished{false};
104+
std::thread register_metrics([&]() {
105+
for (int i = 0; i < 100000; ++i) {
106+
ts::Metrics::Counter::createSpan(1);
107+
}
108+
finished.store(true, std::memory_order_release);
109+
});
110+
111+
bool all_lookups_succeeded = true;
112+
113+
do {
114+
if (RecGetRecordStringAlloc(record_name) != record_value) {
115+
all_lookups_succeeded = false;
116+
break;
117+
}
118+
} while (!finished.load(std::memory_order_acquire));
119+
register_metrics.join();
120+
121+
CHECK(all_lookups_succeeded);
122+
}

0 commit comments

Comments
 (0)