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 37 namespace sdm { 38 39 template <class TaskCode> 40 class SyncTask { 41 public: 42 // This class need to be overridden by caller to pass on a task context. 43 class TaskContext { 44 public: ~TaskContext()45 virtual ~TaskContext() { } 46 }; 47 48 // Methods to callback into caller for command codes executions in worker thread. 49 class TaskHandler { 50 public: ~TaskHandler()51 virtual ~TaskHandler() { } 52 virtual void OnTask(const TaskCode &task_code, TaskContext *task_context) = 0; 53 }; 54 SyncTask(TaskHandler & task_handler)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 ~SyncTask()64 ~SyncTask() { 65 // Task code does not matter here. 66 PerformTask(task_code_, nullptr, true); 67 worker_thread_.join(); 68 } 69 PerformTask(const TaskCode & task_code,TaskContext * task_context)70 void PerformTask(const TaskCode &task_code, TaskContext *task_context) { 71 PerformTask(task_code, task_context, false); 72 } 73 74 private: PerformTask(const TaskCode & task_code,TaskContext * task_context,bool terminate)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 SyncTaskThread(SyncTask * sync_task)93 static void SyncTaskThread(SyncTask *sync_task) { 94 if (sync_task) { 95 sync_task->OnThreadCallback(); 96 } 97 } 98 OnThreadCallback()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