Home | History | Annotate | Download | only in utils
      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:
     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