| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/sync/condition.h> |
| #include <lib/sync/mutex.h> |
| #include <lib/zircon-internal/thread_annotations.h> |
| #include <pthread.h> |
| #include <zircon/assert.h> |
| #include <zircon/types.h> |
| |
| #include <array> |
| #include <vector> |
| |
| #include "event.h" |
| #include "thread.h" |
| #include "tracer.h" |
| #include "utils.h" |
| |
| constexpr uint32_t THREAD_COUNT = 5; |
| using ThreadCollection = std::array<std::unique_ptr<Thread>, THREAD_COUNT>; |
| enum class PrioInherit : bool { No = false, Yes }; |
| |
| /////////////////////////////////////////////////////// |
| // |
| // libsync Synchronization primitives |
| // |
| /////////////////////////////////////////////////////// |
| class TA_CAP("mutex") LibSyncMutex { |
| public: |
| static const char* Name() { return "sync_mutex_t"; } |
| |
| LibSyncMutex() = default; |
| ~LibSyncMutex() = default; |
| void Acquire() TA_ACQ() { sync_mutex_lock(&mutex_); } |
| void Release() TA_REL() { sync_mutex_unlock(&mutex_); } |
| |
| private: |
| sync_mutex_t mutex_; |
| }; |
| |
| class TA_CAP("mutex") LibSyncCondVar { |
| public: |
| static const char* Name() { return "sync_condition_t"; } |
| |
| LibSyncCondVar() = default; |
| ~LibSyncCondVar() = default; |
| void AcquireLock() TA_ACQ() { sync_mutex_lock(&mutex_); } |
| void ReleaseLock() TA_REL() { sync_mutex_unlock(&mutex_); } |
| |
| void Broadcast() { sync_condition_broadcast(&condition_); } |
| void Signal() { sync_condition_signal(&condition_); } |
| void Wait() { sync_condition_wait(&condition_, &mutex_); } |
| |
| private: |
| sync_condition_t condition_; |
| sync_mutex_t mutex_; |
| }; |
| |
| /////////////////////////////////////////////////////// |
| // |
| // pthread Synchronization primitives |
| // |
| /////////////////////////////////////////////////////// |
| template <PrioInherit EnablePi> |
| class TA_CAP("mutex") PThreadMutex { |
| public: |
| static const char* Name() { |
| if constexpr (EnablePi == PrioInherit::Yes) { |
| return "pthread_mutex_t with PI"; |
| } else { |
| return "pthread_mutex_t without PI"; |
| } |
| } |
| |
| PThreadMutex() { |
| pthread_mutexattr_t attr; |
| pthread_mutexattr_init(&attr); |
| |
| if constexpr (EnablePi == PrioInherit::Yes) { |
| pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); |
| } |
| |
| pthread_mutex_init(&mutex_, &attr); |
| pthread_mutexattr_destroy(&attr); |
| } |
| |
| ~PThreadMutex() { pthread_mutex_destroy(&mutex_); } |
| void Acquire() TA_ACQ() { pthread_mutex_lock(&mutex_); } |
| void Release() TA_REL() { pthread_mutex_unlock(&mutex_); } |
| |
| private: |
| pthread_mutex_t mutex_; |
| }; |
| |
| template <PrioInherit EnablePi> |
| class TA_CAP("mutex") PThreadCondVar { |
| public: |
| static const char* Name() { |
| if constexpr (EnablePi == PrioInherit::Yes) { |
| return "pthread_cond_t with PI"; |
| } else { |
| return "pthread_cond_t without PI"; |
| } |
| } |
| |
| PThreadCondVar() { |
| pthread_mutexattr_t attr; |
| pthread_mutexattr_init(&attr); |
| |
| if constexpr (EnablePi == PrioInherit::Yes) { |
| pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); |
| } |
| |
| pthread_cond_init(&condition_, nullptr); |
| pthread_mutex_init(&mutex_, &attr); |
| pthread_mutexattr_destroy(&attr); |
| } |
| |
| ~PThreadCondVar() { |
| pthread_cond_destroy(&condition_); |
| pthread_mutex_destroy(&mutex_); |
| } |
| |
| void AcquireLock() TA_ACQ() { pthread_mutex_lock(&mutex_); } |
| void ReleaseLock() TA_REL() { pthread_mutex_unlock(&mutex_); } |
| |
| void Broadcast() { pthread_cond_broadcast(&condition_); } |
| void Signal() { pthread_cond_signal(&condition_); } |
| void Wait() { pthread_cond_wait(&condition_, &mutex_); } |
| |
| private: |
| pthread_cond_t condition_; |
| pthread_mutex_t mutex_; |
| }; |
| |
| /////////////////////////////////////////////////////// |
| // |
| // C11 Synchronization primitives |
| // |
| /////////////////////////////////////////////////////// |
| class TA_CAP("mutex") MtxTMutex { |
| public: |
| static const char* Name() { return "mtx_t"; } |
| |
| MtxTMutex() { mtx_init(&mutex_, mtx_plain); } |
| ~MtxTMutex() { mtx_destroy(&mutex_); } |
| void Acquire() TA_ACQ() { mtx_lock(&mutex_); } |
| void Release() TA_REL() { mtx_unlock(&mutex_); } |
| |
| private: |
| mtx_t mutex_; |
| }; |
| |
| class TA_CAP("mutex") CndTCondVar { |
| public: |
| static const char* Name() { return "cnd_t"; } |
| |
| CndTCondVar() { |
| cnd_init(&condition_); |
| mtx_init(&mutex_, mtx_plain); |
| } |
| |
| ~CndTCondVar() { |
| cnd_destroy(&condition_); |
| mtx_destroy(&mutex_); |
| } |
| |
| void AcquireLock() TA_ACQ() { mtx_lock(&mutex_); } |
| void ReleaseLock() TA_REL() { mtx_unlock(&mutex_); } |
| |
| void Broadcast() { cnd_broadcast(&condition_); } |
| void Signal() { cnd_signal(&condition_); } |
| void Wait() { cnd_wait(&condition_, &mutex_); } |
| |
| private: |
| cnd_t condition_; |
| mtx_t mutex_; |
| }; |
| |
| /////////////////////////////////////////////////////// |
| // |
| // libfbl Synchronization primitives |
| // |
| /////////////////////////////////////////////////////// |
| class TA_CAP("mutex") FblMutex { |
| public: |
| static const char* Name() { return "fbl::Mutex"; } |
| |
| FblMutex() = default; |
| ~FblMutex() = default; |
| void Acquire() TA_ACQ() { mutex_.Acquire(); } |
| void Release() TA_REL() { mutex_.Release(); } |
| |
| private: |
| fbl::Mutex mutex_; |
| }; |
| |
| template <typename MutexType> |
| zx_status_t ExerciseMutexChain(ThreadCollection* _threads) { |
| ZX_DEBUG_ASSERT(_threads != nullptr); |
| ZX_DEBUG_ASSERT(_threads->size() >= 2); |
| auto& threads = *_threads; |
| |
| zx_status_t res = ZX_ERR_INTERNAL; |
| struct chain_node_t { |
| Event exit_evt; |
| Event ready_evt; |
| MutexType hold_mutex; |
| MutexType* blocking_mutex = nullptr; |
| }; |
| |
| auto nodes = std::unique_ptr<chain_node_t[]>(new chain_node_t[threads.size()]); |
| |
| Tracer::Trace(TRACE_SCOPE_PROCESS, "Setting up mutex chain; type = \"%s\"", MutexType::Name()); |
| for (uint32_t i = 0; i < threads.size(); ++i) { |
| auto& node = nodes[i]; |
| auto& thread = *(threads[i]); |
| |
| if (i > 0) { |
| node.blocking_mutex = &nodes[i - 1].hold_mutex; |
| } |
| |
| res = thread.Start([&node]() { |
| node.hold_mutex.Acquire(); |
| node.ready_evt.Signal(); |
| |
| if (node.blocking_mutex != nullptr) { |
| node.blocking_mutex->Acquire(); |
| node.exit_evt.Wait(); |
| node.blocking_mutex->Release(); |
| } else { |
| node.exit_evt.Wait(); |
| } |
| |
| node.hold_mutex.Release(); |
| }); |
| |
| if (res != ZX_OK) { |
| fprintf(stderr, "Failed to start \"%s\" (res = %d)\n", thread.name(), res); |
| return res; |
| } |
| |
| res = node.ready_evt.Wait(zx::msec(500)); |
| if (res != ZX_OK) { |
| fprintf(stderr, "Time out waiting for \"%s\" to become ready (res = %d)\n", thread.name(), |
| res); |
| return res; |
| } |
| } |
| |
| for (uint32_t i = 0; i < threads.size(); ++i) { |
| nodes[i].exit_evt.Signal(); |
| threads[i]->WaitForReset(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| template <typename MutexType> |
| zx_status_t ExerciseMutexMultiWait(ThreadCollection* _threads) { |
| ZX_DEBUG_ASSERT(_threads != nullptr); |
| ZX_DEBUG_ASSERT(_threads->size() >= 2); |
| auto& threads = *_threads; |
| |
| zx_status_t res = ZX_ERR_INTERNAL; |
| MutexType the_mutex; |
| Event exit_evt; |
| Event ready_evt; |
| |
| Tracer::Trace(TRACE_SCOPE_PROCESS, "Setting up multi-wait; type = \"%s\"", MutexType::Name()); |
| for (uint32_t i = 0; i < threads.size(); ++i) { |
| auto& thread = *(threads[i]); |
| if (i == 0) { |
| res = thread.Start([&the_mutex, &exit_evt, &ready_evt]() { |
| the_mutex.Acquire(); |
| ready_evt.Signal(); |
| exit_evt.Wait(); |
| the_mutex.Release(); |
| }); |
| |
| res = ready_evt.Wait(zx::msec(500)); |
| if (res != ZX_OK) { |
| fprintf(stderr, "Time out waiting for \"%s\" to become ready (res = %d)\n", thread.name(), |
| res); |
| return res; |
| } |
| } else { |
| res = thread.Start([&the_mutex, &exit_evt]() { |
| the_mutex.Acquire(); |
| exit_evt.Wait(); |
| the_mutex.Release(); |
| }); |
| } |
| |
| if (res != ZX_OK) { |
| fprintf(stderr, "Failed to start \"%s\" (res = %d)\n", thread.name(), res); |
| return res; |
| } |
| } |
| |
| exit_evt.Signal(); |
| for (auto& thread : threads) { |
| thread->WaitForReset(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| template <typename CondVarType> |
| zx_status_t ExerciseCondvarBroadcast(ThreadCollection* _threads) { |
| ZX_DEBUG_ASSERT(_threads != nullptr); |
| ZX_DEBUG_ASSERT(_threads->size() >= 2); |
| auto& threads = *_threads; |
| |
| struct { |
| CondVarType the_condvar; |
| uint32_t exit_threshold TA_GUARDED(the_condvar); |
| } ctx; |
| |
| ctx.the_condvar.AcquireLock(); |
| ctx.exit_threshold = 1000; |
| ctx.the_condvar.ReleaseLock(); |
| |
| Tracer::Trace(TRACE_SCOPE_PROCESS, "Setting up condvar broadcast; type = \"%s\"", |
| CondVarType::Name()); |
| |
| for (uint32_t i = 0; i < threads.size(); ++i) { |
| auto& thread = *(threads[i]); |
| uint32_t next_prio = i ? threads[i - 1]->prio() : 0; |
| zx_status_t res; |
| |
| res = thread.Start([&ctx, &thread, next_prio]() { |
| ctx.the_condvar.AcquireLock(); |
| while (thread.prio() < ctx.exit_threshold) { |
| ctx.the_condvar.Wait(); |
| // Linger in the lock for a bit to encourage contention |
| zx::nanosleep(zx::deadline_after(zx::usec(250))); |
| } |
| ctx.exit_threshold = next_prio; |
| ctx.the_condvar.Broadcast(); |
| ctx.the_condvar.ReleaseLock(); |
| }); |
| |
| if (res != ZX_OK) { |
| fprintf(stderr, "Failed to start \"%s\" (res = %d)\n", thread.name(), res); |
| return res; |
| } |
| } |
| |
| // Now that all of the threads are set up and waiting, set the exit |
| // threshold and signal the condvar. |
| ctx.the_condvar.AcquireLock(); |
| ctx.exit_threshold = threads[threads.size() - 1]->prio(); |
| ctx.the_condvar.Broadcast(); |
| ctx.the_condvar.ReleaseLock(); |
| |
| for (auto& thread : threads) { |
| thread->WaitForReset(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| int main(int argc, char** argv) { |
| zx_status_t res; |
| ThreadCollection threads; |
| |
| // Create the thread objects for the threads we will use during testing. We |
| // don't actually want to create new threads for each pass of the testing as |
| // that makes the traces difficult to read. Having one set we use over and |
| // over should be sufficient. |
| constexpr uint32_t BASE_PRIO = 3; |
| constexpr uint32_t PRIO_SPACING = 2; |
| |
| for (uint32_t i = 0; i < threads.size(); ++i) { |
| threads[i] = std::make_unique<Thread>(BASE_PRIO + (i * PRIO_SPACING)); |
| } |
| |
| Tracer the_tracer; |
| res = the_tracer.Start(); |
| if (res != ZX_OK) { |
| return -1; |
| } |
| |
| res = Thread::ConnectSchedulerService(); |
| if (res != ZX_OK) { |
| fprintf(stderr, "Failed to start connect to scheduler service (res = %d)\n", res); |
| return -1; |
| } |
| |
| using TrialFn = zx_status_t (*)(ThreadCollection*); |
| constexpr TrialFn TRIALS[] = { |
| ExerciseMutexChain<LibSyncMutex>, |
| ExerciseMutexMultiWait<LibSyncMutex>, |
| ExerciseMutexChain<PThreadMutex<PrioInherit::No>>, |
| ExerciseMutexMultiWait<PThreadMutex<PrioInherit::No>>, |
| ExerciseMutexChain<PThreadMutex<PrioInherit::Yes>>, |
| ExerciseMutexMultiWait<PThreadMutex<PrioInherit::Yes>>, |
| ExerciseMutexChain<MtxTMutex>, |
| ExerciseMutexMultiWait<MtxTMutex>, |
| ExerciseMutexChain<FblMutex>, |
| ExerciseMutexMultiWait<FblMutex>, |
| ExerciseCondvarBroadcast<LibSyncCondVar>, |
| ExerciseCondvarBroadcast<PThreadCondVar<PrioInherit::No>>, |
| ExerciseCondvarBroadcast<PThreadCondVar<PrioInherit::Yes>>, |
| ExerciseCondvarBroadcast<CndTCondVar>, |
| }; |
| |
| for (auto& DoTrial : TRIALS) { |
| if (DoTrial(&threads) != ZX_OK) { |
| return -1; |
| } |
| } |
| |
| Tracer::Trace(TRACE_SCOPE_PROCESS, "Finished!"); |
| return 0; |
| } |