1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "timer_ring.h"
17
18 #include <chrono>
19 #include <thread>
20 #include <unistd.h>
21
22 #include "xcollie_define.h"
23 #include "xcollie_utils.h"
24
25 namespace OHOS {
26 namespace HiviewDFX {
TimerRing()27 TimerRing::TimerRing() : threadInSleep_(false), lastTimeout_(0), lastDuration_(0), ringPos_(-1)
28 {
29 // 1. init nodes, add node to free list
30 freeTaskNodes_.clear();
31 for (unsigned int i = 0; i < MAX_XCOLLIE_NUM; i++) {
32 struct TaskNode* node = &taskNodes_[i];
33 InitTaskNode(node, i);
34 freeTaskNodes_.push_back(node);
35 }
36 }
37
~TimerRing()38 TimerRing::~TimerRing()
39 {
40 XCOLLIE_LOGI("~TimerRing exit...");
41 }
42
ReadyToWork()43 bool TimerRing::ReadyToWork()
44 {
45 ringPos_ = 0;
46 return true;
47 }
48
InitTaskNode(struct TaskNode * taskNode,unsigned int seq)49 void TimerRing::InitTaskNode(struct TaskNode* taskNode, unsigned int seq)
50 {
51 taskNode->id = INVALID_ID;
52 taskNode->seq = seq;
53 taskNode->pos = 0;
54 taskNode->round = 0;
55 taskNode->timerTask.name = "";
56 taskNode->timerTask.timeout = 0;
57 taskNode->timerTask.loop = false;
58 taskNode->timerTask.func = nullptr;
59 taskNode->timerTask.inputTimerPara = nullptr;
60 }
61
Run()62 bool TimerRing::Run()
63 {
64 // sleep interval
65 if (lastDuration_ < (TIMER_RING_CHECK_INTERVAL * MILLI_SECONDS)) {
66 std::this_thread::sleep_for(std::chrono::milliseconds(
67 (TIMER_RING_CHECK_INTERVAL * MILLI_SECONDS) - lastDuration_));
68 }
69
70 lastDuration_ = 0;
71
72 // find task and do time out
73 auto start = std::chrono::system_clock::now();
74 DoTimeoutTask();
75 auto end = std::chrono::system_clock::now();
76 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
77 lastDuration_ += duration.count();
78
79 // if no task, then sleep
80 start = std::chrono::system_clock::now();
81 TrySleep();
82 end = std::chrono::system_clock::now();
83 duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
84 lastDuration_ += duration.count();
85
86 return true;
87 }
88
TryRecycle(std::list<struct TaskNode * > & timeoutNodes)89 void TimerRing::TryRecycle(std::list<struct TaskNode*> &timeoutNodes)
90 {
91 std::for_each(timeoutNodes.begin(), timeoutNodes.end(), [&](struct TaskNode* &node) {
92 if (node->timerTask.loop) {
93 /* if need loop, add to ring again */
94 node->pos = CALC_RING_POS(node->timerTask.timeout);
95 node->round = CALC_RING_ROUND(node->timerTask.timeout);
96 ringTaskNodes_[node->pos].push_back(node);
97 } else {
98 /* if no loop, add to free list */
99 InitTaskNode(node, node->seq);
100 freeTaskNodes_.push_back(node);
101 }
102 });
103 }
104
DoTimeoutTask()105 void TimerRing::DoTimeoutTask()
106 {
107 std::list<struct TaskNode*> timeoutNodes;
108 {
109 std::lock_guard<std::mutex> lock(lock_);
110 for (auto it = ringTaskNodes_[ringPos_].begin(); it != ringTaskNodes_[ringPos_].end();) {
111 struct TaskNode* node = *it;
112 if (node->round == 0) {
113 node->pos = -1;
114 timeoutNodes.push_back(node);
115 it = ringTaskNodes_[ringPos_].erase(it);
116 } else {
117 node->round--;
118 it++;
119 }
120 }
121 if (timeoutNodes.size() > 0) {
122 lastTimeout_ = time(nullptr);
123 }
124 }
125
126 /* run timeout callback */
127 for (auto it : timeoutNodes) {
128 if (it->timerTask.func) {
129 XCOLLIE_LOGD("Trigger %s:0x%x Callback Function ... ", it->timerTask.name.c_str(), it->id);
130 it->timerTask.func(it->timerTask.inputTimerPara);
131 }
132 }
133
134 /* try recycle and do next loop */
135 std::lock_guard<std::mutex> lock(lock_);
136 TryRecycle(timeoutNodes);
137
138 /* point to the next timeout node */
139 ringPos_ = CALC_RING_POS(TIMER_RING_CHECK_INTERVAL);
140 }
141
TrySleep()142 void TimerRing::TrySleep()
143 {
144 std::unique_lock<std::mutex> lock(lock_);
145 if ((freeTaskNodes_.size() == MAX_XCOLLIE_NUM) &&
146 (difftime(time(nullptr), lastTimeout_) > TIMER_RING_CHECK_INTERVAL * MAX_DELAY_COUNT)) {
147 threadInSleep_ = true;
148 condition_.wait(lock, [this] {
149 return threadInSleep_ == false;
150 });
151 }
152 }
153
TryNotify()154 void TimerRing::TryNotify()
155 {
156 std::unique_lock<std::mutex> lock(lock_);
157 if (threadInSleep_) {
158 threadInSleep_ = false;
159 condition_.notify_one();
160 }
161 }
162
AddTask(const struct TimerTask & task)163 int TimerRing::AddTask(const struct TimerTask &task)
164 {
165 std::unique_lock<std::mutex> lock(lock_);
166 // 1. find task from freelist
167 if (task.timeout == 0) {
168 XCOLLIE_LOGE("timeout %u invalid, add %s failed", task.timeout, task.name.c_str());
169 return INVALID_ID;
170 }
171 if (freeTaskNodes_.empty()) {
172 XCOLLIE_LOGE("no free task node, add %s failed", task.name.c_str());
173 return INVALID_ID;
174 }
175 struct TaskNode* taskNode = freeTaskNodes_.front();
176 freeTaskNodes_.pop_front();
177 // 2. init task
178 time_t timestamp = time(nullptr);
179 taskNode->id = (((static_cast<unsigned int>(timestamp) % MAX_XCOLLIE_NUM) << COOKIE_SHIFT) | taskNode->seq);
180 taskNode->timerTask.timeout = task.timeout;
181 taskNode->timerTask.name = task.name;
182 taskNode->timerTask.inputTimerPara = task.inputTimerPara;
183 taskNode->timerTask.func = task.func;
184 taskNode->timerTask.loop = task.loop;
185 unsigned int timeout = ((task.timeout < TIMER_RING_CHECK_INTERVAL) ? TIMER_RING_CHECK_INTERVAL : task.timeout);
186 taskNode->round = CALC_RING_ROUND(timeout);
187 taskNode->pos = CALC_RING_POS(timeout);
188 if (threadInSleep_) {
189 taskNode->pos = CALC_RING_POS(timeout - TIMER_RING_CHECK_INTERVAL);
190 }
191 // 3. add task to timering
192 XCOLLIE_LOGD("TimerRing::AddTask ringPos_=%d, pos=%d, round=%d, timeout=%u, id =0x%x, name=%s",
193 ringPos_, taskNode->pos, taskNode->round, timeout,
194 taskNode->id, taskNode->timerTask.name.c_str());
195 ringTaskNodes_[taskNode->pos].push_back(taskNode);
196
197 /* if thread in sleep, then notify */
198 if (threadInSleep_) {
199 threadInSleep_ = false;
200 condition_.notify_one();
201 }
202 return taskNode->id;
203 }
204
CancelTask(int id)205 struct InputTimerPara* TimerRing::CancelTask(int id)
206 {
207 if (id <= INVALID_ID) {
208 return nullptr;
209 }
210 // 1. find tasknode
211 unsigned int seq = WRAP_SEQ(static_cast<unsigned int>(id));
212 if (seq >= MAX_XCOLLIE_NUM) {
213 XCOLLIE_LOGE("cancel task failed, seq %u limit, id 0x%x valid", seq, id);
214 return nullptr;
215 }
216
217 // 2. move to free task list
218 std::lock_guard<std::mutex> lock(lock_);
219 struct TaskNode* taskNode = &taskNodes_[seq];
220 if ((taskNode->id != id) || (taskNode->pos == -1)) {
221 XCOLLIE_LOGW("already release id 0x%x", id);
222 return nullptr;
223 }
224 XCOLLIE_LOGD("TimerRing::CancelTask ringPos_=%d, pos=%d id =0x%x,name=%s", ringPos_,
225 taskNode->pos, taskNode->id, taskNode->timerTask.name.c_str());
226 ringTaskNodes_[taskNode->pos].remove(taskNode);
227 struct InputTimerPara* inputPara = taskNode->timerTask.inputTimerPara;
228 InitTaskNode(taskNode, taskNode->seq);
229 freeTaskNodes_.push_back(taskNode);
230 lastTimeout_ = time(nullptr);
231 return inputPara;
232 }
233
UpdateTask(int id,unsigned int timeout)234 bool TimerRing::UpdateTask(int id, unsigned int timeout)
235 {
236 if (id <= INVALID_ID) {
237 return false;
238 }
239 // 1. find tasknode
240 if (timeout == 0) {
241 XCOLLIE_LOGE("timeout %u invalid, update failed", timeout);
242 return false;
243 }
244 unsigned int seq = WRAP_SEQ(static_cast<unsigned int>(id));
245 if (seq >= MAX_XCOLLIE_NUM) {
246 XCOLLIE_LOGE("update task failed, seq %u limit, id 0x%x valid", seq, id);
247 return false;
248 }
249
250 // 2. update tasknode
251 std::lock_guard<std::mutex> lock(lock_);
252 struct TaskNode* taskNode = &taskNodes_[seq];
253 if ((taskNode->id != id) || (taskNode->pos == -1)) {
254 XCOLLIE_LOGW("update task failed, already release id 0x%x", id);
255 return false;
256 }
257 taskNode->round = CALC_RING_ROUND(timeout);
258 int pos = CALC_RING_POS(timeout);
259 if (pos == taskNode->pos) {
260 return true;
261 }
262 XCOLLIE_LOGD("TimerRing::UpdateTask ringPos_=%d, pos=%d id =0x%x,name=%s", ringPos_, pos,
263 taskNode->id, taskNode->timerTask.name.c_str());
264 // update ring list
265 ringTaskNodes_[taskNode->pos].remove(taskNode);
266 taskNode->pos = pos;
267 ringTaskNodes_[pos].push_back(taskNode);
268 return true;
269 }
270 } // end of namespace HiviewDFX
271 } // end of namespace OHOS
272