tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2016 The WebRTC Project Authors. All rights reserved. |
| 3 | * |
| 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 |
| 6 | * tree. An additional intellectual property rights grant can be found |
| 7 | * in the file PATENTS. All contributing project authors may |
| 8 | * be found in the AUTHORS file in the root of the source tree. |
| 9 | */ |
| 10 | |
| 11 | #include "webrtc/base/task_queue.h" |
| 12 | |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 13 | #include <mmsystem.h> |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 14 | #include <string.h> |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 15 | |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 16 | #include <algorithm> |
| 17 | |
| 18 | #include "webrtc/base/arraysize.h" |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 19 | #include "webrtc/base/checks.h" |
| 20 | #include "webrtc/base/logging.h" |
| 21 | |
| 22 | namespace rtc { |
| 23 | namespace { |
| 24 | #define WM_RUN_TASK WM_USER + 1 |
| 25 | #define WM_QUEUE_DELAYED_TASK WM_USER + 2 |
| 26 | |
tommi | c9bb791 | 2017-02-24 10:42:14 -0800 | [diff] [blame] | 27 | using Priority = TaskQueue::Priority; |
| 28 | |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 29 | DWORD g_queue_ptr_tls = 0; |
| 30 | |
| 31 | BOOL CALLBACK InitializeTls(PINIT_ONCE init_once, void* param, void** context) { |
| 32 | g_queue_ptr_tls = TlsAlloc(); |
| 33 | return TRUE; |
| 34 | } |
| 35 | |
| 36 | DWORD GetQueuePtrTls() { |
| 37 | static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT; |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 38 | ::InitOnceExecuteOnce(&init_once, InitializeTls, nullptr, nullptr); |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 39 | return g_queue_ptr_tls; |
| 40 | } |
| 41 | |
| 42 | struct ThreadStartupData { |
| 43 | Event* started; |
| 44 | void* thread_context; |
| 45 | }; |
| 46 | |
| 47 | void CALLBACK InitializeQueueThread(ULONG_PTR param) { |
| 48 | MSG msg; |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 49 | ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 50 | ThreadStartupData* data = reinterpret_cast<ThreadStartupData*>(param); |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 51 | ::TlsSetValue(GetQueuePtrTls(), data->thread_context); |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 52 | data->started->Set(); |
| 53 | } |
tommi | c9bb791 | 2017-02-24 10:42:14 -0800 | [diff] [blame] | 54 | |
| 55 | ThreadPriority TaskQueuePriorityToThreadPriority(Priority priority) { |
| 56 | switch (priority) { |
| 57 | case Priority::HIGH: |
| 58 | return kRealtimePriority; |
| 59 | case Priority::LOW: |
| 60 | return kLowPriority; |
| 61 | case Priority::NORMAL: |
| 62 | return kNormalPriority; |
| 63 | default: |
| 64 | RTC_NOTREACHED(); |
| 65 | break; |
| 66 | } |
| 67 | return kNormalPriority; |
| 68 | } |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 69 | } // namespace |
| 70 | |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 71 | class TaskQueue::MultimediaTimer { |
| 72 | public: |
| 73 | // kMaxTimers defines the limit of how many MultimediaTimer instances should |
| 74 | // be created. |
| 75 | // Background: The maximum number of supported handles for Wait functions, is |
| 76 | // MAXIMUM_WAIT_OBJECTS - 1 (63). |
| 77 | // There are some ways to work around the limitation but as it turns out, the |
| 78 | // limit of concurrently active multimedia timers per process, is much lower, |
| 79 | // or 16. So there isn't much value in going to the lenghts required to |
| 80 | // overcome the Wait limitations. |
| 81 | // kMaxTimers is larger than 16 though since it is possible that 'complete' or |
| 82 | // signaled timers that haven't been handled, are counted as part of |
| 83 | // kMaxTimers and thus a multimedia timer can actually be queued even though |
| 84 | // as far as we're concerned, there are more than 16 that are pending. |
| 85 | static const int kMaxTimers = MAXIMUM_WAIT_OBJECTS - 1; |
| 86 | |
| 87 | // Controls how many MultimediaTimer instances a queue can hold before |
| 88 | // attempting to garbage collect (GC) timers that aren't in use. |
| 89 | static const int kInstanceThresholdGC = 8; |
| 90 | |
| 91 | MultimediaTimer() : event_(::CreateEvent(nullptr, false, false, nullptr)) {} |
| 92 | |
| 93 | MultimediaTimer(MultimediaTimer&& timer) |
| 94 | : event_(timer.event_), |
| 95 | timer_id_(timer.timer_id_), |
| 96 | task_(std::move(timer.task_)) { |
| 97 | RTC_DCHECK(event_); |
| 98 | timer.event_ = nullptr; |
| 99 | timer.timer_id_ = 0; |
| 100 | } |
| 101 | |
| 102 | ~MultimediaTimer() { Close(); } |
| 103 | |
| 104 | // Implementing this operator is required because of the way |
| 105 | // some stl algorithms work, such as std::rotate(). |
| 106 | MultimediaTimer& operator=(MultimediaTimer&& timer) { |
| 107 | if (this != &timer) { |
| 108 | Close(); |
| 109 | event_ = timer.event_; |
| 110 | timer.event_ = nullptr; |
| 111 | task_ = std::move(timer.task_); |
| 112 | timer_id_ = timer.timer_id_; |
| 113 | timer.timer_id_ = 0; |
| 114 | } |
| 115 | return *this; |
| 116 | } |
| 117 | |
| 118 | bool StartOneShotTimer(std::unique_ptr<QueuedTask> task, UINT delay_ms) { |
| 119 | RTC_DCHECK_EQ(0, timer_id_); |
| 120 | RTC_DCHECK(event_ != nullptr); |
| 121 | RTC_DCHECK(!task_.get()); |
| 122 | RTC_DCHECK(task.get()); |
| 123 | task_ = std::move(task); |
| 124 | timer_id_ = |
| 125 | ::timeSetEvent(delay_ms, 0, reinterpret_cast<LPTIMECALLBACK>(event_), 0, |
| 126 | TIME_ONESHOT | TIME_CALLBACK_EVENT_SET); |
| 127 | return timer_id_ != 0; |
| 128 | } |
| 129 | |
| 130 | std::unique_ptr<QueuedTask> Cancel() { |
| 131 | if (timer_id_) { |
| 132 | ::timeKillEvent(timer_id_); |
| 133 | timer_id_ = 0; |
| 134 | } |
| 135 | return std::move(task_); |
| 136 | } |
| 137 | |
| 138 | void OnEventSignaled() { |
| 139 | RTC_DCHECK_NE(0, timer_id_); |
| 140 | timer_id_ = 0; |
| 141 | task_->Run() ? task_.reset() : static_cast<void>(task_.release()); |
| 142 | } |
| 143 | |
| 144 | HANDLE event() const { return event_; } |
| 145 | |
| 146 | bool is_active() const { return timer_id_ != 0; } |
| 147 | |
| 148 | private: |
| 149 | void Close() { |
| 150 | Cancel(); |
| 151 | |
| 152 | if (event_) { |
| 153 | ::CloseHandle(event_); |
| 154 | event_ = nullptr; |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | HANDLE event_ = nullptr; |
| 159 | MMRESULT timer_id_ = 0; |
| 160 | std::unique_ptr<QueuedTask> task_; |
| 161 | |
| 162 | RTC_DISALLOW_COPY_AND_ASSIGN(MultimediaTimer); |
| 163 | }; |
| 164 | |
tommi | c9bb791 | 2017-02-24 10:42:14 -0800 | [diff] [blame] | 165 | TaskQueue::TaskQueue(const char* queue_name, Priority priority /*= NORMAL*/) |
| 166 | : thread_(&TaskQueue::ThreadMain, |
| 167 | this, |
| 168 | queue_name, |
| 169 | TaskQueuePriorityToThreadPriority(priority)) { |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 170 | RTC_DCHECK(queue_name); |
| 171 | thread_.Start(); |
| 172 | Event event(false, false); |
| 173 | ThreadStartupData startup = {&event, this}; |
| 174 | RTC_CHECK(thread_.QueueAPC(&InitializeQueueThread, |
| 175 | reinterpret_cast<ULONG_PTR>(&startup))); |
| 176 | event.Wait(Event::kForever); |
| 177 | } |
| 178 | |
| 179 | TaskQueue::~TaskQueue() { |
| 180 | RTC_DCHECK(!IsCurrent()); |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 181 | while (!::PostThreadMessage(thread_.GetThreadRef(), WM_QUIT, 0, 0)) { |
kwiberg | 352444f | 2016-11-28 15:58:53 -0800 | [diff] [blame] | 182 | RTC_CHECK_EQ(ERROR_NOT_ENOUGH_QUOTA, ::GetLastError()); |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 183 | Sleep(1); |
| 184 | } |
| 185 | thread_.Stop(); |
| 186 | } |
| 187 | |
| 188 | // static |
| 189 | TaskQueue* TaskQueue::Current() { |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 190 | return static_cast<TaskQueue*>(::TlsGetValue(GetQueuePtrTls())); |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 191 | } |
| 192 | |
| 193 | // static |
| 194 | bool TaskQueue::IsCurrent(const char* queue_name) { |
| 195 | TaskQueue* current = Current(); |
| 196 | return current && current->thread_.name().compare(queue_name) == 0; |
| 197 | } |
| 198 | |
| 199 | bool TaskQueue::IsCurrent() const { |
| 200 | return IsThreadRefEqual(thread_.GetThreadRef(), CurrentThreadRef()); |
| 201 | } |
| 202 | |
| 203 | void TaskQueue::PostTask(std::unique_ptr<QueuedTask> task) { |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 204 | if (::PostThreadMessage(thread_.GetThreadRef(), WM_RUN_TASK, 0, |
| 205 | reinterpret_cast<LPARAM>(task.get()))) { |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 206 | task.release(); |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | void TaskQueue::PostDelayedTask(std::unique_ptr<QueuedTask> task, |
| 211 | uint32_t milliseconds) { |
| 212 | WPARAM wparam; |
| 213 | #if defined(_WIN64) |
| 214 | // GetTickCount() returns a fairly coarse tick count (resolution or about 8ms) |
| 215 | // so this compensation isn't that accurate, but since we have unused 32 bits |
| 216 | // on Win64, we might as well use them. |
| 217 | wparam = (static_cast<WPARAM>(::GetTickCount()) << 32) | milliseconds; |
| 218 | #else |
| 219 | wparam = milliseconds; |
| 220 | #endif |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 221 | if (::PostThreadMessage(thread_.GetThreadRef(), WM_QUEUE_DELAYED_TASK, wparam, |
| 222 | reinterpret_cast<LPARAM>(task.get()))) { |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 223 | task.release(); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | void TaskQueue::PostTaskAndReply(std::unique_ptr<QueuedTask> task, |
| 228 | std::unique_ptr<QueuedTask> reply, |
| 229 | TaskQueue* reply_queue) { |
| 230 | QueuedTask* task_ptr = task.release(); |
| 231 | QueuedTask* reply_task_ptr = reply.release(); |
| 232 | DWORD reply_thread_id = reply_queue->thread_.GetThreadRef(); |
| 233 | PostTask([task_ptr, reply_task_ptr, reply_thread_id]() { |
| 234 | if (task_ptr->Run()) |
| 235 | delete task_ptr; |
| 236 | // If the thread's message queue is full, we can't queue the task and will |
| 237 | // have to drop it (i.e. delete). |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 238 | if (!::PostThreadMessage(reply_thread_id, WM_RUN_TASK, 0, |
| 239 | reinterpret_cast<LPARAM>(reply_task_ptr))) { |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 240 | delete reply_task_ptr; |
| 241 | } |
| 242 | }); |
| 243 | } |
| 244 | |
| 245 | void TaskQueue::PostTaskAndReply(std::unique_ptr<QueuedTask> task, |
| 246 | std::unique_ptr<QueuedTask> reply) { |
| 247 | return PostTaskAndReply(std::move(task), std::move(reply), Current()); |
| 248 | } |
| 249 | |
| 250 | // static |
tommi | 0f8b403 | 2017-02-22 11:22:05 -0800 | [diff] [blame] | 251 | void TaskQueue::ThreadMain(void* context) { |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 252 | HANDLE timer_handles[MultimediaTimer::kMaxTimers]; |
| 253 | // Active multimedia timers. |
| 254 | std::vector<MultimediaTimer> mm_timers; |
| 255 | // Tasks that have been queued by using SetTimer/WM_TIMER. |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 256 | DelayedTasks delayed_tasks; |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 257 | |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 258 | while (true) { |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 259 | RTC_DCHECK(mm_timers.size() <= arraysize(timer_handles)); |
| 260 | DWORD count = 0; |
| 261 | for (const auto& t : mm_timers) { |
| 262 | if (!t.is_active()) |
| 263 | break; |
| 264 | timer_handles[count++] = t.event(); |
| 265 | } |
| 266 | // Make sure we do an alertable wait as that's required to allow APCs to run |
| 267 | // (e.g. required for InitializeQueueThread and stopping the thread in |
| 268 | // PlatformThread). |
| 269 | DWORD result = ::MsgWaitForMultipleObjectsEx(count, timer_handles, INFINITE, |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 270 | QS_ALLEVENTS, MWMO_ALERTABLE); |
| 271 | RTC_CHECK_NE(WAIT_FAILED, result); |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 272 | // If we're not waiting for any timers, then count will be equal to |
| 273 | // WAIT_OBJECT_0. If we're waiting for timers, then |count| represents |
| 274 | // "One more than the number of timers", which means that there's a |
| 275 | // message in the queue that needs to be handled. |
| 276 | // If |result| is less than |count|, then its value will be the index of the |
| 277 | // timer that has been signaled. |
| 278 | if (result == (WAIT_OBJECT_0 + count)) { |
| 279 | if (!ProcessQueuedMessages(&delayed_tasks, &mm_timers)) |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 280 | break; |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 281 | } else if (result < (WAIT_OBJECT_0 + count)) { |
| 282 | mm_timers[result].OnEventSignaled(); |
| 283 | RTC_DCHECK(!mm_timers[result].is_active()); |
| 284 | // Reuse timer events by moving inactive timers to the back of the vector. |
| 285 | // When new delayed tasks are queued, they'll get reused. |
| 286 | if (mm_timers.size() > 1) { |
| 287 | auto it = mm_timers.begin() + result; |
| 288 | std::rotate(it, it + 1, mm_timers.end()); |
| 289 | } |
| 290 | |
| 291 | // Collect some garbage. |
| 292 | if (mm_timers.size() > MultimediaTimer::kInstanceThresholdGC) { |
| 293 | const auto inactive = std::find_if( |
| 294 | mm_timers.begin(), mm_timers.end(), |
| 295 | [](const MultimediaTimer& t) { return !t.is_active(); }); |
| 296 | if (inactive != mm_timers.end()) { |
| 297 | // Since inactive timers are always moved to the back, we can |
| 298 | // safely delete all timers following the first inactive one. |
| 299 | mm_timers.erase(inactive, mm_timers.end()); |
| 300 | } |
| 301 | } |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 302 | } else { |
| 303 | RTC_DCHECK_EQ(WAIT_IO_COMPLETION, result); |
| 304 | } |
| 305 | } |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 306 | } |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 307 | |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 308 | // static |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 309 | bool TaskQueue::ProcessQueuedMessages(DelayedTasks* delayed_tasks, |
| 310 | std::vector<MultimediaTimer>* timers) { |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 311 | MSG msg = {}; |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 312 | while (::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) && |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 313 | msg.message != WM_QUIT) { |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 314 | if (!msg.hwnd) { |
| 315 | switch (msg.message) { |
| 316 | case WM_RUN_TASK: { |
| 317 | QueuedTask* task = reinterpret_cast<QueuedTask*>(msg.lParam); |
| 318 | if (task->Run()) |
| 319 | delete task; |
| 320 | break; |
| 321 | } |
| 322 | case WM_QUEUE_DELAYED_TASK: { |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 323 | std::unique_ptr<QueuedTask> task( |
| 324 | reinterpret_cast<QueuedTask*>(msg.lParam)); |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 325 | uint32_t milliseconds = msg.wParam & 0xFFFFFFFF; |
| 326 | #if defined(_WIN64) |
| 327 | // Subtract the time it took to queue the timer. |
| 328 | const DWORD now = GetTickCount(); |
| 329 | DWORD post_time = now - (msg.wParam >> 32); |
| 330 | milliseconds = |
| 331 | post_time > milliseconds ? 0 : milliseconds - post_time; |
| 332 | #endif |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 333 | bool timer_queued = false; |
| 334 | if (timers->size() < MultimediaTimer::kMaxTimers) { |
| 335 | MultimediaTimer* timer = nullptr; |
| 336 | auto available = std::find_if( |
| 337 | timers->begin(), timers->end(), |
| 338 | [](const MultimediaTimer& t) { return !t.is_active(); }); |
| 339 | if (available != timers->end()) { |
| 340 | timer = &(*available); |
| 341 | } else { |
| 342 | timers->emplace_back(); |
| 343 | timer = &timers->back(); |
| 344 | } |
| 345 | |
| 346 | timer_queued = |
| 347 | timer->StartOneShotTimer(std::move(task), milliseconds); |
| 348 | if (!timer_queued) { |
| 349 | // No more multimedia timers can be queued. |
| 350 | // Detach the task and fall back on SetTimer. |
| 351 | task = timer->Cancel(); |
| 352 | } |
| 353 | } |
| 354 | |
| 355 | // When we fail to use multimedia timers, we fall back on the more |
| 356 | // coarse SetTimer/WM_TIMER approach. |
| 357 | if (!timer_queued) { |
| 358 | UINT_PTR timer_id = ::SetTimer(nullptr, 0, milliseconds, nullptr); |
| 359 | delayed_tasks->insert(std::make_pair(timer_id, task.release())); |
| 360 | } |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 361 | break; |
| 362 | } |
| 363 | case WM_TIMER: { |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 364 | ::KillTimer(nullptr, msg.wParam); |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 365 | auto found = delayed_tasks->find(msg.wParam); |
| 366 | RTC_DCHECK(found != delayed_tasks->end()); |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 367 | if (!found->second->Run()) |
| 368 | found->second.release(); |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 369 | delayed_tasks->erase(found); |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 370 | break; |
| 371 | } |
| 372 | default: |
| 373 | RTC_NOTREACHED(); |
| 374 | break; |
| 375 | } |
| 376 | } else { |
tommi | f9d9154 | 2017-02-17 02:47:11 -0800 | [diff] [blame] | 377 | ::TranslateMessage(&msg); |
| 378 | ::DispatchMessage(&msg); |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 379 | } |
| 380 | } |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 381 | return msg.message != WM_QUIT; |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 382 | } |
tommi | b89257a | 2016-07-12 01:24:36 -0700 | [diff] [blame] | 383 | |
tommi | c06b133 | 2016-05-14 11:31:40 -0700 | [diff] [blame] | 384 | } // namespace rtc |