chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,920 @@
|
||||
// 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 <bthread/execution_queue.h>
|
||||
#include <bthread/sys_futex.h>
|
||||
#include <bthread/countdown_event.h>
|
||||
#include "butil/time.h"
|
||||
#include "butil/fast_rand.h"
|
||||
#include "gperftools_helper.h"
|
||||
|
||||
namespace {
|
||||
bool stopped = false;
|
||||
|
||||
class ExecutionQueueTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() { stopped = false; }
|
||||
void TearDown() {}
|
||||
};
|
||||
|
||||
struct LongIntTask {
|
||||
long value;
|
||||
bthread::CountdownEvent* event;
|
||||
LongIntTask(long v)
|
||||
: value(v), event(NULL)
|
||||
{}
|
||||
LongIntTask(long v, bthread::CountdownEvent* e)
|
||||
: value(v), event(e)
|
||||
{}
|
||||
LongIntTask() : value(0), event(NULL) {}
|
||||
};
|
||||
|
||||
int add(void* meta, bthread::TaskIterator<LongIntTask> &iter) {
|
||||
stopped = iter.is_queue_stopped();
|
||||
int64_t* result = (int64_t*)meta;
|
||||
for (; iter; ++iter) {
|
||||
*result += iter->value;
|
||||
if (iter->event) { iter->event->signal(); }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_single_thread(bool use_pthread) {
|
||||
int64_t result = 0;
|
||||
int64_t expected_result = 0;
|
||||
stopped = false;
|
||||
bthread::ExecutionQueueId<LongIntTask> queue_id;
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
add, &result));
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
expected_result += i;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, i));
|
||||
}
|
||||
LOG(INFO) << "stop";
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_NE(0, bthread::execution_queue_execute(queue_id, 0));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(expected_result, result);
|
||||
ASSERT_TRUE(stopped);
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, single_thread) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_single_thread(i);
|
||||
}
|
||||
}
|
||||
|
||||
class RValue {
|
||||
public:
|
||||
RValue() : _value(0) {}
|
||||
explicit RValue(int v) : _value(v) {}
|
||||
RValue(RValue&& rhs) noexcept : _value(rhs._value) {}
|
||||
RValue& operator=(RValue&& rhs) noexcept {
|
||||
if (this != &rhs) {
|
||||
_value = rhs._value;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RValue);
|
||||
|
||||
int value() const { return _value; }
|
||||
|
||||
|
||||
private:
|
||||
int _value;
|
||||
};
|
||||
|
||||
int add(void* meta, bthread::TaskIterator<RValue> &iter) {
|
||||
stopped = iter.is_queue_stopped();
|
||||
int* result = (int*)meta;
|
||||
for (; iter; ++iter) {
|
||||
*result += iter->value();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_rvalue(bool use_pthread) {
|
||||
int64_t result = 0;
|
||||
int64_t expected_result = 0;
|
||||
stopped = false;
|
||||
bthread::ExecutionQueueId<RValue> queue_id;
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
add, &result));
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
expected_result += i;
|
||||
RValue v(i);
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, std::move(v)));
|
||||
}
|
||||
LOG(INFO) << "stop";
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_NE(0, bthread::execution_queue_execute(queue_id, RValue(0)));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(expected_result, result);
|
||||
ASSERT_TRUE(stopped);
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, rvalue) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_rvalue(i);
|
||||
}
|
||||
}
|
||||
|
||||
struct PushArg {
|
||||
bthread::ExecutionQueueId<LongIntTask> id {0};
|
||||
butil::atomic<int64_t> total_num {0};
|
||||
butil::atomic<int64_t> total_time {0};
|
||||
butil::atomic<int64_t> expected_value {0};
|
||||
volatile bool stopped {false};
|
||||
bool wait_task_completed {false};
|
||||
};
|
||||
|
||||
void* push_thread(void *arg) {
|
||||
PushArg* pa = (PushArg*)arg;
|
||||
int64_t sum = 0;
|
||||
butil::Timer timer;
|
||||
timer.start();
|
||||
int num = 0;
|
||||
bthread::CountdownEvent e;
|
||||
LongIntTask t(num, pa->wait_task_completed ? &e : NULL);
|
||||
if (pa->wait_task_completed) {
|
||||
e.reset(1);
|
||||
}
|
||||
while (bthread::execution_queue_execute(pa->id, t) == 0) {
|
||||
sum += num;
|
||||
t.value = ++num;
|
||||
if (pa->wait_task_completed) {
|
||||
e.wait();
|
||||
e.reset(1);
|
||||
}
|
||||
}
|
||||
timer.stop();
|
||||
pa->expected_value.fetch_add(sum, butil::memory_order_relaxed);
|
||||
pa->total_num.fetch_add(num);
|
||||
pa->total_time.fetch_add(timer.n_elapsed());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* push_thread_which_addresses_execq(void *arg) {
|
||||
PushArg* pa = (PushArg*)arg;
|
||||
int64_t sum = 0;
|
||||
butil::Timer timer;
|
||||
timer.start();
|
||||
int num = 0;
|
||||
bthread::ExecutionQueue<LongIntTask>::scoped_ptr_t ptr
|
||||
= bthread::execution_queue_address(pa->id);
|
||||
EXPECT_TRUE(ptr);
|
||||
while (ptr->execute(num) == 0) {
|
||||
sum += num;
|
||||
++num;
|
||||
}
|
||||
EXPECT_TRUE(ptr->stopped());
|
||||
timer.stop();
|
||||
pa->expected_value.fetch_add(sum, butil::memory_order_relaxed);
|
||||
pa->total_num.fetch_add(num);
|
||||
pa->total_time.fetch_add(timer.n_elapsed());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void test_performance(bool use_pthread) {
|
||||
pthread_t threads[8];
|
||||
bthread::ExecutionQueueId<LongIntTask> queue_id = { 0 }; // to suppress warnings
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
int64_t result = 0;
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
add, &result));
|
||||
PushArg pa;
|
||||
pa.id = queue_id;
|
||||
pa.total_num = 0;
|
||||
pa.total_time = 0;
|
||||
pa.expected_value = 0;
|
||||
pa.stopped = false;
|
||||
ProfilerStart("execq.prof");
|
||||
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
|
||||
pthread_create(&threads[i], NULL, &push_thread_which_addresses_execq, &pa);
|
||||
}
|
||||
usleep(500 * 1000);
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
ProfilerStop();
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(pa.expected_value.load(), result);
|
||||
LOG(INFO) << "With addressed execq, each execution_queue_execute takes "
|
||||
<< pa.total_time.load() / pa.total_num.load()
|
||||
<< " total_num=" << pa.total_num
|
||||
<< " ns with " << ARRAY_SIZE(threads) << " threads";
|
||||
#define BENCHMARK_BOTH
|
||||
#ifdef BENCHMARK_BOTH
|
||||
result = 0;
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
add, &result));
|
||||
pa.id = queue_id;
|
||||
pa.total_num = 0;
|
||||
pa.total_time = 0;
|
||||
pa.expected_value = 0;
|
||||
pa.stopped = false;
|
||||
ProfilerStart("execq_id.prof");
|
||||
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
|
||||
pthread_create(&threads[i], NULL, &push_thread, &pa);
|
||||
}
|
||||
usleep(500 * 1000);
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
ProfilerStop();
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(pa.expected_value.load(), result);
|
||||
LOG(INFO) << "With id explicitly, execution_queue_execute takes "
|
||||
<< pa.total_time.load() / pa.total_num.load()
|
||||
<< " total_num=" << pa.total_num
|
||||
<< " ns with " << ARRAY_SIZE(threads) << " threads";
|
||||
#endif // BENCHMARK_BOTH
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, performance) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_performance(i);
|
||||
}
|
||||
}
|
||||
|
||||
volatile bool g_suspending = false;
|
||||
volatile bool g_should_be_urgent = false;
|
||||
int urgent_times = 0;
|
||||
|
||||
int add_with_suspend(void* meta, bthread::TaskIterator<LongIntTask>& iter) {
|
||||
int64_t* result = (int64_t*)meta;
|
||||
if (iter.is_queue_stopped()) {
|
||||
stopped = true;
|
||||
return 0;
|
||||
}
|
||||
if (g_should_be_urgent) {
|
||||
g_should_be_urgent = false;
|
||||
EXPECT_EQ(-1, iter->value) << urgent_times;
|
||||
if (iter->event) { iter->event->signal(); }
|
||||
++iter;
|
||||
EXPECT_FALSE(iter) << urgent_times;
|
||||
++urgent_times;
|
||||
} else {
|
||||
for (; iter; ++iter) {
|
||||
if (iter->value == -100) {
|
||||
g_suspending = true;
|
||||
while (g_suspending) {
|
||||
bthread_usleep(100);
|
||||
}
|
||||
g_should_be_urgent = true;
|
||||
if (iter->event) { iter->event->signal(); }
|
||||
EXPECT_FALSE(++iter);
|
||||
return 0;
|
||||
} else {
|
||||
*result += iter->value;
|
||||
if (iter->event) { iter->event->signal(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_execute_urgent(bool use_pthread) {
|
||||
g_should_be_urgent = false;
|
||||
pthread_t threads[10];
|
||||
bthread::ExecutionQueueId<LongIntTask> queue_id = { 0 }; // to suppress warnings
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
int64_t result = 0;
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
add_with_suspend, &result));
|
||||
PushArg pa;
|
||||
pa.id = queue_id;
|
||||
pa.total_num = 0;
|
||||
pa.total_time = 0;
|
||||
pa.expected_value = 0;
|
||||
pa.stopped = false;
|
||||
pa.wait_task_completed = true;
|
||||
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
|
||||
pthread_create(&threads[i], NULL, &push_thread, &pa);
|
||||
}
|
||||
g_suspending = false;
|
||||
usleep(1000);
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, -100));
|
||||
while (!g_suspending) {
|
||||
usleep(100);
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(
|
||||
queue_id, -1, &bthread::TASK_OPTIONS_URGENT));
|
||||
g_suspending = false;
|
||||
usleep(100);
|
||||
}
|
||||
usleep(500* 1000);
|
||||
pa.stopped = true;
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
LOG(INFO) << "result=" << result;
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(pa.expected_value.load(), result);
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, execute_urgent) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_execute_urgent(i);
|
||||
}
|
||||
}
|
||||
|
||||
void test_urgent_task_is_the_last_task(bool use_pthread) {
|
||||
g_should_be_urgent = false;
|
||||
g_suspending = false;
|
||||
bthread::ExecutionQueueId<LongIntTask> queue_id = { 0 }; // to suppress warnings
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
int64_t result = 0;
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
add_with_suspend, &result));
|
||||
g_suspending = false;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, -100));
|
||||
while (!g_suspending) {
|
||||
usleep(10);
|
||||
}
|
||||
LOG(INFO) << "Going to push";
|
||||
int64_t expected = 0;
|
||||
for (int j = 1; j < 100; ++j) {
|
||||
expected += j;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, j));
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(
|
||||
queue_id, -1, &bthread::TASK_OPTIONS_URGENT));
|
||||
usleep(100);
|
||||
g_suspending = false;
|
||||
butil::atomic_thread_fence(butil::memory_order_acq_rel);
|
||||
usleep(10 * 1000);
|
||||
LOG(INFO) << "going to quit";
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(expected, result);
|
||||
}
|
||||
TEST_F(ExecutionQueueTest, urgent_task_is_the_last_task) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_urgent_task_is_the_last_task(i);
|
||||
}
|
||||
}
|
||||
|
||||
long next_task[1024];
|
||||
butil::atomic<int> num_threads(0);
|
||||
|
||||
void* push_thread_with_id(void* arg) {
|
||||
bthread::ExecutionQueueId<LongIntTask> id = { (uint64_t)arg };
|
||||
int thread_id = num_threads.fetch_add(1, butil::memory_order_relaxed);
|
||||
LOG(INFO) << "Start thread" << thread_id;
|
||||
for (int i = 0; i < 100000; ++i) {
|
||||
bthread::execution_queue_execute(id, ((long)thread_id << 32) | i);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int check_order(void* meta, bthread::TaskIterator<LongIntTask>& iter) {
|
||||
for (; iter; ++iter) {
|
||||
long value = iter->value;
|
||||
int thread_id = value >> 32;
|
||||
long task = value & 0xFFFFFFFFul;
|
||||
if (task != next_task[thread_id]++) {
|
||||
EXPECT_TRUE(false) << "task=" << task << " thread_id=" << thread_id;
|
||||
++*(long*)meta;
|
||||
}
|
||||
if (iter->event) { iter->event->signal(); }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_multi_threaded_order(bool use_pthread) {
|
||||
memset(next_task, 0, sizeof(next_task));
|
||||
long disorder_times = 0;
|
||||
bthread::ExecutionQueueId<LongIntTask> queue_id = { 0 }; // to suppress warnings
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
check_order, &disorder_times));
|
||||
pthread_t threads[12];
|
||||
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
|
||||
pthread_create(&threads[i], NULL, &push_thread_with_id, (void *)queue_id.value);
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(0, disorder_times);
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, multi_threaded_order) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_multi_threaded_order(i);
|
||||
}
|
||||
}
|
||||
|
||||
int check_running_thread(void* arg, bthread::TaskIterator<LongIntTask>& iter) {
|
||||
if (iter.is_queue_stopped()) {
|
||||
return 0;
|
||||
}
|
||||
for (; iter; ++iter) {}
|
||||
EXPECT_EQ(pthread_self(), (pthread_t)arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_in_place_task(bool use_pthread) {
|
||||
pthread_t thread_id = pthread_self();
|
||||
bthread::ExecutionQueueId<LongIntTask> queue_id = { 0 }; // to suppress warnings
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
check_running_thread,
|
||||
(void*)thread_id));
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(
|
||||
queue_id, 0, &bthread::TASK_OPTIONS_INPLACE));
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, in_place_task) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_in_place_task(i);
|
||||
}
|
||||
}
|
||||
|
||||
struct InPlaceTask {
|
||||
bool first_task;
|
||||
pthread_t thread_id;
|
||||
};
|
||||
|
||||
void *run_first_tasks(void* arg) {
|
||||
bthread::ExecutionQueueId<InPlaceTask> queue_id = { (uint64_t)arg };
|
||||
InPlaceTask task;
|
||||
task.first_task = true;
|
||||
task.thread_id = pthread_self();
|
||||
EXPECT_EQ(0, bthread::execution_queue_execute(
|
||||
queue_id, task, &bthread::TASK_OPTIONS_INPLACE));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int stuck_and_check_running_thread(void* arg, bthread::TaskIterator<InPlaceTask>& iter) {
|
||||
if (iter.is_queue_stopped()) {
|
||||
return 0;
|
||||
}
|
||||
butil::atomic<int>* futex = (butil::atomic<int>*)arg;
|
||||
if (iter->first_task) {
|
||||
EXPECT_EQ(pthread_self(), iter->thread_id);
|
||||
futex->store(1);
|
||||
bthread::futex_wake_private(futex, 1);
|
||||
while (futex->load() != 2) {
|
||||
bthread::futex_wait_private(futex, 1, NULL);
|
||||
}
|
||||
++iter;
|
||||
EXPECT_FALSE(iter);
|
||||
} else {
|
||||
for (; iter; ++iter) {
|
||||
EXPECT_FALSE(iter->first_task);
|
||||
EXPECT_NE(pthread_self(), iter->thread_id);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_should_start_new_thread_on_more_tasks(bool use_pthread) {
|
||||
bthread::ExecutionQueueId<InPlaceTask> queue_id = { 0 };
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
butil::atomic<int> futex(0);
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
stuck_and_check_running_thread,
|
||||
(void*)&futex));
|
||||
pthread_t thread;
|
||||
ASSERT_EQ(0, pthread_create(&thread, NULL, run_first_tasks, (void*)queue_id.value));
|
||||
while (futex.load() != 1) {
|
||||
bthread::futex_wait_private(&futex, 0, NULL);
|
||||
}
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
InPlaceTask task;
|
||||
task.first_task = false;
|
||||
task.thread_id = pthread_self();
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(
|
||||
queue_id, task, &bthread::TASK_OPTIONS_INPLACE));
|
||||
}
|
||||
futex.store(2);
|
||||
bthread::futex_wake_private(&futex, 1);
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, should_start_new_thread_on_more_tasks) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_should_start_new_thread_on_more_tasks(i);
|
||||
}
|
||||
}
|
||||
|
||||
void* inplace_push_thread(void* arg) {
|
||||
bthread::ExecutionQueueId<LongIntTask> id = { (uint64_t)arg };
|
||||
int thread_id = num_threads.fetch_add(1, butil::memory_order_relaxed);
|
||||
LOG(INFO) << "Start thread" << thread_id;
|
||||
for (int i = 0; i < 100000; ++i) {
|
||||
bthread::execution_queue_execute(id, ((long)thread_id << 32) | i,
|
||||
&bthread::TASK_OPTIONS_INPLACE);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void test_inplace_and_order(bool use_pthread) {
|
||||
memset(next_task, 0, sizeof(next_task));
|
||||
long disorder_times = 0;
|
||||
bthread::ExecutionQueueId<LongIntTask> queue_id = { 0 }; // to suppress warnings
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
check_order, &disorder_times));
|
||||
pthread_t threads[12];
|
||||
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
|
||||
pthread_create(&threads[i], NULL, &inplace_push_thread, (void *)queue_id.value);
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(0, disorder_times);
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, inplace_and_order) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_inplace_and_order(i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, size_of_task_node) {
|
||||
LOG(INFO) << "sizeof(TaskNode)=" << sizeof(bthread::TaskNode);
|
||||
}
|
||||
|
||||
int add_with_suspend2(void* meta, bthread::TaskIterator<LongIntTask>& iter) {
|
||||
int64_t* result = (int64_t*)meta;
|
||||
if (iter.is_queue_stopped()) {
|
||||
stopped = true;
|
||||
return 0;
|
||||
}
|
||||
for (; iter; ++iter) {
|
||||
if (iter->value == -100) {
|
||||
g_suspending = true;
|
||||
while (g_suspending) {
|
||||
usleep(10);
|
||||
}
|
||||
if (iter->event) { iter->event->signal(); }
|
||||
} else {
|
||||
*result += iter->value;
|
||||
if (iter->event) { iter->event->signal(); }
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_cancel(bool use_pthread) {
|
||||
bthread::ExecutionQueueId<LongIntTask> queue_id = { 0 }; // to suppress warnings
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
int64_t result = 0;
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
add_with_suspend2, &result));
|
||||
g_suspending = false;
|
||||
bthread::TaskHandle handle0;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, -100, NULL, &handle0));
|
||||
while (!g_suspending) {
|
||||
usleep(10);
|
||||
}
|
||||
ASSERT_EQ(1, bthread::execution_queue_cancel(handle0));
|
||||
ASSERT_EQ(1, bthread::execution_queue_cancel(handle0));
|
||||
bthread::TaskHandle handle1;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, 100, NULL, &handle1));
|
||||
ASSERT_EQ(0, bthread::execution_queue_cancel(handle1));
|
||||
g_suspending = false;
|
||||
ASSERT_EQ(-1, bthread::execution_queue_cancel(handle1));
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(0, result);
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, cancel) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_cancel(i);
|
||||
}
|
||||
}
|
||||
|
||||
struct CancelSelf {
|
||||
butil::atomic<bthread::TaskHandle*> handle;
|
||||
};
|
||||
|
||||
int cancel_self(void* /*meta*/, bthread::TaskIterator<CancelSelf*>& iter) {
|
||||
|
||||
for (; iter; ++iter) {
|
||||
while ((*iter)->handle == NULL) {
|
||||
usleep(10);
|
||||
}
|
||||
EXPECT_EQ(1, bthread::execution_queue_cancel(*(*iter)->handle.load()));
|
||||
EXPECT_EQ(1, bthread::execution_queue_cancel(*(*iter)->handle.load()));
|
||||
EXPECT_EQ(1, bthread::execution_queue_cancel(*(*iter)->handle.load()));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_cancel_self(bool use_pthread) {
|
||||
bthread::ExecutionQueueId<CancelSelf*> queue_id = { 0 }; // to suppress warnings
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
cancel_self, NULL));
|
||||
CancelSelf task;
|
||||
task.handle = NULL;
|
||||
bthread::TaskHandle handle;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, &task, NULL, &handle));
|
||||
task.handle.store(&handle);
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, cancel_self) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_cancel_self(i);
|
||||
}
|
||||
}
|
||||
|
||||
struct AddTask {
|
||||
int value;
|
||||
bool cancel_task;
|
||||
int cancel_value;
|
||||
bthread::TaskHandle handle;
|
||||
};
|
||||
|
||||
struct AddMeta {
|
||||
int64_t sum;
|
||||
butil::atomic<int64_t> expected;
|
||||
butil::atomic<int64_t> succ_times;
|
||||
butil::atomic<int64_t> race_times;
|
||||
butil::atomic<int64_t> fail_times;
|
||||
};
|
||||
|
||||
int add_with_cancel(void* meta, bthread::TaskIterator<AddTask>& iter) {
|
||||
if (iter.is_queue_stopped()) {
|
||||
return 0;
|
||||
}
|
||||
AddMeta* m = (AddMeta*)meta;
|
||||
for (; iter; ++iter) {
|
||||
if (iter->cancel_task) {
|
||||
const int rc = bthread::execution_queue_cancel(iter->handle);
|
||||
if (rc == 0) {
|
||||
m->expected.fetch_sub(iter->cancel_value);
|
||||
m->succ_times.fetch_add(1);
|
||||
} else if (rc < 0) {
|
||||
m->fail_times.fetch_add(1);
|
||||
} else {
|
||||
m->race_times.fetch_add(1);
|
||||
}
|
||||
} else {
|
||||
m->sum += iter->value;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_random_cancel(bool use_pthread) {
|
||||
bthread::ExecutionQueueId<AddTask> queue_id = { 0 };
|
||||
AddMeta m;
|
||||
m.sum = 0;
|
||||
m.expected.store(0);
|
||||
m.succ_times.store(0);
|
||||
m.fail_times.store(0);
|
||||
m.race_times.store(0);
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, NULL,
|
||||
add_with_cancel, &m));
|
||||
int64_t expected = 0;
|
||||
for (int i = 0; i < 100000; ++i) {
|
||||
bthread::TaskHandle h;
|
||||
AddTask t;
|
||||
t.value = i;
|
||||
t.cancel_task = false;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, t, NULL, &h));
|
||||
const int r = butil::fast_rand_less_than(4);
|
||||
expected += i;
|
||||
if (r == 0) {
|
||||
if (bthread::execution_queue_cancel(h) == 0) {
|
||||
expected -= i;
|
||||
}
|
||||
} else if (r == 1) {
|
||||
t.cancel_task = true;
|
||||
t.cancel_value = i;
|
||||
t.handle = h;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, t, NULL));
|
||||
} else if (r == 2) {
|
||||
t.cancel_task = true;
|
||||
t.cancel_value = i;
|
||||
t.handle = h;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(
|
||||
queue_id, t, &bthread::TASK_OPTIONS_URGENT));
|
||||
} else {
|
||||
// do nothing;
|
||||
}
|
||||
}
|
||||
m.expected.fetch_add(expected);
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(m.sum, m.expected.load());
|
||||
LOG(INFO) << "sum=" << m.sum << " race_times=" << m.race_times
|
||||
<< " succ_times=" << m.succ_times
|
||||
<< " fail_times=" << m.fail_times;
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, random_cancel) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_random_cancel(i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int add2(void* meta, bthread::TaskIterator<LongIntTask> &iter) {
|
||||
if (iter) {
|
||||
int64_t* result = (int64_t*)meta;
|
||||
*result += iter->value;
|
||||
if (iter->event) { iter->event->signal(); }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_not_do_iterate_at_all(bool use_pthread) {
|
||||
int64_t result = 0;
|
||||
int64_t expected_result = 0;
|
||||
bthread::ExecutionQueueId<LongIntTask> queue_id;
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
add2, &result));
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
expected_result += i;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, i));
|
||||
}
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_NE(0, bthread::execution_queue_execute(queue_id, 0));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
ASSERT_EQ(expected_result, result);
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, not_do_iterate_at_all) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_not_do_iterate_at_all(i);
|
||||
}
|
||||
}
|
||||
|
||||
int add_with_suspend3(void* meta, bthread::TaskIterator<LongIntTask>& iter) {
|
||||
int64_t* result = (int64_t*)meta;
|
||||
if (iter.is_queue_stopped()) {
|
||||
stopped = true;
|
||||
return 0;
|
||||
}
|
||||
for (; iter; ++iter) {
|
||||
if (iter->value == -100) {
|
||||
g_suspending = true;
|
||||
while (g_suspending) {
|
||||
usleep(10);
|
||||
}
|
||||
if (iter->event) { iter->event->signal(); }
|
||||
} else {
|
||||
*result += iter->value;
|
||||
if (iter->event) { iter->event->signal(); }
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_cancel_unexecuted_high_priority_task(bool use_pthread) {
|
||||
g_should_be_urgent = false;
|
||||
bthread::ExecutionQueueId<LongIntTask> queue_id = { 0 }; // to suppress warnings
|
||||
bthread::ExecutionQueueOptions options;
|
||||
options.use_pthread = use_pthread;
|
||||
if (options.use_pthread) {
|
||||
LOG(INFO) << "================ pthread ================";
|
||||
} else {
|
||||
LOG(INFO) << "================ bthread ================";
|
||||
}
|
||||
int64_t result = 0;
|
||||
ASSERT_EQ(0, bthread::execution_queue_start(&queue_id, &options,
|
||||
add_with_suspend3, &result));
|
||||
// Push a normal task to make the executor suspend
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, -100));
|
||||
while (!g_suspending) {
|
||||
usleep(10);
|
||||
}
|
||||
// At this point, executor is suspended by the first task. Then we put
|
||||
// a high_priority task which is going to be cancelled immediately,
|
||||
// expecting that both operations are successful.
|
||||
bthread::TaskHandle h;
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(
|
||||
queue_id, -100, &bthread::TASK_OPTIONS_URGENT, &h));
|
||||
ASSERT_EQ(0, bthread::execution_queue_cancel(h));
|
||||
|
||||
// Resume executor
|
||||
g_suspending = false;
|
||||
|
||||
// Push a normal task
|
||||
ASSERT_EQ(0, bthread::execution_queue_execute(queue_id, 12345));
|
||||
|
||||
// The execq should stop normally
|
||||
ASSERT_EQ(0, bthread::execution_queue_stop(queue_id));
|
||||
ASSERT_EQ(0, bthread::execution_queue_join(queue_id));
|
||||
|
||||
ASSERT_EQ(12345, result);
|
||||
}
|
||||
|
||||
TEST_F(ExecutionQueueTest, cancel_unexecuted_high_priority_task) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
test_cancel_unexecuted_high_priority_task(i);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user