Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(37)

Side by Side Diff: webrtc/base/task_queue_win.cc

Issue 2733723002: Refactor Windows TaskQueue code to only need a single high res timer. (Closed)
Patch Set: Change the PostMultipleDelayed test to fire tasks targeted at different intervals Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« webrtc/base/task_queue.h ('K') | « webrtc/base/task_queue_unittest.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright 2016 The WebRTC Project Authors. All rights reserved. 2 * Copyright 2016 The WebRTC Project Authors. All rights reserved.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license 4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source 5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found 6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may 7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree. 8 * be found in the AUTHORS file in the root of the source tree.
9 */ 9 */
10 10
11 #include "webrtc/base/task_queue.h" 11 #include "webrtc/base/task_queue.h"
12 12
13 #include <mmsystem.h> 13 #include <mmsystem.h>
14 #include <string.h> 14 #include <string.h>
15 15
16 #include <algorithm> 16 #include <set>
17 17
18 #include "webrtc/base/arraysize.h" 18 #include "webrtc/base/arraysize.h"
19 #include "webrtc/base/checks.h" 19 #include "webrtc/base/checks.h"
20 #include "webrtc/base/logging.h" 20 #include "webrtc/base/logging.h"
21 #include "webrtc/base/timeutils.h"
21 22
22 namespace rtc { 23 namespace rtc {
23 namespace { 24 namespace {
24 #define WM_RUN_TASK WM_USER + 1 25 #define WM_RUN_TASK WM_USER + 1
25 #define WM_QUEUE_DELAYED_TASK WM_USER + 2 26 #define WM_QUEUE_DELAYED_TASK WM_USER + 2
26 27
27 using Priority = TaskQueue::Priority; 28 using Priority = TaskQueue::Priority;
28 29
29 DWORD g_queue_ptr_tls = 0; 30 DWORD g_queue_ptr_tls = 0;
30 31
(...skipping 29 matching lines...) Expand all
60 return kLowPriority; 61 return kLowPriority;
61 case Priority::NORMAL: 62 case Priority::NORMAL:
62 return kNormalPriority; 63 return kNormalPriority;
63 default: 64 default:
64 RTC_NOTREACHED(); 65 RTC_NOTREACHED();
65 break; 66 break;
66 } 67 }
67 return kNormalPriority; 68 return kNormalPriority;
68 } 69 }
69 70
70 #if defined(_WIN64) 71 int64_t GetTick() {
71 DWORD GetTick() {
72 static const UINT kPeriod = 1; 72 static const UINT kPeriod = 1;
73 bool high_res = (timeBeginPeriod(kPeriod) == TIMERR_NOERROR); 73 bool high_res = (timeBeginPeriod(kPeriod) == TIMERR_NOERROR);
74 DWORD ret = timeGetTime(); 74 int64_t ret = TimeMillis();
75 if (high_res) 75 if (high_res)
76 timeEndPeriod(kPeriod); 76 timeEndPeriod(kPeriod);
77 return ret; 77 return ret;
78 } 78 }
79 #endif
80 } // namespace
81 79
82 class TaskQueue::MultimediaTimer { 80 struct DelayedTaskInfo {
83 public: 81 DelayedTaskInfo(uint32_t milliseconds, std::unique_ptr<QueuedTask> task)
84 // kMaxTimers defines the limit of how many MultimediaTimer instances should 82 : due_time(GetTick() + milliseconds), task(std::move(task)) {}
85 // be created. 83 DelayedTaskInfo(const DelayedTaskInfo&) = delete;
86 // Background: The maximum number of supported handles for Wait functions, is 84 DelayedTaskInfo(DelayedTaskInfo&&) = default;
the sun 2017/03/07 11:24:24 Maybe delete operator= too?
tommi 2017/03/09 20:27:42 Done. (added RTC_DISALLOW_COPY_AND_ASSIGN)
tommi 2017/03/09 20:29:08 oops, discard that. I had to support the operator
87 // MAXIMUM_WAIT_OBJECTS - 1 (63).
88 // There are some ways to work around the limitation but as it turns out, the
89 // limit of concurrently active multimedia timers per process, is much lower,
90 // or 16. So there isn't much value in going to the lenghts required to
91 // overcome the Wait limitations.
92 // kMaxTimers is larger than 16 though since it is possible that 'complete' or
93 // signaled timers that haven't been handled, are counted as part of
94 // kMaxTimers and thus a multimedia timer can actually be queued even though
95 // as far as we're concerned, there are more than 16 that are pending.
96 static const int kMaxTimers = MAXIMUM_WAIT_OBJECTS - 1;
97 85
98 // Controls how many MultimediaTimer instances a queue can hold before 86 // Implement for <set>. to maintain an order of increasing |due_time|.
the sun 2017/03/07 11:24:24 nit: , after <set>
tommi 2017/03/09 20:27:42 Done.
99 // attempting to garbage collect (GC) timers that aren't in use. 87 bool operator<(const DelayedTaskInfo& other) const {
100 static const int kInstanceThresholdGC = 8; 88 return due_time < other.due_time;
101
102 MultimediaTimer() : event_(::CreateEvent(nullptr, false, false, nullptr)) {}
103
104 MultimediaTimer(MultimediaTimer&& timer)
105 : event_(timer.event_),
106 timer_id_(timer.timer_id_),
107 task_(std::move(timer.task_)) {
108 RTC_DCHECK(event_);
109 timer.event_ = nullptr;
110 timer.timer_id_ = 0;
111 } 89 }
112 90
113 ~MultimediaTimer() { Close(); } 91 // See below for why this method is const.
114 92 void Run() const {
115 // Implementing this operator is required because of the way 93 task->Run() ? task.reset() : static_cast<void>(task.release());
116 // some stl algorithms work, such as std::rotate().
117 MultimediaTimer& operator=(MultimediaTimer&& timer) {
118 if (this != &timer) {
119 Close();
120 event_ = timer.event_;
121 timer.event_ = nullptr;
122 task_ = std::move(timer.task_);
123 timer_id_ = timer.timer_id_;
124 timer.timer_id_ = 0;
125 }
126 return *this;
127 } 94 }
128 95
129 bool StartOneShotTimer(std::unique_ptr<QueuedTask> task, UINT delay_ms) { 96 const int64_t due_time; // Absolute timestamp in milliseconds.
97
98 private:
99 // |task| needs to be mutable because of an issue with std::multiset whereby
100 // set::begin() returns a const_iterator by default.
101 // There are two basic workarounds, one using const_cast, which would also
102 // make the key (|due_time|), non-const and the other is to make the non-key
103 // (|task|), mutable.
104 // Because of this, the |task| variable is made private and can only be
105 // mutated by calling the |Run()| method.
106 mutable std::unique_ptr<QueuedTask> task;
107 };
108
109 class MultimediaTimer {
110 public:
111 MultimediaTimer() : event_(::CreateEvent(nullptr, false, false, nullptr)) {}
112 MultimediaTimer(MultimediaTimer&&) = delete;
the sun 2017/03/07 11:24:24 Strictly speaking I don't think this is needed: ht
tommi 2017/03/09 20:27:42 Removed. I only needed it as I was changing the pr
113
114 ~MultimediaTimer() {
115 Cancel();
116 ::CloseHandle(event_);
117 }
118
119 bool StartOneShotTimer(UINT delay_ms) {
130 RTC_DCHECK_EQ(0, timer_id_); 120 RTC_DCHECK_EQ(0, timer_id_);
131 RTC_DCHECK(event_ != nullptr); 121 RTC_DCHECK(event_ != nullptr);
132 RTC_DCHECK(!task_.get());
133 RTC_DCHECK(task.get());
134 task_ = std::move(task);
135 timer_id_ = 122 timer_id_ =
136 ::timeSetEvent(delay_ms, 0, reinterpret_cast<LPTIMECALLBACK>(event_), 0, 123 ::timeSetEvent(delay_ms, 0, reinterpret_cast<LPTIMECALLBACK>(event_), 0,
137 TIME_ONESHOT | TIME_CALLBACK_EVENT_SET); 124 TIME_ONESHOT | TIME_CALLBACK_EVENT_SET);
138 return timer_id_ != 0; 125 return timer_id_ != 0;
139 } 126 }
140 127
141 std::unique_ptr<QueuedTask> Cancel() { 128 void Cancel() {
142 if (timer_id_) { 129 if (timer_id_) {
143 ::timeKillEvent(timer_id_); 130 ::timeKillEvent(timer_id_);
144 timer_id_ = 0; 131 timer_id_ = 0;
145 } 132 }
146 return std::move(task_);
147 } 133 }
148 134
149 void OnEventSignaled() { 135 HANDLE* event_for_wait() { return &event_; }
150 RTC_DCHECK_NE(0, timer_id_);
151 timer_id_ = 0;
152 task_->Run() ? task_.reset() : static_cast<void>(task_.release());
153 }
154
155 HANDLE event() const { return event_; }
156
157 bool is_active() const { return timer_id_ != 0; }
158 136
159 private: 137 private:
160 void Close() {
161 Cancel();
162
163 if (event_) {
164 ::CloseHandle(event_);
165 event_ = nullptr;
166 }
167 }
168
169 HANDLE event_ = nullptr; 138 HANDLE event_ = nullptr;
170 MMRESULT timer_id_ = 0; 139 MMRESULT timer_id_ = 0;
171 std::unique_ptr<QueuedTask> task_;
172 140
173 RTC_DISALLOW_COPY_AND_ASSIGN(MultimediaTimer); 141 RTC_DISALLOW_COPY_AND_ASSIGN(MultimediaTimer);
174 }; 142 };
175 143
144 } // namespace
145
146 class TaskQueue::ThreadState {
147 public:
148 ThreadState() {}
149 ~ThreadState() {}
150
151 void RunThreadMain();
152
153 private:
154 bool ProcessQueuedMessages();
155 void RunDueTasks();
156 void ScheduleNextTimer();
157 void CancelTimers();
158
159 MultimediaTimer timer_;
160 std::multiset<DelayedTaskInfo> timer_tasks_;
the sun 2017/03/07 11:24:24 It's not clear to me that multiset is the best str
tommi 2017/03/09 20:27:42 Done.
161 UINT_PTR timer_id_ = 0;
162 };
163
176 TaskQueue::TaskQueue(const char* queue_name, Priority priority /*= NORMAL*/) 164 TaskQueue::TaskQueue(const char* queue_name, Priority priority /*= NORMAL*/)
177 : thread_(&TaskQueue::ThreadMain, 165 : thread_(&TaskQueue::ThreadMain,
178 this, 166 this,
179 queue_name, 167 queue_name,
180 TaskQueuePriorityToThreadPriority(priority)) { 168 TaskQueuePriorityToThreadPriority(priority)) {
181 RTC_DCHECK(queue_name); 169 RTC_DCHECK(queue_name);
182 thread_.Start(); 170 thread_.Start();
183 Event event(false, false); 171 Event event(false, false);
184 ThreadStartupData startup = {&event, this}; 172 ThreadStartupData startup = {&event, this};
185 RTC_CHECK(thread_.QueueAPC(&InitializeQueueThread, 173 RTC_CHECK(thread_.QueueAPC(&InitializeQueueThread,
(...skipping 27 matching lines...) Expand all
213 201
214 void TaskQueue::PostTask(std::unique_ptr<QueuedTask> task) { 202 void TaskQueue::PostTask(std::unique_ptr<QueuedTask> task) {
215 if (::PostThreadMessage(thread_.GetThreadRef(), WM_RUN_TASK, 0, 203 if (::PostThreadMessage(thread_.GetThreadRef(), WM_RUN_TASK, 0,
216 reinterpret_cast<LPARAM>(task.get()))) { 204 reinterpret_cast<LPARAM>(task.get()))) {
217 task.release(); 205 task.release();
218 } 206 }
219 } 207 }
220 208
221 void TaskQueue::PostDelayedTask(std::unique_ptr<QueuedTask> task, 209 void TaskQueue::PostDelayedTask(std::unique_ptr<QueuedTask> task,
222 uint32_t milliseconds) { 210 uint32_t milliseconds) {
223 WPARAM wparam; 211 if (!milliseconds) {
224 #if defined(_WIN64) 212 PostTask(std::move(task));
225 // GetTickCount() returns a fairly coarse tick count (resolution or about 8ms) 213 return;
226 // so this compensation isn't that accurate, but since we have unused 32 bits 214 }
227 // on Win64, we might as well use them. 215
228 wparam = (static_cast<WPARAM>(GetTick()) << 32) | milliseconds; 216 auto* task_info = new DelayedTaskInfo(milliseconds, std::move(task));
the sun 2017/03/07 11:24:24 Add a TODO that we should remove this heap alloc.
tommi 2017/03/09 20:27:42 Done.
229 #else 217 if (!::PostThreadMessage(thread_.GetThreadRef(), WM_QUEUE_DELAYED_TASK, 0,
230 wparam = milliseconds; 218 reinterpret_cast<LPARAM>(task_info))) {
231 #endif 219 delete task_info;
232 if (::PostThreadMessage(thread_.GetThreadRef(), WM_QUEUE_DELAYED_TASK, wparam,
233 reinterpret_cast<LPARAM>(task.get()))) {
234 task.release();
235 } 220 }
236 } 221 }
237 222
238 void TaskQueue::PostTaskAndReply(std::unique_ptr<QueuedTask> task, 223 void TaskQueue::PostTaskAndReply(std::unique_ptr<QueuedTask> task,
239 std::unique_ptr<QueuedTask> reply, 224 std::unique_ptr<QueuedTask> reply,
240 TaskQueue* reply_queue) { 225 TaskQueue* reply_queue) {
241 QueuedTask* task_ptr = task.release(); 226 QueuedTask* task_ptr = task.release();
242 QueuedTask* reply_task_ptr = reply.release(); 227 QueuedTask* reply_task_ptr = reply.release();
243 DWORD reply_thread_id = reply_queue->thread_.GetThreadRef(); 228 DWORD reply_thread_id = reply_queue->thread_.GetThreadRef();
244 PostTask([task_ptr, reply_task_ptr, reply_thread_id]() { 229 PostTask([task_ptr, reply_task_ptr, reply_thread_id]() {
245 if (task_ptr->Run()) 230 if (task_ptr->Run())
246 delete task_ptr; 231 delete task_ptr;
247 // If the thread's message queue is full, we can't queue the task and will 232 // If the thread's message queue is full, we can't queue the task and will
248 // have to drop it (i.e. delete). 233 // have to drop it (i.e. delete).
249 if (!::PostThreadMessage(reply_thread_id, WM_RUN_TASK, 0, 234 if (!::PostThreadMessage(reply_thread_id, WM_RUN_TASK, 0,
250 reinterpret_cast<LPARAM>(reply_task_ptr))) { 235 reinterpret_cast<LPARAM>(reply_task_ptr))) {
251 delete reply_task_ptr; 236 delete reply_task_ptr;
252 } 237 }
253 }); 238 });
254 } 239 }
255 240
256 void TaskQueue::PostTaskAndReply(std::unique_ptr<QueuedTask> task, 241 void TaskQueue::PostTaskAndReply(std::unique_ptr<QueuedTask> task,
257 std::unique_ptr<QueuedTask> reply) { 242 std::unique_ptr<QueuedTask> reply) {
258 return PostTaskAndReply(std::move(task), std::move(reply), Current()); 243 return PostTaskAndReply(std::move(task), std::move(reply), Current());
259 } 244 }
260 245
261 // static 246 // static
262 void TaskQueue::ThreadMain(void* context) { 247 void TaskQueue::ThreadMain(void* context) {
263 HANDLE timer_handles[MultimediaTimer::kMaxTimers]; 248 ThreadState state;
264 // Active multimedia timers. 249 state.RunThreadMain();
265 std::vector<MultimediaTimer> mm_timers; 250 }
266 // Tasks that have been queued by using SetTimer/WM_TIMER.
267 DelayedTasks delayed_tasks;
268 251
252 void TaskQueue::ThreadState::RunThreadMain() {
269 while (true) { 253 while (true) {
270 RTC_DCHECK(mm_timers.size() <= arraysize(timer_handles));
271 DWORD count = 0;
272 for (const auto& t : mm_timers) {
273 if (!t.is_active())
274 break;
275 timer_handles[count++] = t.event();
276 }
277 // Make sure we do an alertable wait as that's required to allow APCs to run 254 // Make sure we do an alertable wait as that's required to allow APCs to run
278 // (e.g. required for InitializeQueueThread and stopping the thread in 255 // (e.g. required for InitializeQueueThread and stopping the thread in
279 // PlatformThread). 256 // PlatformThread).
280 DWORD result = ::MsgWaitForMultipleObjectsEx(count, timer_handles, INFINITE, 257 DWORD result = ::MsgWaitForMultipleObjectsEx(
281 QS_ALLEVENTS, MWMO_ALERTABLE); 258 1, timer_.event_for_wait(), INFINITE, QS_ALLEVENTS, MWMO_ALERTABLE);
282 RTC_CHECK_NE(WAIT_FAILED, result); 259 RTC_CHECK_NE(WAIT_FAILED, result);
283 // If we're not waiting for any timers, then count will be equal to 260 if (result == (WAIT_OBJECT_0 + 1)) {
284 // WAIT_OBJECT_0. If we're waiting for timers, then |count| represents 261 // There are messages in the message queue that need to be handled.
285 // "One more than the number of timers", which means that there's a 262 if (!ProcessQueuedMessages())
286 // message in the queue that needs to be handled.
287 // If |result| is less than |count|, then its value will be the index of the
288 // timer that has been signaled.
289 if (result == (WAIT_OBJECT_0 + count)) {
290 if (!ProcessQueuedMessages(&delayed_tasks, &mm_timers))
291 break; 263 break;
292 } else if (result < (WAIT_OBJECT_0 + count)) { 264 } else if (result == WAIT_OBJECT_0) {
293 mm_timers[result].OnEventSignaled(); 265 // The multimedia timer was signaled.
294 RTC_DCHECK(!mm_timers[result].is_active()); 266 timer_.Cancel();
295 // Reuse timer events by moving inactive timers to the back of the vector. 267 RTC_DCHECK(!timer_tasks_.empty());
296 // When new delayed tasks are queued, they'll get reused. 268 RunDueTasks();
297 if (mm_timers.size() > 1) { 269 ScheduleNextTimer();
298 auto it = mm_timers.begin() + result;
299 std::rotate(it, it + 1, mm_timers.end());
300 }
301
302 // Collect some garbage.
303 if (mm_timers.size() > MultimediaTimer::kInstanceThresholdGC) {
304 const auto inactive = std::find_if(
305 mm_timers.begin(), mm_timers.end(),
306 [](const MultimediaTimer& t) { return !t.is_active(); });
307 if (inactive != mm_timers.end()) {
308 // Since inactive timers are always moved to the back, we can
309 // safely delete all timers following the first inactive one.
310 mm_timers.erase(inactive, mm_timers.end());
311 }
312 }
313 } else { 270 } else {
314 RTC_DCHECK_EQ(WAIT_IO_COMPLETION, result); 271 RTC_DCHECK_EQ(WAIT_IO_COMPLETION, result);
315 } 272 }
316 } 273 }
317 } 274 }
318 275
319 // static 276 bool TaskQueue::ThreadState::ProcessQueuedMessages() {
320 bool TaskQueue::ProcessQueuedMessages(DelayedTasks* delayed_tasks,
321 std::vector<MultimediaTimer>* timers) {
322 MSG msg = {}; 277 MSG msg = {};
323 while (::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) && 278 while (::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) &&
324 msg.message != WM_QUIT) { 279 msg.message != WM_QUIT) {
325 if (!msg.hwnd) { 280 if (!msg.hwnd) {
326 switch (msg.message) { 281 switch (msg.message) {
327 case WM_RUN_TASK: { 282 case WM_RUN_TASK: {
328 QueuedTask* task = reinterpret_cast<QueuedTask*>(msg.lParam); 283 QueuedTask* task = reinterpret_cast<QueuedTask*>(msg.lParam);
329 if (task->Run()) 284 if (task->Run())
330 delete task; 285 delete task;
331 break; 286 break;
332 } 287 }
333 case WM_QUEUE_DELAYED_TASK: { 288 case WM_QUEUE_DELAYED_TASK: {
334 std::unique_ptr<QueuedTask> task( 289 std::unique_ptr<DelayedTaskInfo> info(
335 reinterpret_cast<QueuedTask*>(msg.lParam)); 290 reinterpret_cast<DelayedTaskInfo*>(msg.lParam));
336 uint32_t milliseconds = msg.wParam & 0xFFFFFFFF; 291 auto inserted = timer_tasks_.insert(std::move(*info.get()));
337 #if defined(_WIN64) 292 // If the new task was not added to the front, we're done since a
338 // Subtract the time it took to queue the timer. 293 // timer has already been scheduled.
339 const DWORD now = GetTick(); 294 if (inserted == timer_tasks_.begin()) {
340 DWORD post_time = now - (msg.wParam >> 32); 295 CancelTimers();
341 milliseconds = 296 ScheduleNextTimer();
342 post_time > milliseconds ? 0 : milliseconds - post_time;
343 #endif
344 bool timer_queued = false;
345 if (timers->size() < MultimediaTimer::kMaxTimers) {
346 MultimediaTimer* timer = nullptr;
347 auto available = std::find_if(
348 timers->begin(), timers->end(),
349 [](const MultimediaTimer& t) { return !t.is_active(); });
350 if (available != timers->end()) {
351 timer = &(*available);
352 } else {
353 timers->emplace_back();
354 timer = &timers->back();
355 }
356
357 timer_queued =
358 timer->StartOneShotTimer(std::move(task), milliseconds);
359 if (!timer_queued) {
360 // No more multimedia timers can be queued.
361 // Detach the task and fall back on SetTimer.
362 task = timer->Cancel();
363 }
364 }
365
366 // When we fail to use multimedia timers, we fall back on the more
367 // coarse SetTimer/WM_TIMER approach.
368 if (!timer_queued) {
369 UINT_PTR timer_id = ::SetTimer(nullptr, 0, milliseconds, nullptr);
370 delayed_tasks->insert(std::make_pair(timer_id, task.release()));
371 } 297 }
372 break; 298 break;
373 } 299 }
374 case WM_TIMER: { 300 case WM_TIMER: {
301 RTC_DCHECK_EQ(timer_id_, msg.wParam);
375 ::KillTimer(nullptr, msg.wParam); 302 ::KillTimer(nullptr, msg.wParam);
376 auto found = delayed_tasks->find(msg.wParam); 303 timer_id_ = 0;
377 RTC_DCHECK(found != delayed_tasks->end()); 304 RunDueTasks();
378 if (!found->second->Run()) 305 ScheduleNextTimer();
379 found->second.release();
380 delayed_tasks->erase(found);
381 break; 306 break;
382 } 307 }
383 default: 308 default:
384 RTC_NOTREACHED(); 309 RTC_NOTREACHED();
385 break; 310 break;
386 } 311 }
387 } else { 312 } else {
388 ::TranslateMessage(&msg); 313 ::TranslateMessage(&msg);
389 ::DispatchMessage(&msg); 314 ::DispatchMessage(&msg);
390 } 315 }
391 } 316 }
392 return msg.message != WM_QUIT; 317 return msg.message != WM_QUIT;
393 } 318 }
394 319
320 void TaskQueue::ThreadState::RunDueTasks() {
321 std::multiset<DelayedTaskInfo>::iterator it = timer_tasks_.begin();
322 do {
323 if ((*it).due_time > GetTick())
324 break;
325 (*it).Run();
326 ++it;
327 } while (it != timer_tasks_.end());
328
329 timer_tasks_.erase(timer_tasks_.begin(), it);
the sun 2017/03/07 11:24:24 I assume nothing will be erased from the set if bo
tommi 2017/03/09 20:27:42 Yes - however, the implementation has changed slig
330 }
331
332 void TaskQueue::ThreadState::ScheduleNextTimer() {
333 RTC_DCHECK_EQ(timer_id_, 0);
334 if (timer_tasks_.empty())
335 return;
336
337 uint32_t milliseconds =
338 static_cast<uint32_t>(timer_tasks_.begin()->due_time - GetTick());
the sun 2017/03/07 11:24:24 It seems to me you need to manage the case when we
tommi 2017/03/09 20:27:42 Good catch. Done.
339 if (!timer_.StartOneShotTimer(milliseconds))
340 timer_id_ = ::SetTimer(nullptr, 0, milliseconds, nullptr);
341 }
342
343 void TaskQueue::ThreadState::CancelTimers() {
344 timer_.Cancel();
345 if (timer_id_) {
346 ::KillTimer(nullptr, timer_id_);
347 timer_id_ = 0;
348 }
349 }
350
395 } // namespace rtc 351 } // namespace rtc
OLDNEW
« webrtc/base/task_queue.h ('K') | « webrtc/base/task_queue_unittest.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698