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

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

Issue 2691973002: Add support for multimedia timers to TaskQueue on Windows. (Closed)
Patch Set: Update comment Created 3 years, 10 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
« no previous file with comments | « 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 <string.h> 14 #include <string.h>
14 15
16 #include <algorithm>
17
18 #include "webrtc/base/arraysize.h"
15 #include "webrtc/base/checks.h" 19 #include "webrtc/base/checks.h"
16 #include "webrtc/base/logging.h" 20 #include "webrtc/base/logging.h"
17 21
18 namespace rtc { 22 namespace rtc {
19 namespace { 23 namespace {
20 #define WM_RUN_TASK WM_USER + 1 24 #define WM_RUN_TASK WM_USER + 1
21 #define WM_QUEUE_DELAYED_TASK WM_USER + 2 25 #define WM_QUEUE_DELAYED_TASK WM_USER + 2
22 26
23 DWORD g_queue_ptr_tls = 0; 27 DWORD g_queue_ptr_tls = 0;
24 28
25 BOOL CALLBACK InitializeTls(PINIT_ONCE init_once, void* param, void** context) { 29 BOOL CALLBACK InitializeTls(PINIT_ONCE init_once, void* param, void** context) {
26 g_queue_ptr_tls = TlsAlloc(); 30 g_queue_ptr_tls = TlsAlloc();
27 return TRUE; 31 return TRUE;
28 } 32 }
29 33
30 DWORD GetQueuePtrTls() { 34 DWORD GetQueuePtrTls() {
31 static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT; 35 static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT;
32 InitOnceExecuteOnce(&init_once, InitializeTls, nullptr, nullptr); 36 InitOnceExecuteOnce(&init_once, InitializeTls, nullptr, nullptr);
the sun 2017/02/16 12:07:25 nit: Would be nice to use global scope for all win
tommi 2017/02/16 17:38:47 I was debating that since that's what I'm actually
33 return g_queue_ptr_tls; 37 return g_queue_ptr_tls;
34 } 38 }
35 39
36 struct ThreadStartupData { 40 struct ThreadStartupData {
37 Event* started; 41 Event* started;
38 void* thread_context; 42 void* thread_context;
39 }; 43 };
40 44
41 void CALLBACK InitializeQueueThread(ULONG_PTR param) { 45 void CALLBACK InitializeQueueThread(ULONG_PTR param) {
42 MSG msg; 46 MSG msg;
43 PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); 47 PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
44 ThreadStartupData* data = reinterpret_cast<ThreadStartupData*>(param); 48 ThreadStartupData* data = reinterpret_cast<ThreadStartupData*>(param);
45 TlsSetValue(GetQueuePtrTls(), data->thread_context); 49 TlsSetValue(GetQueuePtrTls(), data->thread_context);
46 data->started->Set(); 50 data->started->Set();
47 } 51 }
48 } // namespace 52 } // namespace
49 53
54 class TaskQueue::MultimediaTimer {
55 public:
56 // kMaxTimers defines the limit of how many MultimediaTimer instances should
57 // be created.
58 // Background: The maximum number of supported handles for Wait functions, is
59 // MAXIMUM_WAIT_OBJECTS - 1 (63).
60 // There are some ways to work around the limitation but as it turns out, the
61 // limit of concurrently active multimedia timers per process, is much lower,
62 // or 16. So there isn't much value in going to the lenghts required to
63 // overcome the Wait limitations.
64 // kMaxTimers larger than 16 though since it is possible that 'complete' or
the sun 2017/02/16 12:07:25 nit: Odd start of sentence
tommi 2017/02/16 17:38:47 ah, there was a missing 'is'. fixed.
65 // signaled timers that haven't been handled, are counted as part of
66 // kMaxTimers and thus a multimedia timer can actually be queued even though
67 // as far as we're concerned, there are more than 16 that are pending.
68 static const int kMaxTimers = MAXIMUM_WAIT_OBJECTS - 1;
69
70 MultimediaTimer() : event_(CreateEvent(nullptr, false, false, nullptr)) {}
71
72 MultimediaTimer(MultimediaTimer&& timer)
73 : event_(timer.event_),
74 timer_id_(timer.timer_id_),
75 task_(std::move(timer.task_)) {
76 RTC_DCHECK(event_);
77 timer.event_ = nullptr;
78 timer.timer_id_ = 0;
79 }
80
81 ~MultimediaTimer() { Close(); }
82
83 // Implementing this operator is required because of the way
84 // some stl algorithms work, such as std::rotate().
85 MultimediaTimer& operator=(MultimediaTimer&& timer) {
86 if (this != &timer) {
87 Close();
88 event_ = timer.event_;
89 timer.event_ = nullptr;
90 task_ = std::move(timer.task_);
91 timer_id_ = timer.timer_id_;
92 timer.timer_id_ = 0;
93 }
94 return *this;
95 }
96
97 void Close() {
the sun 2017/02/16 12:07:25 make private
tommi 2017/02/16 17:38:47 Done.
98 Cancel();
99
100 if (event_) {
101 CloseHandle(event_);
102 event_ = nullptr;
103 }
104 }
105
106 bool StartOneShotTimer(std::unique_ptr<QueuedTask> task, UINT delay_ms) {
107 RTC_DCHECK(timer_id_ == 0);
the sun 2017/02/16 12:07:25 nit: _EQ, _NE is what's preferred (though personal
tommi 2017/02/16 17:38:47 Done. I couldn't do it in a couple of cases since
108 RTC_DCHECK(event_ != nullptr);
109 RTC_DCHECK(!task_.get());
110 RTC_DCHECK(task.get());
111 task_ = std::move(task);
112 timer_id_ =
113 timeSetEvent(delay_ms, 0, reinterpret_cast<LPTIMECALLBACK>(event_), 0,
114 TIME_ONESHOT | TIME_CALLBACK_EVENT_SET);
115 return timer_id_ != 0;
116 }
117
118 std::unique_ptr<QueuedTask> Cancel() {
119 if (timer_id_) {
120 timeKillEvent(timer_id_);
121 timer_id_ = 0;
122 }
123 return std::move(task_);
124 }
125
126 void OnEventSignaled() {
127 RTC_DCHECK(timer_id_ != 0);
128 timer_id_ = 0;
129 task_->Run() ? task_.reset() : static_cast<void>(task_.release());
130 }
131
132 HANDLE event() const { return event_; }
133
134 bool is_active() const { return timer_id_ != 0; }
135
136 private:
137 HANDLE event_ = nullptr;
138 MMRESULT timer_id_ = 0;
139 std::unique_ptr<QueuedTask> task_;
140
141 RTC_DISALLOW_COPY_AND_ASSIGN(MultimediaTimer);
142 };
143
50 TaskQueue::TaskQueue(const char* queue_name) 144 TaskQueue::TaskQueue(const char* queue_name)
51 : thread_(&TaskQueue::ThreadMain, this, queue_name) { 145 : thread_(&TaskQueue::ThreadMain, this, queue_name) {
52 RTC_DCHECK(queue_name); 146 RTC_DCHECK(queue_name);
53 thread_.Start(); 147 thread_.Start();
54 Event event(false, false); 148 Event event(false, false);
55 ThreadStartupData startup = {&event, this}; 149 ThreadStartupData startup = {&event, this};
56 RTC_CHECK(thread_.QueueAPC(&InitializeQueueThread, 150 RTC_CHECK(thread_.QueueAPC(&InitializeQueueThread,
57 reinterpret_cast<ULONG_PTR>(&startup))); 151 reinterpret_cast<ULONG_PTR>(&startup)));
58 event.Wait(Event::kForever); 152 event.Wait(Event::kForever);
59 } 153 }
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
124 }); 218 });
125 } 219 }
126 220
127 void TaskQueue::PostTaskAndReply(std::unique_ptr<QueuedTask> task, 221 void TaskQueue::PostTaskAndReply(std::unique_ptr<QueuedTask> task,
128 std::unique_ptr<QueuedTask> reply) { 222 std::unique_ptr<QueuedTask> reply) {
129 return PostTaskAndReply(std::move(task), std::move(reply), Current()); 223 return PostTaskAndReply(std::move(task), std::move(reply), Current());
130 } 224 }
131 225
132 // static 226 // static
133 bool TaskQueue::ThreadMain(void* context) { 227 bool TaskQueue::ThreadMain(void* context) {
228 HANDLE timer_handles[MultimediaTimer::kMaxTimers];
229 // Active multimedia timers.
230 std::vector<MultimediaTimer> mm_timers;
231 // Tasks that have been queued by using SetTimer/WM_TIMER.
134 DelayedTasks delayed_tasks; 232 DelayedTasks delayed_tasks;
233
135 while (true) { 234 while (true) {
136 DWORD result = ::MsgWaitForMultipleObjectsEx(0, nullptr, INFINITE, 235 RTC_DCHECK(mm_timers.size() <= arraysize(timer_handles));
236 DWORD count = 0;
237 for (const auto& t : mm_timers) {
238 if (!t.is_active())
239 break;
240 timer_handles[count++] = t.event();
241 }
242 DWORD result = ::MsgWaitForMultipleObjectsEx(count, timer_handles, INFINITE,
137 QS_ALLEVENTS, MWMO_ALERTABLE); 243 QS_ALLEVENTS, MWMO_ALERTABLE);
the sun 2017/02/16 12:07:25 Ah, so we need MWMO_ALTERTABLE for the stop event
tommi 2017/02/16 17:38:47 Done. It's also needed for InitializeQueueThread a
138 RTC_CHECK_NE(WAIT_FAILED, result); 244 RTC_CHECK_NE(WAIT_FAILED, result);
139 if (result == WAIT_OBJECT_0) { 245 if (result == count) {
the sun 2017/02/16 12:07:25 I'd rather spell out WAIT_OBJECT_0 when checking t
tommi 2017/02/16 17:38:47 |count| will only be equal to WAIT_OBJECT_0 if cou
the sun 2017/02/16 20:18:47 I was thinking about being overly clear to the cas
tommi 2017/02/17 09:43:57 Ah, yes, that makes sense.
140 if (!ProcessQueuedMessages(&delayed_tasks)) 246 if (!ProcessQueuedMessages(&delayed_tasks, &mm_timers))
141 break; 247 break;
248 } else if (result < count) {
the sun 2017/02/16 12:07:25 and here
tommi 2017/02/16 17:38:47 Same here. The purpose of this check is to make s
249 mm_timers[result].OnEventSignaled();
250 RTC_DCHECK(!mm_timers[result].is_active());
251 // Reuse timer events by moving inactive timers to the back of the vector.
252 // When new delayed tasks are queued, they'll get reused.
253 if (mm_timers.size() > 1) {
254 auto it = mm_timers.begin() + result;
255 std::rotate(it, it + 1, mm_timers.end());
256 }
257
258 // Collect some garbage.
259 if (mm_timers.size() > 8) {
the sun 2017/02/16 12:07:25 Add a constant for the "8" inside MultimediaTimer:
tommi 2017/02/16 17:38:47 Done.
260 const auto inactive = std::find_if(
261 mm_timers.begin(), mm_timers.end(),
262 [](const MultimediaTimer& t) { return !t.is_active(); });
263 if (inactive != mm_timers.end()) {
264 // Since inactive timers are always moved to the back, we can
265 // safely delete all timers following the first inactive one.
266 mm_timers.erase(inactive, mm_timers.end());
267 }
268 }
142 } else { 269 } else {
143 RTC_DCHECK_EQ(WAIT_IO_COMPLETION, result); 270 RTC_DCHECK_EQ(WAIT_IO_COMPLETION, result);
144 } 271 }
145 } 272 }
273
146 return false; 274 return false;
147 } 275 }
148 276
149 // static 277 // static
150 bool TaskQueue::ProcessQueuedMessages(DelayedTasks* delayed_tasks) { 278 bool TaskQueue::ProcessQueuedMessages(DelayedTasks* delayed_tasks,
279 std::vector<MultimediaTimer>* timers) {
151 MSG msg = {}; 280 MSG msg = {};
152 while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) && 281 while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) &&
153 msg.message != WM_QUIT) { 282 msg.message != WM_QUIT) {
154 if (!msg.hwnd) { 283 if (!msg.hwnd) {
155 switch (msg.message) { 284 switch (msg.message) {
156 case WM_RUN_TASK: { 285 case WM_RUN_TASK: {
157 QueuedTask* task = reinterpret_cast<QueuedTask*>(msg.lParam); 286 QueuedTask* task = reinterpret_cast<QueuedTask*>(msg.lParam);
158 if (task->Run()) 287 if (task->Run())
159 delete task; 288 delete task;
160 break; 289 break;
161 } 290 }
162 case WM_QUEUE_DELAYED_TASK: { 291 case WM_QUEUE_DELAYED_TASK: {
163 QueuedTask* task = reinterpret_cast<QueuedTask*>(msg.lParam); 292 std::unique_ptr<QueuedTask> task(
293 reinterpret_cast<QueuedTask*>(msg.lParam));
164 uint32_t milliseconds = msg.wParam & 0xFFFFFFFF; 294 uint32_t milliseconds = msg.wParam & 0xFFFFFFFF;
165 #if defined(_WIN64) 295 #if defined(_WIN64)
166 // Subtract the time it took to queue the timer. 296 // Subtract the time it took to queue the timer.
167 const DWORD now = GetTickCount(); 297 const DWORD now = GetTickCount();
168 DWORD post_time = now - (msg.wParam >> 32); 298 DWORD post_time = now - (msg.wParam >> 32);
169 milliseconds = 299 milliseconds =
170 post_time > milliseconds ? 0 : milliseconds - post_time; 300 post_time > milliseconds ? 0 : milliseconds - post_time;
171 #endif 301 #endif
172 UINT_PTR timer_id = SetTimer(nullptr, 0, milliseconds, nullptr); 302 bool timer_queued = false;
173 delayed_tasks->insert(std::make_pair(timer_id, task)); 303 if (timers->size() < MultimediaTimer::kMaxTimers) {
304 MultimediaTimer* timer = nullptr;
305 auto available = std::find_if(
306 timers->begin(), timers->end(),
307 [](const MultimediaTimer& t) { return !t.is_active(); });
308 if (available != timers->end()) {
309 timer = &(*available);
310 } else {
311 timers->emplace_back();
the sun 2017/02/16 12:07:25 What's the overhead of recreating timers (CreateEv
tommi 2017/02/16 17:38:47 CreateEvent creates a kernel object, so the cost c
the sun 2017/02/16 20:18:47 Out of curiosity, what's the limit? The only limit
tommi 2017/02/17 09:43:57 That's probably correct, given the source :) I do
the sun 2017/02/17 10:50:14 Yeah that was it.
312 timer = &timers->back();
313 }
314
315 timer_queued =
316 timer->StartOneShotTimer(std::move(task), milliseconds);
317 if (!timer_queued) {
318 // No more multimedia timers can be queued.
319 // Detach the task and fall back on SetTimer.
320 task = timer->Cancel();
321 }
322 }
323
324 // When we fail to use multimedia timers, we fall back on the more
325 // coarse SetTimer/WM_TIMER approach.
326 if (!timer_queued) {
the sun 2017/02/16 12:07:25 Do we need logging when this happens? If I'm doing
tommi 2017/02/16 17:38:47 That sounds useful so I've added warning log. How
the sun 2017/02/16 20:18:47 Sounds like the best option. :/ Can you add approp
tommi 2017/02/17 09:43:57 Done.
327 UINT_PTR timer_id = SetTimer(nullptr, 0, milliseconds, nullptr);
328 delayed_tasks->insert(std::make_pair(timer_id, task.release()));
329 }
174 break; 330 break;
175 } 331 }
176 case WM_TIMER: { 332 case WM_TIMER: {
177 KillTimer(nullptr, msg.wParam); 333 KillTimer(nullptr, msg.wParam);
178 auto found = delayed_tasks->find(msg.wParam); 334 auto found = delayed_tasks->find(msg.wParam);
179 RTC_DCHECK(found != delayed_tasks->end()); 335 RTC_DCHECK(found != delayed_tasks->end());
180 if (!found->second->Run()) 336 if (!found->second->Run())
181 found->second.release(); 337 found->second.release();
182 delayed_tasks->erase(found); 338 delayed_tasks->erase(found);
183 break; 339 break;
184 } 340 }
185 default: 341 default:
186 RTC_NOTREACHED(); 342 RTC_NOTREACHED();
187 break; 343 break;
188 } 344 }
189 } else { 345 } else {
190 TranslateMessage(&msg); 346 TranslateMessage(&msg);
191 DispatchMessage(&msg); 347 DispatchMessage(&msg);
192 } 348 }
193 } 349 }
194 return msg.message != WM_QUIT; 350 return msg.message != WM_QUIT;
195 } 351 }
196 352
197 } // namespace rtc 353 } // namespace rtc
OLDNEW
« no previous file with comments | « webrtc/base/task_queue_unittest.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698