Files
apache--brpc/test/bthread_priority_queue_unittest.cpp
T
2026-07-13 13:29:29 +08:00

229 lines
7.2 KiB
C++

// 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 <gtest/gtest.h>
#include <gflags/gflags.h>
#include <atomic>
#include <vector>
#include <set>
#include <mutex>
#include <sched.h>
#include "bthread/bthread.h"
#include "bthread/task_group.h"
namespace {
// Counter incremented by priority bthreads to verify execution
std::atomic<int> g_priority_count(0);
// Mutex + set for collecting executed tids to verify no loss
std::mutex g_tid_mutex;
std::set<int> g_executed_ids;
void reset_globals() {
g_priority_count.store(0);
std::lock_guard<std::mutex> lk(g_tid_mutex);
g_executed_ids.clear();
}
struct TaskArg {
int id;
};
void* priority_task_fn(void* arg) {
TaskArg* ta = static_cast<TaskArg*>(arg);
g_priority_count.fetch_add(1, std::memory_order_relaxed);
{
std::lock_guard<std::mutex> lk(g_tid_mutex);
g_executed_ids.insert(ta->id);
}
delete ta;
return NULL;
}
void* normal_task_fn(void* /*arg*/) {
// Just a normal task that does nothing, used as a filler
bthread_usleep(1000);
return NULL;
}
class PriorityQueueTest : public ::testing::Test {
protected:
static void SetUpTestSuite() {
google::SetCommandLineOption("enable_bthread_priority_queue", "true");
google::SetCommandLineOption("event_dispatcher_num", "4");
}
void SetUp() override {
reset_globals();
}
};
// Test 1: End-to-end priority task submission and execution.
// Multiple producers submit priority tasks, verify all tasks are executed.
TEST_F(PriorityQueueTest, e2e_priority_tasks_all_executed) {
const int N = 200;
bthread_attr_t attr = BTHREAD_ATTR_NORMAL;
attr.flags |= BTHREAD_GLOBAL_PRIORITY;
std::vector<bthread_t> tids(N);
for (int i = 0; i < N; ++i) {
TaskArg* arg = new TaskArg{i};
ASSERT_EQ(0, bthread_start_background(&tids[i], &attr,
priority_task_fn, arg));
}
for (int i = 0; i < N; ++i) {
bthread_join(tids[i], NULL);
}
ASSERT_EQ(N, g_priority_count.load());
std::lock_guard<std::mutex> lk(g_tid_mutex);
ASSERT_EQ((size_t)N, g_executed_ids.size());
for (int i = 0; i < N; ++i) {
ASSERT_TRUE(g_executed_ids.count(i)) << "Missing task id=" << i;
}
}
// Test 2: Mix of priority and normal tasks, all complete correctly.
TEST_F(PriorityQueueTest, mixed_priority_and_normal_tasks) {
const int N_PRIORITY = 100;
const int N_NORMAL = 100;
bthread_attr_t priority_attr = BTHREAD_ATTR_NORMAL;
priority_attr.flags |= BTHREAD_GLOBAL_PRIORITY;
std::vector<bthread_t> tids;
tids.reserve(N_PRIORITY + N_NORMAL);
for (int i = 0; i < N_PRIORITY + N_NORMAL; ++i) {
bthread_t tid;
if (i % 2 == 0 && (i / 2) < N_PRIORITY) {
TaskArg* arg = new TaskArg{i / 2};
ASSERT_EQ(0, bthread_start_background(&tid, &priority_attr,
priority_task_fn, arg));
} else {
ASSERT_EQ(0, bthread_start_background(&tid, NULL,
normal_task_fn, NULL));
}
tids.push_back(tid);
}
for (auto tid : tids) {
bthread_join(tid, NULL);
}
ASSERT_EQ(N_PRIORITY, g_priority_count.load());
}
// Test 3: start_foreground (bthread_start_urgent) with GLOBAL_PRIORITY.
// Simulates ED calling StartInputEvent: ED bthread calls start_urgent,
// gets preempted into PQ via priority_to_run, child runs and ends,
// ending_sched steals ED from PQ to resume.
TEST_F(PriorityQueueTest, start_foreground_priority_to_run) {
const int N = 200;
struct EDSimArg {
int n_tasks;
};
EDSimArg ed_arg{N};
auto ed_fn = [](void* arg) -> void* {
EDSimArg* ea = static_cast<EDSimArg*>(arg);
bthread::TaskMeta* meta =
bthread::TaskGroup::address_meta(bthread_self());
meta->priority_index = 0;
for (int i = 0; i < ea->n_tasks; ++i) {
TaskArg* ta = new TaskArg{i};
bthread_t child;
bthread_start_urgent(&child, NULL, priority_task_fn, ta);
}
return NULL;
};
bthread_attr_t priority_attr = BTHREAD_ATTR_NORMAL;
priority_attr.flags |= BTHREAD_GLOBAL_PRIORITY;
bthread_t ed_tid;
ASSERT_EQ(0, bthread_start_background(&ed_tid, &priority_attr,
ed_fn, &ed_arg));
bthread_join(ed_tid, NULL);
ASSERT_EQ(N, g_priority_count.load());
std::lock_guard<std::mutex> lk(g_tid_mutex);
ASSERT_EQ((size_t)N, g_executed_ids.size());
}
// Test 4: Multiple ED-like bthreads concurrently calling start_urgent.
// Verifies PQ correctness under concurrent preemption from multiple EDs.
TEST_F(PriorityQueueTest, multiple_eds_concurrent_preempt) {
const int NUM_EDS = 4;
const int TASKS_PER_ED = 50;
const int TOTAL = NUM_EDS * TASKS_PER_ED;
std::atomic<int> resume_count(0);
struct EDArg {
int ed_index;
int n_children;
std::atomic<int>* resume_count;
};
auto ed_fn = [](void* arg) -> void* {
EDArg* ea = static_cast<EDArg*>(arg);
bthread::TaskMeta* meta =
bthread::TaskGroup::address_meta(bthread_self());
meta->priority_index = ea->ed_index;
std::vector<bthread_t> children;
children.reserve(ea->n_children);
for (int i = 0; i < ea->n_children; ++i) {
int id = ea->ed_index * ea->n_children + i;
TaskArg* ta = new TaskArg{id};
bthread_t child;
bthread_start_urgent(&child, NULL, priority_task_fn, ta);
children.push_back(child);
ea->resume_count->fetch_add(1, std::memory_order_relaxed);
}
for (auto c : children) {
bthread_join(c, NULL);
}
return NULL;
};
bthread_attr_t priority_attr = BTHREAD_ATTR_NORMAL;
priority_attr.flags |= BTHREAD_GLOBAL_PRIORITY;
std::vector<EDArg> ed_args(NUM_EDS);
std::vector<bthread_t> ed_tids(NUM_EDS);
for (int i = 0; i < NUM_EDS; ++i) {
ed_args[i] = {i, TASKS_PER_ED, &resume_count};
ASSERT_EQ(0, bthread_start_background(&ed_tids[i], &priority_attr,
ed_fn, &ed_args[i]));
}
for (int i = 0; i < NUM_EDS; ++i) {
bthread_join(ed_tids[i], NULL);
}
ASSERT_EQ(TOTAL, g_priority_count.load());
ASSERT_EQ(TOTAL, resume_count.load());
std::lock_guard<std::mutex> lk(g_tid_mutex);
ASSERT_EQ((size_t)TOTAL, g_executed_ids.size());
}
} // namespace