OLD | NEW |
---|---|
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); |
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, is much lower, or 16. | |
the sun
2017/02/14 10:29:19
Is the limit per process or across the system?
tommi
2017/02/14 10:43:07
per process, comment updated.
| |
62 // So there isn't much value in going to the lenghts required to overcome the | |
63 // Wait limitations. | |
64 // kMaxTimers larger than 16 though since it is possible that 'complete' or | |
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() { | |
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); | |
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, | |
the sun
2017/02/14 10:29:19
MSDN says timeSetEvent() is obsolete (since XP/Ser
tommi
2017/02/14 10:43:07
Yes. I looked into the various options and measure
| |
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 Loading... | |
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); |
138 RTC_CHECK_NE(WAIT_FAILED, result); | 244 RTC_CHECK_NE(WAIT_FAILED, result); |
139 if (result == WAIT_OBJECT_0) { | 245 if (result == count) { |
140 if (!ProcessQueuedMessages(&delayed_tasks)) | 246 if (!ProcessQueuedMessages(&delayed_tasks, &mm_timers)) |
141 break; | 247 break; |
248 } else if (result < count) { | |
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) { | |
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(); | |
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) { | |
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 |
OLD | NEW |