xpcom/threads/TimerThread.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "nsTimerImpl.h"
michael@0 7 #include "TimerThread.h"
michael@0 8
michael@0 9 #include "nsThreadUtils.h"
michael@0 10 #include "pratom.h"
michael@0 11
michael@0 12 #include "nsIObserverService.h"
michael@0 13 #include "nsIServiceManager.h"
michael@0 14 #include "mozilla/Services.h"
michael@0 15 #include "mozilla/ChaosMode.h"
michael@0 16 #include "mozilla/ArrayUtils.h"
michael@0 17
michael@0 18 #include <math.h>
michael@0 19
michael@0 20 using namespace mozilla;
michael@0 21
michael@0 22 NS_IMPL_ISUPPORTS(TimerThread, nsIRunnable, nsIObserver)
michael@0 23
michael@0 24 TimerThread::TimerThread() :
michael@0 25 mInitInProgress(false),
michael@0 26 mInitialized(false),
michael@0 27 mMonitor("TimerThread.mMonitor"),
michael@0 28 mShutdown(false),
michael@0 29 mWaiting(false),
michael@0 30 mNotified(false),
michael@0 31 mSleeping(false)
michael@0 32 {
michael@0 33 }
michael@0 34
michael@0 35 TimerThread::~TimerThread()
michael@0 36 {
michael@0 37 mThread = nullptr;
michael@0 38
michael@0 39 NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread");
michael@0 40 }
michael@0 41
michael@0 42 nsresult
michael@0 43 TimerThread::InitLocks()
michael@0 44 {
michael@0 45 return NS_OK;
michael@0 46 }
michael@0 47
michael@0 48 namespace {
michael@0 49
michael@0 50 class TimerObserverRunnable : public nsRunnable
michael@0 51 {
michael@0 52 public:
michael@0 53 TimerObserverRunnable(nsIObserver* observer)
michael@0 54 : mObserver(observer)
michael@0 55 { }
michael@0 56
michael@0 57 NS_DECL_NSIRUNNABLE
michael@0 58
michael@0 59 private:
michael@0 60 nsCOMPtr<nsIObserver> mObserver;
michael@0 61 };
michael@0 62
michael@0 63 NS_IMETHODIMP
michael@0 64 TimerObserverRunnable::Run()
michael@0 65 {
michael@0 66 nsCOMPtr<nsIObserverService> observerService =
michael@0 67 mozilla::services::GetObserverService();
michael@0 68 if (observerService) {
michael@0 69 observerService->AddObserver(mObserver, "sleep_notification", false);
michael@0 70 observerService->AddObserver(mObserver, "wake_notification", false);
michael@0 71 observerService->AddObserver(mObserver, "suspend_process_notification", false);
michael@0 72 observerService->AddObserver(mObserver, "resume_process_notification", false);
michael@0 73 }
michael@0 74 return NS_OK;
michael@0 75 }
michael@0 76
michael@0 77 } // anonymous namespace
michael@0 78
michael@0 79 nsresult TimerThread::Init()
michael@0 80 {
michael@0 81 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Init [%d]\n", mInitialized));
michael@0 82
michael@0 83 if (mInitialized) {
michael@0 84 if (!mThread)
michael@0 85 return NS_ERROR_FAILURE;
michael@0 86
michael@0 87 return NS_OK;
michael@0 88 }
michael@0 89
michael@0 90 if (mInitInProgress.exchange(true) == false) {
michael@0 91 // We hold on to mThread to keep the thread alive.
michael@0 92 nsresult rv = NS_NewThread(getter_AddRefs(mThread), this);
michael@0 93 if (NS_FAILED(rv)) {
michael@0 94 mThread = nullptr;
michael@0 95 }
michael@0 96 else {
michael@0 97 nsRefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this);
michael@0 98 if (NS_IsMainThread()) {
michael@0 99 r->Run();
michael@0 100 }
michael@0 101 else {
michael@0 102 NS_DispatchToMainThread(r);
michael@0 103 }
michael@0 104 }
michael@0 105
michael@0 106 {
michael@0 107 MonitorAutoLock lock(mMonitor);
michael@0 108 mInitialized = true;
michael@0 109 mMonitor.NotifyAll();
michael@0 110 }
michael@0 111 }
michael@0 112 else {
michael@0 113 MonitorAutoLock lock(mMonitor);
michael@0 114 while (!mInitialized) {
michael@0 115 mMonitor.Wait();
michael@0 116 }
michael@0 117 }
michael@0 118
michael@0 119 if (!mThread)
michael@0 120 return NS_ERROR_FAILURE;
michael@0 121
michael@0 122 return NS_OK;
michael@0 123 }
michael@0 124
michael@0 125 nsresult TimerThread::Shutdown()
michael@0 126 {
michael@0 127 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown begin\n"));
michael@0 128
michael@0 129 if (!mThread)
michael@0 130 return NS_ERROR_NOT_INITIALIZED;
michael@0 131
michael@0 132 nsTArray<nsTimerImpl*> timers;
michael@0 133 { // lock scope
michael@0 134 MonitorAutoLock lock(mMonitor);
michael@0 135
michael@0 136 mShutdown = true;
michael@0 137
michael@0 138 // notify the cond var so that Run() can return
michael@0 139 if (mWaiting) {
michael@0 140 mNotified = true;
michael@0 141 mMonitor.Notify();
michael@0 142 }
michael@0 143
michael@0 144 // Need to copy content of mTimers array to a local array
michael@0 145 // because call to timers' ReleaseCallback() (and release its self)
michael@0 146 // must not be done under the lock. Destructor of a callback
michael@0 147 // might potentially call some code reentering the same lock
michael@0 148 // that leads to unexpected behavior or deadlock.
michael@0 149 // See bug 422472.
michael@0 150 timers.AppendElements(mTimers);
michael@0 151 mTimers.Clear();
michael@0 152 }
michael@0 153
michael@0 154 uint32_t timersCount = timers.Length();
michael@0 155 for (uint32_t i = 0; i < timersCount; i++) {
michael@0 156 nsTimerImpl *timer = timers[i];
michael@0 157 timer->ReleaseCallback();
michael@0 158 ReleaseTimerInternal(timer);
michael@0 159 }
michael@0 160
michael@0 161 mThread->Shutdown(); // wait for the thread to die
michael@0 162
michael@0 163 PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown end\n"));
michael@0 164 return NS_OK;
michael@0 165 }
michael@0 166
michael@0 167 #ifdef MOZ_NUWA_PROCESS
michael@0 168 #include "ipc/Nuwa.h"
michael@0 169 #endif
michael@0 170
michael@0 171 /* void Run(); */
michael@0 172 NS_IMETHODIMP TimerThread::Run()
michael@0 173 {
michael@0 174 PR_SetCurrentThreadName("Timer");
michael@0 175
michael@0 176 #ifdef MOZ_NUWA_PROCESS
michael@0 177 if (IsNuwaProcess()) {
michael@0 178 NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
michael@0 179 "NuwaMarkCurrentThread is undefined!");
michael@0 180 NuwaMarkCurrentThread(nullptr, nullptr);
michael@0 181 }
michael@0 182 #endif
michael@0 183
michael@0 184 MonitorAutoLock lock(mMonitor);
michael@0 185
michael@0 186 // We need to know how many microseconds give a positive PRIntervalTime. This
michael@0 187 // is platform-dependent, we calculate it at runtime now.
michael@0 188 // First we find a value such that PR_MicrosecondsToInterval(high) = 1
michael@0 189 int32_t low = 0, high = 1;
michael@0 190 while (PR_MicrosecondsToInterval(high) == 0)
michael@0 191 high <<= 1;
michael@0 192 // We now have
michael@0 193 // PR_MicrosecondsToInterval(low) = 0
michael@0 194 // PR_MicrosecondsToInterval(high) = 1
michael@0 195 // and we can proceed to find the critical value using binary search
michael@0 196 while (high-low > 1) {
michael@0 197 int32_t mid = (high+low) >> 1;
michael@0 198 if (PR_MicrosecondsToInterval(mid) == 0)
michael@0 199 low = mid;
michael@0 200 else
michael@0 201 high = mid;
michael@0 202 }
michael@0 203
michael@0 204 // Half of the amount of microseconds needed to get positive PRIntervalTime.
michael@0 205 // We use this to decide how to round our wait times later
michael@0 206 int32_t halfMicrosecondsIntervalResolution = high >> 1;
michael@0 207 bool forceRunNextTimer = false;
michael@0 208
michael@0 209 while (!mShutdown) {
michael@0 210 // Have to use PRIntervalTime here, since PR_WaitCondVar takes it
michael@0 211 PRIntervalTime waitFor;
michael@0 212 bool forceRunThisTimer = forceRunNextTimer;
michael@0 213 forceRunNextTimer = false;
michael@0 214
michael@0 215 if (mSleeping) {
michael@0 216 // Sleep for 0.1 seconds while not firing timers.
michael@0 217 uint32_t milliseconds = 100;
michael@0 218 if (ChaosMode::isActive()) {
michael@0 219 milliseconds = ChaosMode::randomUint32LessThan(200);
michael@0 220 }
michael@0 221 waitFor = PR_MillisecondsToInterval(milliseconds);
michael@0 222 } else {
michael@0 223 waitFor = PR_INTERVAL_NO_TIMEOUT;
michael@0 224 TimeStamp now = TimeStamp::Now();
michael@0 225 nsTimerImpl *timer = nullptr;
michael@0 226
michael@0 227 if (!mTimers.IsEmpty()) {
michael@0 228 timer = mTimers[0];
michael@0 229
michael@0 230 if (now >= timer->mTimeout || forceRunThisTimer) {
michael@0 231 next:
michael@0 232 // NB: AddRef before the Release under RemoveTimerInternal to avoid
michael@0 233 // mRefCnt passing through zero, in case all other refs than the one
michael@0 234 // from mTimers have gone away (the last non-mTimers[i]-ref's Release
michael@0 235 // must be racing with us, blocked in gThread->RemoveTimer waiting
michael@0 236 // for TimerThread::mMonitor, under nsTimerImpl::Release.
michael@0 237
michael@0 238 nsRefPtr<nsTimerImpl> timerRef(timer);
michael@0 239 RemoveTimerInternal(timer);
michael@0 240 timer = nullptr;
michael@0 241
michael@0 242 {
michael@0 243 // We release mMonitor around the Fire call to avoid deadlock.
michael@0 244 MonitorAutoUnlock unlock(mMonitor);
michael@0 245
michael@0 246 #ifdef DEBUG_TIMERS
michael@0 247 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) {
michael@0 248 PR_LOG(GetTimerLog(), PR_LOG_DEBUG,
michael@0 249 ("Timer thread woke up %fms from when it was supposed to\n",
michael@0 250 fabs((now - timerRef->mTimeout).ToMilliseconds())));
michael@0 251 }
michael@0 252 #endif
michael@0 253
michael@0 254 // We are going to let the call to PostTimerEvent here handle the
michael@0 255 // release of the timer so that we don't end up releasing the timer
michael@0 256 // on the TimerThread instead of on the thread it targets.
michael@0 257 timerRef = nsTimerImpl::PostTimerEvent(timerRef.forget());
michael@0 258
michael@0 259 if (timerRef) {
michael@0 260 // We got our reference back due to an error.
michael@0 261 // Unhook the nsRefPtr, and release manually so we can get the
michael@0 262 // refcount.
michael@0 263 nsrefcnt rc = timerRef.forget().take()->Release();
michael@0 264 (void)rc;
michael@0 265
michael@0 266 // The nsITimer interface requires that its users keep a reference
michael@0 267 // to the timers they use while those timers are initialized but
michael@0 268 // have not yet fired. If this ever happens, it is a bug in the
michael@0 269 // code that created and used the timer.
michael@0 270 //
michael@0 271 // Further, note that this should never happen even with a
michael@0 272 // misbehaving user, because nsTimerImpl::Release checks for a
michael@0 273 // refcount of 1 with an armed timer (a timer whose only reference
michael@0 274 // is from the timer thread) and when it hits this will remove the
michael@0 275 // timer from the timer thread and thus destroy the last reference,
michael@0 276 // preventing this situation from occurring.
michael@0 277 MOZ_ASSERT(rc != 0, "destroyed timer off its target thread!");
michael@0 278 }
michael@0 279 }
michael@0 280
michael@0 281 if (mShutdown)
michael@0 282 break;
michael@0 283
michael@0 284 // Update now, as PostTimerEvent plus the locking may have taken a
michael@0 285 // tick or two, and we may goto next below.
michael@0 286 now = TimeStamp::Now();
michael@0 287 }
michael@0 288 }
michael@0 289
michael@0 290 if (!mTimers.IsEmpty()) {
michael@0 291 timer = mTimers[0];
michael@0 292
michael@0 293 TimeStamp timeout = timer->mTimeout;
michael@0 294
michael@0 295 // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
michael@0 296 // is due now or overdue.
michael@0 297 //
michael@0 298 // Note that we can only sleep for integer values of a certain
michael@0 299 // resolution. We use halfMicrosecondsIntervalResolution, calculated
michael@0 300 // before, to do the optimal rounding (i.e., of how to decide what
michael@0 301 // interval is so small we should not wait at all).
michael@0 302 double microseconds = (timeout - now).ToMilliseconds()*1000;
michael@0 303
michael@0 304 if (ChaosMode::isActive()) {
michael@0 305 // The mean value of sFractions must be 1 to ensure that
michael@0 306 // the average of a long sequence of timeouts converges to the
michael@0 307 // actual sum of their times.
michael@0 308 static const float sFractions[] = {
michael@0 309 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.75f, 2.75f
michael@0 310 };
michael@0 311 microseconds *= sFractions[ChaosMode::randomUint32LessThan(ArrayLength(sFractions))];
michael@0 312 forceRunNextTimer = true;
michael@0 313 }
michael@0 314
michael@0 315 if (microseconds < halfMicrosecondsIntervalResolution) {
michael@0 316 forceRunNextTimer = false;
michael@0 317 goto next; // round down; execute event now
michael@0 318 }
michael@0 319 waitFor = PR_MicrosecondsToInterval(static_cast<uint32_t>(microseconds)); // Floor is accurate enough.
michael@0 320 if (waitFor == 0)
michael@0 321 waitFor = 1; // round up, wait the minimum time we can wait
michael@0 322 }
michael@0 323
michael@0 324 #ifdef DEBUG_TIMERS
michael@0 325 if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) {
michael@0 326 if (waitFor == PR_INTERVAL_NO_TIMEOUT)
michael@0 327 PR_LOG(GetTimerLog(), PR_LOG_DEBUG,
michael@0 328 ("waiting for PR_INTERVAL_NO_TIMEOUT\n"));
michael@0 329 else
michael@0 330 PR_LOG(GetTimerLog(), PR_LOG_DEBUG,
michael@0 331 ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor)));
michael@0 332 }
michael@0 333 #endif
michael@0 334 }
michael@0 335
michael@0 336 mWaiting = true;
michael@0 337 mNotified = false;
michael@0 338 mMonitor.Wait(waitFor);
michael@0 339 if (mNotified) {
michael@0 340 forceRunNextTimer = false;
michael@0 341 }
michael@0 342 mWaiting = false;
michael@0 343 }
michael@0 344
michael@0 345 return NS_OK;
michael@0 346 }
michael@0 347
michael@0 348 nsresult TimerThread::AddTimer(nsTimerImpl *aTimer)
michael@0 349 {
michael@0 350 MonitorAutoLock lock(mMonitor);
michael@0 351
michael@0 352 // Add the timer to our list.
michael@0 353 int32_t i = AddTimerInternal(aTimer);
michael@0 354 if (i < 0)
michael@0 355 return NS_ERROR_OUT_OF_MEMORY;
michael@0 356
michael@0 357 // Awaken the timer thread.
michael@0 358 if (mWaiting && i == 0) {
michael@0 359 mNotified = true;
michael@0 360 mMonitor.Notify();
michael@0 361 }
michael@0 362
michael@0 363 return NS_OK;
michael@0 364 }
michael@0 365
michael@0 366 nsresult TimerThread::TimerDelayChanged(nsTimerImpl *aTimer)
michael@0 367 {
michael@0 368 MonitorAutoLock lock(mMonitor);
michael@0 369
michael@0 370 // Our caller has a strong ref to aTimer, so it can't go away here under
michael@0 371 // ReleaseTimerInternal.
michael@0 372 RemoveTimerInternal(aTimer);
michael@0 373
michael@0 374 int32_t i = AddTimerInternal(aTimer);
michael@0 375 if (i < 0)
michael@0 376 return NS_ERROR_OUT_OF_MEMORY;
michael@0 377
michael@0 378 // Awaken the timer thread.
michael@0 379 if (mWaiting && i == 0) {
michael@0 380 mNotified = true;
michael@0 381 mMonitor.Notify();
michael@0 382 }
michael@0 383
michael@0 384 return NS_OK;
michael@0 385 }
michael@0 386
michael@0 387 nsresult TimerThread::RemoveTimer(nsTimerImpl *aTimer)
michael@0 388 {
michael@0 389 MonitorAutoLock lock(mMonitor);
michael@0 390
michael@0 391 // Remove the timer from our array. Tell callers that aTimer was not found
michael@0 392 // by returning NS_ERROR_NOT_AVAILABLE. Unlike the TimerDelayChanged case
michael@0 393 // immediately above, our caller may be passing a (now-)weak ref in via the
michael@0 394 // aTimer param, specifically when nsTimerImpl::Release loses a race with
michael@0 395 // TimerThread::Run, must wait for the mMonitor auto-lock here, and during the
michael@0 396 // wait Run drops the only remaining ref to aTimer via RemoveTimerInternal.
michael@0 397
michael@0 398 if (!RemoveTimerInternal(aTimer))
michael@0 399 return NS_ERROR_NOT_AVAILABLE;
michael@0 400
michael@0 401 // Awaken the timer thread.
michael@0 402 if (mWaiting) {
michael@0 403 mNotified = true;
michael@0 404 mMonitor.Notify();
michael@0 405 }
michael@0 406
michael@0 407 return NS_OK;
michael@0 408 }
michael@0 409
michael@0 410 // This function must be called from within a lock
michael@0 411 int32_t TimerThread::AddTimerInternal(nsTimerImpl *aTimer)
michael@0 412 {
michael@0 413 if (mShutdown)
michael@0 414 return -1;
michael@0 415
michael@0 416 TimeStamp now = TimeStamp::Now();
michael@0 417
michael@0 418 TimerAdditionComparator c(now, aTimer);
michael@0 419 nsTimerImpl** insertSlot = mTimers.InsertElementSorted(aTimer, c);
michael@0 420
michael@0 421 if (!insertSlot)
michael@0 422 return -1;
michael@0 423
michael@0 424 aTimer->mArmed = true;
michael@0 425 NS_ADDREF(aTimer);
michael@0 426
michael@0 427 #ifdef MOZ_TASK_TRACER
michael@0 428 aTimer->DispatchTracedTask();
michael@0 429 #endif
michael@0 430
michael@0 431 return insertSlot - mTimers.Elements();
michael@0 432 }
michael@0 433
michael@0 434 bool TimerThread::RemoveTimerInternal(nsTimerImpl *aTimer)
michael@0 435 {
michael@0 436 if (!mTimers.RemoveElement(aTimer))
michael@0 437 return false;
michael@0 438
michael@0 439 ReleaseTimerInternal(aTimer);
michael@0 440 return true;
michael@0 441 }
michael@0 442
michael@0 443 void TimerThread::ReleaseTimerInternal(nsTimerImpl *aTimer)
michael@0 444 {
michael@0 445 // Order is crucial here -- see nsTimerImpl::Release.
michael@0 446 aTimer->mArmed = false;
michael@0 447 NS_RELEASE(aTimer);
michael@0 448 }
michael@0 449
michael@0 450 void TimerThread::DoBeforeSleep()
michael@0 451 {
michael@0 452 mSleeping = true;
michael@0 453 }
michael@0 454
michael@0 455 void TimerThread::DoAfterSleep()
michael@0 456 {
michael@0 457 mSleeping = true; // wake may be notified without preceding sleep notification
michael@0 458 for (uint32_t i = 0; i < mTimers.Length(); i ++) {
michael@0 459 nsTimerImpl *timer = mTimers[i];
michael@0 460 // get and set the delay to cause its timeout to be recomputed
michael@0 461 uint32_t delay;
michael@0 462 timer->GetDelay(&delay);
michael@0 463 timer->SetDelay(delay);
michael@0 464 }
michael@0 465
michael@0 466 mSleeping = false;
michael@0 467 }
michael@0 468
michael@0 469
michael@0 470 /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */
michael@0 471 NS_IMETHODIMP
michael@0 472 TimerThread::Observe(nsISupports* /* aSubject */, const char *aTopic, const char16_t* /* aData */)
michael@0 473 {
michael@0 474 if (strcmp(aTopic, "sleep_notification") == 0 ||
michael@0 475 strcmp(aTopic, "suspend_process_notification") == 0)
michael@0 476 DoBeforeSleep();
michael@0 477 else if (strcmp(aTopic, "wake_notification") == 0 ||
michael@0 478 strcmp(aTopic, "resume_process_notification") == 0)
michael@0 479 DoAfterSleep();
michael@0 480
michael@0 481 return NS_OK;
michael@0 482 }

mercurial