1 /* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is worker threads.
17 * The Initial Developer of the Original Code is
18 * Mozilla Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2008
20 * the Initial Developer. All Rights Reserved.
23 * Ben Turner <bent.mozilla@gmail.com> (Original Author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsDOMWorkerTimeout.h"
42 #include "nsIJSContextStack.h"
43 #include "nsIJSRuntimeService.h"
45 #include "nsIXPConnect.h"
48 #include "nsContentUtils.h"
49 #include "nsJSUtils.h"
53 #include "nsDOMThreadService.h"
55 #define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
57 #define CONSTRUCTOR_ENSURE_TRUE(_cond, _rv) \
59 if (NS_UNLIKELY(!(_cond))) { \
60 NS_WARNING("CONSTRUCTOR_ENSURE_TRUE(" #_cond ") failed"); \
61 (_rv) = NS_ERROR_FAILURE; \
66 #define SUSPEND_SPINLOCK_COUNT 5000
68 static const char* kSetIntervalStr
= "setInterval";
69 static const char* kSetTimeoutStr
= "setTimeout";
71 nsDOMWorkerTimeout::FunctionCallback::FunctionCallback(PRUint32 aArgc
,
75 mCallbackArgs(nsnull
),
76 mCallbackArgsLength(0)
78 MOZ_COUNT_CTOR(nsDOMWorkerTimeout::FunctionCallback
);
81 *aRv
= nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt
);
82 NS_ENSURE_SUCCESS(*aRv
,);
84 PRBool success
= JS_AddNamedRootRT(rt
, &mCallback
,
85 "nsDOMWorkerTimeout Callback Object");
86 CONSTRUCTOR_ENSURE_TRUE(success
, *aRv
);
90 // We want enough space for an extra lateness arg.
91 mCallbackArgsLength
= aArgc
> 2 ? aArgc
- 1 : 1;
93 mCallbackArgs
= new jsval
[mCallbackArgsLength
];
94 if (NS_UNLIKELY(!mCallbackArgs
)) {
96 mCallbackArgsLength
= 0;
98 NS_ERROR("Out of memory!");
99 *aRv
= NS_ERROR_OUT_OF_MEMORY
;
103 for (PRUint32 i
= 0; i
< mCallbackArgsLength
- 1; i
++) {
104 mCallbackArgs
[i
] = aArgv
[i
+ 2];
105 success
= JS_AddNamedRootRT(rt
, &mCallbackArgs
[i
],
106 "nsDOMWorkerTimeout Callback Arg");
107 if (NS_UNLIKELY(!success
)) {
108 // Set this to i so that the destructor only unroots the right number of
110 mCallbackArgsLength
= i
;
112 NS_WARNING("Failed to add root!");
113 *aRv
= NS_ERROR_FAILURE
;
118 // Take care of the last arg.
119 mCallbackArgs
[mCallbackArgsLength
- 1] = 0;
120 success
= JS_AddNamedRootRT(rt
, &mCallbackArgs
[mCallbackArgsLength
- 1],
121 "nsDOMWorkerTimeout Callback Final Arg");
122 if (NS_UNLIKELY(!success
)) {
123 // Decrement this so that the destructor only unroots the right number of
125 mCallbackArgsLength
-= 1;
127 NS_WARNING("Failed to add root!");
128 *aRv
= NS_ERROR_FAILURE
;
135 nsDOMWorkerTimeout::FunctionCallback::~FunctionCallback()
137 MOZ_COUNT_DTOR(nsDOMWorkerTimeout::FunctionCallback
);
141 nsresult rv
= nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt
);
143 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv
), "Can't unroot callback objects!");
145 if (NS_SUCCEEDED(rv
)) {
146 for (PRUint32 i
= 0; i
< mCallbackArgsLength
; i
++) {
147 JS_RemoveRootRT(rt
, &mCallbackArgs
[i
]);
149 JS_RemoveRootRT(rt
, &mCallback
);
153 delete [] mCallbackArgs
;
157 nsDOMWorkerTimeout::FunctionCallback::Run(nsDOMWorkerTimeout
* aTimeout
,
160 PRInt32 lateness
= PR_MAX(0, PRInt32(PR_Now() - aTimeout
->mTargetTime
)) /
161 (PRTime
)PR_USEC_PER_MSEC
;
162 mCallbackArgs
[mCallbackArgsLength
- 1] = INT_TO_JSVAL(lateness
);
164 JSObject
* global
= JS_GetGlobalObject(aCx
);
165 NS_ENSURE_TRUE(global
, NS_ERROR_FAILURE
);
169 JS_CallFunctionValue(aCx
, global
, mCallback
, mCallbackArgsLength
,
170 mCallbackArgs
, &rval
);
171 NS_ENSURE_TRUE(success
, NS_ERROR_FAILURE
);
176 nsDOMWorkerTimeout::ExpressionCallback::ExpressionCallback(PRUint32 aArgc
,
180 : mExpression(nsnull
),
183 MOZ_COUNT_CTOR(nsDOMWorkerTimeout::ExpressionCallback
);
185 JSString
* expr
= JS_ValueToString(aCx
, aArgv
[0]);
186 *aRv
= expr
? NS_OK
: NS_ERROR_FAILURE
;
187 NS_ENSURE_SUCCESS(*aRv
,);
190 *aRv
= nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt
);
191 NS_ENSURE_SUCCESS(*aRv
,);
193 PRBool success
= JS_AddNamedRootRT(rt
, &mExpression
,
194 "nsDOMWorkerTimeout Expression");
195 CONSTRUCTOR_ENSURE_TRUE(success
, *aRv
);
199 // Get the calling location.
200 const char* fileName
;
202 if (nsJSUtils::GetCallingLocation(aCx
, &fileName
, &lineNumber
, nsnull
)) {
203 CopyUTF8toUTF16(nsDependentCString(fileName
), mFileName
);
204 mLineNumber
= lineNumber
;
210 nsDOMWorkerTimeout::ExpressionCallback::~ExpressionCallback()
212 MOZ_COUNT_DTOR(nsDOMWorkerTimeout::ExpressionCallback
);
216 nsresult rv
= nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt
);
218 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv
), "Can't unroot callback objects!");
220 if (NS_SUCCEEDED(rv
)) {
221 JS_RemoveRootRT(rt
, &mExpression
);
227 nsDOMWorkerTimeout::ExpressionCallback::Run(nsDOMWorkerTimeout
* aTimeout
,
230 NS_ERROR("Not yet implemented!");
231 return NS_ERROR_NOT_IMPLEMENTED
;
234 nsDOMWorkerTimeout::nsDOMWorkerTimeout(nsDOMWorkerThread
* aWorker
,
238 mIsInterval(PR_FALSE
),
241 mIsSuspended(PR_FALSE
),
244 , mFiredOrCanceled(PR_FALSE
)
247 MOZ_COUNT_CTOR(nsDOMWorkerTimeout
);
248 NS_ASSERTION(mWorker
, "Need a worker here!");
251 nsDOMWorkerTimeout::~nsDOMWorkerTimeout()
253 MOZ_COUNT_DTOR(nsDOMWorkerTimeout
);
255 // If we have a timer then we assume we added ourselves to the thread's list.
257 NS_ASSERTION(mFiredOrCanceled
|| mWorker
->IsCanceled(),
258 "Timeout should have fired or been canceled!");
260 mWorker
->RemoveTimeout(this);
264 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerTimeout
, nsITimerCallback
)
267 nsDOMWorkerTimeout::Init(JSContext
* aCx
, PRUint32 aArgc
, jsval
* aArgv
,
270 NS_ASSERTION(aCx
, "Null pointer!");
271 NS_ASSERTION(aArgv
, "Null pointer!");
273 JSAutoRequest
ar(aCx
);
276 JS_ReportError(aCx
, "Function %s requires at least 1 parameter",
277 aIsInterval
? kSetIntervalStr
: kSetTimeoutStr
);
278 return NS_ERROR_INVALID_ARG
;
283 if (!JS_ValueToECMAUint32(aCx
, aArgv
[1], (uint32
*)&interval
)) {
284 JS_ReportError(aCx
, "Second argument to %s must be a millisecond value",
285 aIsInterval
? kSetIntervalStr
: kSetTimeoutStr
);
286 return NS_ERROR_INVALID_ARG
;
290 // If no interval was specified, treat this like a timeout, to avoid
291 // setting an interval of 0 milliseconds.
292 aIsInterval
= PR_FALSE
;
295 mInterval
= interval
;
297 mTargetTime
= PR_Now() + interval
* (PRTime
)PR_USEC_PER_MSEC
;
300 switch (JS_TypeOfValue(aCx
, aArgv
[0])) {
301 case JSTYPE_FUNCTION
:
302 mCallback
= new FunctionCallback(aArgc
, aArgv
, &rv
);
303 NS_ENSURE_TRUE(mCallback
, NS_ERROR_OUT_OF_MEMORY
);
304 NS_ENSURE_SUCCESS(rv
, rv
);
310 mCallback
= new ExpressionCallback(aArgc
, aArgv
, aCx
, &rv
);
311 NS_ENSURE_TRUE(mCallback
, NS_ERROR_OUT_OF_MEMORY
);
312 NS_ENSURE_SUCCESS(rv
, rv
);
316 JS_ReportError(aCx
, "useless %s call (missing quotes around argument?)",
317 aIsInterval
? kSetIntervalStr
: kSetTimeoutStr
);
319 // Return an error that nsGlobalWindow can recognize and turn into NS_OK.
320 return NS_ERROR_INVALID_ARG
;
325 type
= nsITimer::TYPE_REPEATING_SLACK
;
328 type
= nsITimer::TYPE_ONE_SHOT
;
330 mIsInterval
= aIsInterval
;
332 nsCOMPtr
<nsITimer
> timer
= do_CreateInstance(NS_TIMER_CONTRACTID
, &rv
);
333 NS_ENSURE_SUCCESS(rv
, rv
);
335 nsIEventTarget
* target
=
336 static_cast<nsIEventTarget
*>(nsDOMThreadService::get());
338 rv
= timer
->SetTarget(target
);
339 NS_ENSURE_SUCCESS(rv
, rv
);
341 rv
= timer
->InitWithCallback(this, interval
, type
);
342 NS_ENSURE_SUCCESS(rv
, rv
);
346 if (!mWorker
->AddTimeout(this)) {
347 // Must have been canceled.
350 return NS_ERROR_ABORT
;
357 nsDOMWorkerTimeout::Run()
359 NS_ENSURE_TRUE(mCallback
, NS_ERROR_NOT_INITIALIZED
);
360 LOG(("Worker [0x%p] running timeout [0x%p] with id %u",
361 static_cast<void*>(mWorker
.get()), static_cast<void*>(this), mId
));
364 mFiredOrCanceled
= PR_TRUE
;
369 nsDOMThreadService::ThreadJSContextStack()->GetSafeJSContext(&cx
);
370 NS_ENSURE_SUCCESS(rv
, rv
);
372 JSAutoRequest
ar(cx
);
374 rv
= mCallback
->Run(this, cx
);
376 // Make sure any pending exceptions are converted to errors for the pool.
377 JS_ReportPendingException(cx
);
380 mTargetTime
= PR_Now() + mInterval
* (PRTime
)PR_USEC_PER_MSEC
;
387 nsDOMWorkerTimeout::Cancel()
389 NS_ASSERTION(mTimer
, "Impossible to get here without a timer!");
391 LOG(("Worker [0x%p] canceling timeout [0x%p] with id %u",
392 static_cast<void*>(mWorker
.get()), static_cast<void*>(this), mId
));
395 mFiredOrCanceled
= PR_TRUE
;
399 AutoSpinlock
lock(this);
401 if (IsSuspendedNoLock()) {
402 mIsSuspended
= PR_FALSE
;
403 // This should kill us when all is said and done.
404 mSuspendedRef
= nsnull
;
408 // This call to Cancel should kill us.
413 nsDOMWorkerTimeout::Suspend(PRTime aNow
)
415 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
416 NS_ASSERTION(mTimer
, "Impossible to get here without a timer!");
418 AutoSpinlock
lock(this);
421 mIsSuspended
= PR_TRUE
;
422 mSuspendedRef
= this;
427 mSuspendInterval
= PR_MAX(0, PRInt32(mTargetTime
- aNow
)) /
428 (PRTime
)PR_USEC_PER_MSEC
;
430 LOG(("Worker [0x%p] suspending timeout [0x%p] with id %u (interval = %u)",
431 static_cast<void*>(mWorker
.get()), static_cast<void*>(this), mId
,
436 nsDOMWorkerTimeout::Resume(PRTime aNow
)
438 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
439 NS_ASSERTION(mTimer
, "Impossible to get here without a timer!");
441 LOG(("Worker [0x%p] resuming timeout [0x%p] with id %u",
442 static_cast<void*>(mWorker
.get()), static_cast<void*>(this), mId
));
444 AutoSpinlock
lock(this);
446 NS_ASSERTION(IsSuspendedNoLock(), "Should be suspended!");
448 mTargetTime
= aNow
+ mSuspendInterval
* (PRTime
)PR_USEC_PER_MSEC
;
453 mTimer
->InitWithCallback(this, mSuspendInterval
, nsITimer::TYPE_ONE_SHOT
);
454 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv
), "Failed to init timer!");
458 nsDOMWorkerTimeout::AcquireSpinlock()
460 PRUint32 loopCount
= 0;
461 while (PR_AtomicSet(&mSuspendSpinlock
, 1) == 1) {
462 if (++loopCount
> SUSPEND_SPINLOCK_COUNT
) {
463 LOG(("AcquireSpinlock taking too long (looped %u times), yielding.",
466 PR_Sleep(PR_INTERVAL_NO_WAIT
);
471 LOG(("AcquireSpinlock needed %u loops", loopCount
));
477 nsDOMWorkerTimeout::ReleaseSpinlock()
482 PR_AtomicSet(&mSuspendSpinlock
, 0);
483 NS_ASSERTION(suspended
== 1, "Huh?!");
487 nsDOMWorkerTimeout::Notify(nsITimer
* aTimer
)
489 // Should be on the timer thread.
490 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
491 NS_ASSERTION(aTimer
== mTimer
, "Wrong timer?!");
494 nsresult rv
= aTimer
->GetType(&type
);
495 NS_ENSURE_SUCCESS(rv
, rv
);
497 // We only care about one-shot timers here because that may be the one that
498 // we set from Resume().
499 if (type
== nsITimer::TYPE_ONE_SHOT
) {
500 AutoSpinlock
lock(this);
503 //LOG(("Timeout [0x%p] resuming normal interval (%u) with id %u",
504 //static_cast<void*>(this), mInterval, mId));
506 // This is the first fire since we resumed. Set our interval back to the
508 mTargetTime
= PR_Now() + mInterval
* (PRTime
)PR_USEC_PER_MSEC
;
510 rv
= aTimer
->InitWithCallback(this, mInterval
,
511 nsITimer::TYPE_REPEATING_SLACK
);
512 NS_ENSURE_SUCCESS(rv
, rv
);
515 mIsSuspended
= PR_FALSE
;
516 mSuspendedRef
= nsnull
;
520 nsDOMThreadService::get()->TimeoutReady(this);