1 //
2 // Copyright (C) 2012 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #include "shill/hook_table.h"
18 
19 #include <list>
20 #include <string>
21 
22 #include <base/bind.h>
23 #include <base/callback.h>
24 #include <base/cancelable_callback.h>
25 
26 #include "shill/error.h"
27 #include "shill/event_dispatcher.h"
28 #include "shill/logging.h"
29 
30 using base::Bind;
31 using base::Closure;
32 using base::Unretained;
33 using std::list;
34 using std::string;
35 
36 namespace shill {
37 
38 namespace Logging {
39 static auto kModuleLogScope = ScopeLogger::kManager;
ObjectID(const HookTable * h)40 static string ObjectID(const HookTable* h) { return "(hook_table)"; }
41 }
42 
HookTable(EventDispatcher * event_dispatcher)43 HookTable::HookTable(EventDispatcher* event_dispatcher)
44     : event_dispatcher_(event_dispatcher) {}
45 
Add(const string & name,const Closure & start_callback)46 void HookTable::Add(const string& name, const Closure& start_callback) {
47   SLOG(this, 2) << __func__ << ": " << name;
48   Remove(name);
49   hook_table_.emplace(name, HookAction(start_callback));
50 }
51 
~HookTable()52 HookTable::~HookTable() {
53   timeout_callback_.Cancel();
54 }
55 
Remove(const std::string & name)56 void HookTable::Remove(const std::string& name) {
57   SLOG(this, 2) << __func__ << ": " << name;
58   hook_table_.erase(name);
59 }
60 
ActionComplete(const std::string & name)61 void HookTable::ActionComplete(const std::string& name) {
62   SLOG(this, 2) << __func__ << ": " << name;
63   HookTableMap::iterator it = hook_table_.find(name);
64   if (it != hook_table_.end()) {
65     HookAction* action = &it->second;
66     if (action->started && !action->completed) {
67       action->completed = true;
68     }
69   }
70   if (AllActionsComplete() && !done_callback_.is_null()) {
71     timeout_callback_.Cancel();
72     done_callback_.Run(Error(Error::kSuccess));
73     done_callback_.Reset();
74   }
75 }
76 
Run(int timeout_ms,const ResultCallback & done)77 void HookTable::Run(int timeout_ms, const ResultCallback& done) {
78   SLOG(this, 2) << __func__;
79   if (hook_table_.empty()) {
80     done.Run(Error(Error::kSuccess));
81     return;
82   }
83   done_callback_ = done;
84   timeout_callback_.Reset(Bind(&HookTable::ActionsTimedOut, Unretained(this)));
85   event_dispatcher_->PostDelayedTask(timeout_callback_.callback(), timeout_ms);
86 
87   // Mark all actions as having started before we execute any actions.
88   // Otherwise, if the first action completes inline, its call to
89   // ActionComplete() will cause the |done| callback to be invoked before the
90   // rest of the actions get started.
91   //
92   // An action that completes inline could call HookTable::Remove(), which
93   // modifies |hook_table_|. It is thus not safe to iterate through
94   // |hook_table_| to execute the actions. Instead, we keep a list of start
95   // callback of each action and iterate through that to invoke the callback.
96   list<Closure> action_start_callbacks;
97   for (auto& hook_entry : hook_table_) {
98     HookAction* action = &hook_entry.second;
99     action_start_callbacks.push_back(action->start_callback);
100     action->started = true;
101     action->completed = false;
102   }
103   // Now start the actions.
104   for (auto& callback : action_start_callbacks) {
105     callback.Run();
106   }
107 }
108 
AllActionsComplete() const109 bool HookTable::AllActionsComplete() const {
110   SLOG(this, 2) << __func__;
111   for (const auto& hook_entry : hook_table_) {
112     const HookAction& action = hook_entry.second;
113     if (action.started && !action.completed) {
114         return false;
115     }
116   }
117   return true;
118 }
119 
ActionsTimedOut()120 void HookTable::ActionsTimedOut() {
121   done_callback_.Run(Error(Error::kOperationTimeout));
122   done_callback_.Reset();
123 }
124 
125 }  // namespace shill
126