1/* 2* Copyright (c) 2017, The Linux Foundation. All rights reserved. 3* 4* Redistribution and use in source and binary forms, with or without 5* modification, are permitted provided that the following conditions are 6* met: 7* * Redistributions of source code must retain the above copyright 8* notice, this list of conditions and the following disclaimer. 9* * Redistributions in binary form must reproduce the above 10* copyright notice, this list of conditions and the following 11* disclaimer in the documentation and/or other materials provided 12* with the distribution. 13* * Neither the name of The Linux Foundation nor the names of its 14* contributors may be used to endorse or promote products derived 15* from this software without specific prior written permission. 16* 17* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 18* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 20* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28*/ 29 30#ifndef __SYNC_TASK_H__ 31#define __SYNC_TASK_H__ 32 33#include <thread> 34#include <mutex> 35#include <condition_variable> // NOLINT 36 37namespace sdm { 38 39template <class TaskCode> 40class SyncTask { 41 public: 42 // This class need to be overridden by caller to pass on a task context. 43 class TaskContext { 44 public: 45 virtual ~TaskContext() { } 46 }; 47 48 // Methods to callback into caller for command codes executions in worker thread. 49 class TaskHandler { 50 public: 51 virtual ~TaskHandler() { } 52 virtual void OnTask(const TaskCode &task_code, TaskContext *task_context) = 0; 53 }; 54 55 explicit SyncTask(TaskHandler &task_handler) : task_handler_(task_handler) { 56 // Block caller thread until worker thread has started and ready to listen to task commands. 57 // Worker thread will signal as soon as callback is received in the new thread. 58 std::unique_lock<std::mutex> caller_lock(caller_mutex_); 59 std::thread worker_thread(SyncTaskThread, this); 60 worker_thread_.swap(worker_thread); 61 caller_cv_.wait(caller_lock); 62 } 63 64 ~SyncTask() { 65 // Task code does not matter here. 66 PerformTask(task_code_, nullptr, true); 67 worker_thread_.join(); 68 } 69 70 void PerformTask(const TaskCode &task_code, TaskContext *task_context) { 71 PerformTask(task_code, task_context, false); 72 } 73 74 private: 75 void PerformTask(const TaskCode &task_code, TaskContext *task_context, bool terminate) { 76 std::unique_lock<std::mutex> caller_lock(caller_mutex_); 77 78 // New scope to limit scope of worker lock to this block. 79 { 80 // Set task command code and notify worker thread. 81 std::unique_lock<std::mutex> worker_lock(worker_mutex_); 82 task_code_ = task_code; 83 task_context_ = task_context; 84 worker_thread_exit_ = terminate; 85 pending_code_ = true; 86 worker_cv_.notify_one(); 87 } 88 89 // Wait for worker thread to finish and signal. 90 caller_cv_.wait(caller_lock); 91 } 92 93 static void SyncTaskThread(SyncTask *sync_task) { 94 if (sync_task) { 95 sync_task->OnThreadCallback(); 96 } 97 } 98 99 void OnThreadCallback() { 100 // Acquire worker lock and start waiting for events. 101 // Wait must start before caller thread can post events, otherwise posted events will be lost. 102 // Caller thread will be blocked until worker thread signals readiness. 103 std::unique_lock<std::mutex> worker_lock(worker_mutex_); 104 105 // New scope to limit scope of caller lock to this block. 106 { 107 // Signal caller thread that worker thread is ready to listen to events. 108 std::unique_lock<std::mutex> caller_lock(caller_mutex_); 109 caller_cv_.notify_one(); 110 } 111 112 while (!worker_thread_exit_) { 113 // Add predicate to handle spurious interrupts. 114 // Wait for caller thread to signal new command codes. 115 worker_cv_.wait(worker_lock, [this] { return pending_code_; }); 116 117 // Call task handler which is implemented by the caller. 118 if (!worker_thread_exit_) { 119 task_handler_.OnTask(task_code_, task_context_); 120 } 121 122 pending_code_ = false; 123 // Notify completion of current task to the caller thread which is blocked. 124 std::unique_lock<std::mutex> caller_lock(caller_mutex_); 125 caller_cv_.notify_one(); 126 } 127 } 128 129 TaskHandler &task_handler_; 130 TaskCode task_code_; 131 TaskContext *task_context_ = nullptr; 132 std::thread worker_thread_; 133 std::mutex caller_mutex_; 134 std::mutex worker_mutex_; 135 std::condition_variable caller_cv_; 136 std::condition_variable worker_cv_; 137 bool worker_thread_exit_ = false; 138 bool pending_code_ = false; 139}; 140 141} // namespace sdm 142 143#endif // __SYNC_TASK_H__ 144