| // 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/bthread.h> |
| |
| namespace { |
| |
| const size_t SEM_COUNT = 10000; |
| |
| void* sem_waiter(void* arg) { |
| bthread_usleep(10 * 1000); |
| auto sem = (bthread_sem_t*)arg; |
| for (size_t i = 0; i < SEM_COUNT; ++i) { |
| bthread_sem_wait(sem); |
| } |
| return NULL; |
| } |
| |
| void* sem_poster(void* arg) { |
| bthread_usleep(10 * 1000); |
| auto sem = (bthread_sem_t*)arg; |
| for (size_t i = 0; i < SEM_COUNT; ++i) { |
| bthread_sem_post(sem); |
| } |
| return NULL; |
| } |
| |
| TEST(SemaphoreTest, sanity) { |
| bthread_sem_t sem; |
| ASSERT_EQ(0, bthread_sem_init(&sem, 1)); |
| ASSERT_EQ(0, bthread_sem_wait(&sem)); |
| ASSERT_EQ(0, bthread_sem_post(&sem)); |
| ASSERT_EQ(0, bthread_sem_wait(&sem)); |
| |
| bthread_t waiter_th; |
| bthread_t poster_th; |
| ASSERT_EQ(0, bthread_start_urgent(&waiter_th, NULL, sem_waiter, &sem)); |
| ASSERT_EQ(0, bthread_start_urgent(&poster_th, NULL, sem_poster, &sem)); |
| ASSERT_EQ(0, bthread_join(waiter_th, NULL)); |
| ASSERT_EQ(0, bthread_join(poster_th, NULL)); |
| |
| ASSERT_EQ(0, bthread_sem_destroy(&sem)); |
| } |
| |
| |
| |
| TEST(SemaphoreTest, used_in_pthread) { |
| bthread_sem_t sem; |
| ASSERT_EQ(0, bthread_sem_init(&sem, 0)); |
| |
| pthread_t waiter_th[8]; |
| pthread_t poster_th[8]; |
| for (auto& th : waiter_th) { |
| ASSERT_EQ(0, pthread_create(&th, NULL, sem_waiter, &sem)); |
| } |
| for (auto& th : poster_th) { |
| ASSERT_EQ(0, pthread_create(&th, NULL, sem_poster, &sem)); |
| } |
| for (auto& th : waiter_th) { |
| pthread_join(th, NULL); |
| } |
| for (auto& th : poster_th) { |
| pthread_join(th, NULL); |
| } |
| |
| ASSERT_EQ(0, bthread_sem_destroy(&sem)); |
| } |
| |
| void* do_timedwait(void *arg) { |
| struct timespec t = { -2, 0 }; |
| EXPECT_EQ(ETIMEDOUT, bthread_sem_timedwait((bthread_sem_t*)arg, &t)); |
| return NULL; |
| } |
| |
| TEST(SemaphoreTest, timedwait) { |
| bthread_sem_t sem; |
| ASSERT_EQ(0, bthread_sem_init(&sem, 0)); |
| bthread_t th; |
| ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_timedwait, &sem)); |
| ASSERT_EQ(0, bthread_join(th, NULL)); |
| ASSERT_EQ(0, bthread_sem_destroy(&sem)); |
| } |
| |
| |
| struct TryWaitArgs { |
| bthread_sem_t* sem; |
| int rc; |
| }; |
| |
| void* do_trywait(void *arg) { |
| auto trylock_args = (TryWaitArgs*)arg; |
| EXPECT_EQ(trylock_args->rc, bthread_sem_trywait(trylock_args->sem)); |
| return NULL; |
| } |
| |
| TEST(SemaphoreTest, trywait) { |
| bthread_sem_t sem; |
| ASSERT_EQ(0, bthread_sem_init(&sem, 0)); |
| |
| ASSERT_EQ(EAGAIN, bthread_sem_trywait(&sem)); |
| ASSERT_EQ(0, bthread_sem_post(&sem)); |
| ASSERT_EQ(0, bthread_sem_trywait(&sem)); |
| ASSERT_EQ(EAGAIN, bthread_sem_trywait(&sem)); |
| |
| ASSERT_EQ(0, bthread_sem_post(&sem)); |
| bthread_t th; |
| TryWaitArgs args{ &sem, 0}; |
| ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_trywait, &args)); |
| ASSERT_EQ(0, bthread_join(th, NULL)); |
| args.rc = EAGAIN; |
| ASSERT_EQ(0, bthread_start_urgent(&th, NULL, do_trywait, &args)); |
| ASSERT_EQ(0, bthread_join(th, NULL)); |
| |
| ASSERT_EQ(0, bthread_sem_destroy(&sem)); |
| } |
| |
| bool g_stopped = false; |
| |
| void wait_op(bthread_sem_t* sem, int64_t sleep_us) { |
| ASSERT_EQ(0, bthread_sem_wait(sem)); |
| if (0 != sleep_us) { |
| bthread_usleep(sleep_us); |
| } |
| } |
| |
| void post_op(bthread_sem_t* rw, int64_t sleep_us) { |
| ASSERT_EQ(0, bthread_sem_post(rw)); |
| if (0 != sleep_us) { |
| bthread_usleep(sleep_us); |
| } |
| } |
| |
| typedef void (*OP)(bthread_sem_t* sem, int64_t sleep_us); |
| |
| struct MixThreadArg { |
| bthread_sem_t* sem; |
| OP op; |
| }; |
| |
| void* loop_until_stopped(void* arg) { |
| auto args = (MixThreadArg*)arg; |
| for (size_t i = 0; i < SEM_COUNT; ++i) { |
| args->op(args->sem, 20); |
| } |
| return NULL; |
| } |
| |
| TEST(SemaphoreTest, mix_thread_types) { |
| g_stopped = false; |
| bthread_sem_t sem; |
| ASSERT_EQ(0, bthread_sem_init(&sem, 0)); |
| |
| const int N = 16; |
| const int M = N * 2; |
| pthread_t pthreads[N]; |
| bthread_t bthreads[M]; |
| // reserve enough workers for test. This is a must since we have |
| // BTHREAD_ATTR_PTHREAD bthreads which may cause deadlocks (the |
| // bhtread_usleep below can't be scheduled and g_stopped is never |
| // true, thus loop_until_stopped spins forever) |
| bthread_setconcurrency(M); |
| std::vector<MixThreadArg> args; |
| args.reserve(N + M); |
| for (int i = 0; i < N; ++i) { |
| if (i % 2 == 0) { |
| args.push_back({ &sem, wait_op }); |
| } else { |
| args.push_back({ &sem, post_op }); |
| } |
| ASSERT_EQ(0, pthread_create(&pthreads[i], NULL, loop_until_stopped, &args.back())); |
| } |
| |
| for (int i = 0; i < M; ++i) { |
| if (i % 2 == 0) { |
| args.push_back({ &sem, wait_op }); |
| } else { |
| args.push_back({ &sem, post_op }); |
| } |
| const bthread_attr_t* attr = i % 2 ? NULL : &BTHREAD_ATTR_PTHREAD; |
| ASSERT_EQ(0, bthread_start_urgent(&bthreads[i], attr, loop_until_stopped, &args.back())); |
| } |
| for (bthread_t bthread : bthreads) { |
| bthread_join(bthread, NULL); |
| } |
| for (pthread_t pthread : pthreads) { |
| pthread_join(pthread, NULL); |
| } |
| |
| ASSERT_EQ(0, bthread_sem_destroy(&sem)); |
| } |
| |
| } // namespace |