1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sw=4 et tw=99:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
17 * The Original Code is JavaScript shell workers.
19 * The Initial Developer of the Original Code is
20 * Mozilla Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 2010
22 * the Initial Developer. All Rights Reserved.
25 * Jason Orendorff <jorendorff@mozilla.com>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
43 #include "mozilla/Attributes.h"
52 #include "jsfriendapi.h"
54 #include "jsworkers.h"
56 extern size_t gMaxStackSize
;
64 AutoLock(PRLock
*lock
) : lock(lock
) { PR_Lock(lock
); }
65 ~AutoLock() { PR_Unlock(lock
); }
69 * JavaScript shell workers.
71 * == Object lifetime rules ==
73 * - The ThreadPool lasts from init() to finish().
75 * - The ThreadPool owns the MainQueue and the WorkerQueue. Those live from
76 * the time the first Worker is created until finish().
78 * - Each JS Worker object has the same lifetime as the corresponding C++
79 * Worker object. A Worker is live if (a) the Worker JSObject is still
80 * live; (b) the Worker has an incoming event pending or running; (c) it
81 * has sent an outgoing event to its parent that is still pending; or (d)
82 * it has any live child Workers.
84 * - finish() continues to wait for events until all threads are idle.
86 * Event objects, however, are basically C++-only. The JS Event objects are
87 * just plain old JSObjects. They don't keep anything alive.
89 * == Locking scheme ==
91 * When mixing mutexes and the JSAPI request model, there are two choices:
93 * - Always nest the mutexes in requests. Since threads in requests are not
94 * supposed to block, this means the mutexes must be only briefly held.
96 * - Never nest the mutexes in requests. Since this allows threads to race
97 * with the GC, trace() methods must go through the mutexes just like
100 * This code uses the latter approach for all locks.
102 * In one case, a thread holding a Worker's mutex can acquire the mutex of one
103 * of its child Workers. See Worker::terminateSelf. (This can't deadlock because
104 * the parent-child relationship is a partial order.)
110 template <class T
, class AllocPolicy
>
112 typedef Vector
<T
, 4, AllocPolicy
> Vec
;
118 Queue(const Queue
&) MOZ_DELETE
;
119 Queue
& operator=(const Queue
&) MOZ_DELETE
;
122 Queue() : front(&v1
), back(&v2
) {}
123 bool push(T t
) { return back
->append(t
); }
124 bool empty() { return front
->empty() && back
->empty(); }
127 if (front
->empty()) {
128 js::Reverse(back
->begin(), back
->end());
133 T item
= front
->back();
143 void trace(JSTracer
*trc
) {
144 for (T
*p
= v1
.begin(); p
!= v1
.end(); p
++)
146 for (T
*p
= v2
.begin(); p
!= v2
.end(); p
++)
157 typedef HashSet
<Worker
*, DefaultHasher
<Worker
*>, SystemAllocPolicy
> ChildSet
;
160 bool initWorkerParent() { return children
.init(8); }
163 virtual PRLock
*getLock() = 0;
164 virtual ThreadPool
*getThreadPool() = 0;
165 virtual bool post(Event
*item
) = 0; // false on OOM or queue closed
166 virtual void trace(JSTracer
*trc
) = 0;
168 bool addChild(Worker
*w
) {
169 AutoLock
hold(getLock());
170 return children
.put(w
);
173 // This must be called only from GC or when all threads are shut down. It
174 // does not bother with locking.
175 void removeChild(Worker
*w
) {
176 ChildSet::Ptr p
= children
.lookup(w
);
181 void disposeChildren();
182 void notifyTerminating();
186 class ThreadSafeQueue
189 Queue
<T
, SystemAllocPolicy
> queue
;
195 Vector
<T
, 8, SystemAllocPolicy
> busy
;
198 ThreadSafeQueue() : lock(NULL
), condvar(NULL
), closed(false) {}
202 PR_DestroyCondVar(condvar
);
204 PR_DestroyLock(lock
);
207 // Called by take() with the lock held.
208 virtual bool shouldStop() { return closed
; }
211 bool initThreadSafeQueue() {
214 return (lock
= PR_NewLock()) && (condvar
= PR_NewCondVar(lock
));
222 PR_NotifyAllCondVar(condvar
);
223 return queue
.push(t
);
230 PR_NotifyAllCondVar(condvar
);
233 // The caller must hold the lock.
235 while (queue
.empty()) {
238 PR_WaitCondVar(condvar
, PR_INTERVAL_NO_TIMEOUT
);
245 // The caller must hold the lock.
247 for (T
*p
= busy
.begin(); p
!= busy
.end(); p
++) {
254 JS_NOT_REACHED("removeBusy");
257 bool lockedIsIdle() { return busy
.empty() && queue
.empty(); }
261 return lockedIsIdle();
266 PR_NotifyAllCondVar(condvar
);
269 void trace(JSTracer
*trc
) {
271 for (T
*p
= busy
.begin(); p
!= busy
.end(); p
++)
282 virtual ~Event() { JS_ASSERT(!data
); }
284 WorkerParent
*recipient
;
290 enum Result
{ fail
= JS_FALSE
, ok
= JS_TRUE
, forwardToParent
};
292 virtual void destroy(JSContext
*cx
) {
300 void setChildAndRecipient(Worker
*aChild
, WorkerParent
*aRecipient
) {
302 recipient
= aRecipient
;
305 bool deserializeData(JSContext
*cx
, jsval
*vp
) {
306 return !!JS_ReadStructuredClone(cx
, data
, nbytes
, JS_STRUCTURED_CLONE_VERSION
, vp
,
310 virtual Result
process(JSContext
*cx
) = 0;
312 inline void trace(JSTracer
*trc
);
314 template <class EventType
>
315 static EventType
*createEvent(JSContext
*cx
, WorkerParent
*recipient
, Worker
*child
,
320 if (!JS_WriteStructuredClone(cx
, v
, &data
, &nbytes
, NULL
, NULL
))
323 EventType
*event
= new EventType
;
325 JS_ReportOutOfMemory(cx
);
328 event
->recipient
= recipient
;
329 event
->child
= child
;
331 event
->nbytes
= nbytes
;
335 Result
dispatch(JSContext
*cx
, JSObject
*thisobj
, const char *dataPropName
,
336 const char *methodName
, Result noHandler
)
342 if (!JS_HasProperty(cx
, thisobj
, methodName
, &found
))
347 // Create event object.
349 if (!deserializeData(cx
, &v
))
351 JSObject
*obj
= JS_NewObject(cx
, NULL
, NULL
, NULL
);
352 if (!obj
|| !JS_DefineProperty(cx
, obj
, dataPropName
, v
, NULL
, NULL
, 0))
355 // Call event handler.
356 jsval argv
[1] = { OBJECT_TO_JSVAL(obj
) };
357 jsval rval
= JSVAL_VOID
;
358 return Result(JS_CallFunctionName(cx
, thisobj
, methodName
, 1, argv
, &rval
));
362 typedef ThreadSafeQueue
<Event
*> EventQueue
;
364 class MainQueue MOZ_FINAL
: public EventQueue
, public WorkerParent
367 ThreadPool
*threadPool
;
370 explicit MainQueue(ThreadPool
*tp
) : threadPool(tp
) {}
373 JS_ASSERT(queue
.empty());
376 bool init() { return initThreadSafeQueue() && initWorkerParent(); }
378 void destroy(JSContext
*cx
) {
379 while (!queue
.empty())
380 queue
.pop()->destroy(cx
);
384 virtual PRLock
*getLock() { return lock
; }
385 virtual ThreadPool
*getThreadPool() { return threadPool
; }
388 virtual bool shouldStop();
391 virtual bool post(Event
*event
) { return EventQueue::post(event
); }
393 virtual void trace(JSTracer
*trc
);
395 void traceChildren(JSTracer
*trc
) { EventQueue::trace(trc
); }
397 JSBool
mainThreadWork(JSContext
*cx
, bool continueOnError
) {
398 JSAutoSuspendRequest
suspend(cx
);
402 while (take(&event
)) {
404 Event::Result result
;
406 JSAutoRequest
req(cx
);
407 result
= event
->process(cx
);
408 if (result
== Event::forwardToParent
) {
409 // FIXME - pointlessly truncates the string to 8 bits
411 JSAutoByteString bytes
;
412 if (event
->deserializeData(cx
, &data
) &&
413 JSVAL_IS_STRING(data
) &&
414 bytes
.encode(cx
, JSVAL_TO_STRING(data
))) {
415 JS_ReportError(cx
, "%s", bytes
.ptr());
417 JS_ReportOutOfMemory(cx
);
419 result
= Event::fail
;
421 if (result
== Event::fail
&& continueOnError
) {
422 if (JS_IsExceptionPending(cx
) && !JS_ReportPendingException(cx
))
423 JS_ClearPendingException(cx
);
430 if (result
!= Event::ok
)
438 * A queue of workers.
440 * We keep a queue of workers with pending events, rather than a queue of
441 * events, so that two threads won't try to run a Worker at the same time.
443 class WorkerQueue MOZ_FINAL
: public ThreadSafeQueue
<Worker
*>
449 explicit WorkerQueue(MainQueue
*main
) : main(main
) {}
454 /* The top-level object that owns everything else. */
458 enum { threadCount
= 6 };
464 PRThread
*threads
[threadCount
];
467 static JSClass jsClass
;
469 static void start(void* arg
) {
470 ((WorkerQueue
*) arg
)->work();
473 explicit ThreadPool(WorkerHooks
*hooks
) : hooks(hooks
), mq(NULL
), wq(NULL
), terminating(0) {
474 for (int i
= 0; i
< threadCount
; i
++)
482 JS_ASSERT(!threads
[0]);
485 static ThreadPool
*create(JSContext
*cx
, WorkerHooks
*hooks
) {
486 ThreadPool
*tp
= new ThreadPool(hooks
);
488 JS_ReportOutOfMemory(cx
);
492 JSObject
*obj
= JS_NewObject(cx
, &jsClass
, NULL
, NULL
);
497 JS_SetPrivate(obj
, tp
);
502 JSObject
*asObject() { return obj
; }
503 WorkerHooks
*getHooks() { return hooks
; }
504 WorkerQueue
*getWorkerQueue() { return wq
; }
505 MainQueue
*getMainQueue() { return mq
; }
506 bool isTerminating() { return terminating
!= 0; }
509 * Main thread only. Requires request (to prevent GC, which could see the
510 * object in an inconsistent state).
512 bool start(JSContext
*cx
) {
513 JS_ASSERT(!mq
&& !wq
);
514 mq
= new MainQueue(this);
515 if (!mq
|| !mq
->init()) {
520 wq
= new WorkerQueue(mq
);
521 if (!wq
|| !wq
->initThreadSafeQueue()) {
528 JSAutoSuspendRequest
suspend(cx
);
530 for (int i
= 0; i
< threadCount
; i
++) {
531 threads
[i
] = PR_CreateThread(PR_USER_THREAD
, start
, wq
, PR_PRIORITY_NORMAL
,
532 PR_LOCAL_THREAD
, PR_JOINABLE_THREAD
, 0);
542 void terminateAll() {
543 // See comment about JS_ATOMIC_SET in the implementation of
544 // JS_TriggerOperationCallback.
545 JS_ATOMIC_SET(&terminating
, 1);
547 mq
->notifyTerminating();
550 /* This context is used only to free memory. */
551 void shutdown(JSContext
*cx
) {
553 for (int i
= 0; i
< threadCount
; i
++) {
555 PR_JoinThread(threads
[i
]);
563 mq
->disposeChildren();
570 static void jsTraceThreadPool(JSTracer
*trc
, JSObject
*obj
) {
571 ThreadPool
*tp
= unwrap(obj
);
573 tp
->mq
->traceChildren(trc
);
579 static void jsFinalize(JSFreeOp
*fop
, JSObject
*obj
) {
580 if (ThreadPool
*tp
= unwrap(obj
))
585 static ThreadPool
*unwrap(JSObject
*obj
) {
586 JS_ASSERT(JS_GetClass(obj
) == &jsClass
);
587 return (ThreadPool
*) JS_GetPrivate(obj
);
592 * A Worker is always in one of 4 states, except when it is being initialized
593 * or destroyed, or its lock is held:
594 * - idle (!terminated && current == NULL && events.empty())
595 * - enqueued (!terminated && current == NULL && !events.empty())
596 * - busy (!terminated && current != NULL)
597 * - terminated (terminated && current == NULL && events.empty())
599 * Separately, there is a terminateFlag that other threads can set
600 * asynchronously to tell the Worker to terminate.
602 class Worker MOZ_FINAL
: public WorkerParent
605 ThreadPool
*threadPool
;
606 WorkerParent
*parent
;
607 JSObject
*object
; // Worker object exposed to parent
611 Queue
<Event
*, SystemAllocPolicy
> events
; // owning pointers to pending events
614 int32_t terminateFlag
;
616 static JSClass jsWorkerClass
;
619 : threadPool(NULL
), parent(NULL
), object(NULL
), runtime(NULL
),
620 context(NULL
), lock(NULL
), current(NULL
), terminated(false), terminateFlag(0) {}
622 bool init(JSContext
*parentcx
, WorkerParent
*parent
, JSObject
*obj
) {
623 JS_ASSERT(!threadPool
&& !this->parent
&& !object
&& !lock
);
625 if (!initWorkerParent() || !parent
->addChild(this))
627 threadPool
= parent
->getThreadPool();
628 this->parent
= parent
;
631 if (!lock
|| !createRuntime(parentcx
) || !createContext(parentcx
, parent
))
633 JS_SetPrivate(obj
, this);
637 bool createRuntime(JSContext
*parentcx
) {
638 runtime
= JS_NewRuntime(1L * 1024L * 1024L);
640 JS_ReportOutOfMemory(parentcx
);
643 JS_ClearRuntimeThread(runtime
);
647 bool createContext(JSContext
*parentcx
, WorkerParent
*parent
) {
648 JSAutoSetRuntimeThread
guard(runtime
);
649 context
= JS_NewContext(runtime
, 8192);
653 // The Worker has a strong reference to the global; see jsTraceWorker.
654 // JSOPTION_UNROOTED_GLOBAL ensures that when the worker becomes
655 // unreachable, it and its global object can be collected. Otherwise
656 // the cx->globalObject root would keep them both alive forever.
657 JS_SetOptions(context
, JS_GetOptions(parentcx
) | JSOPTION_UNROOTED_GLOBAL
|
658 JSOPTION_DONT_REPORT_UNCAUGHT
);
659 JS_SetVersion(context
, JS_GetVersion(parentcx
));
660 JS_SetContextPrivate(context
, this);
661 JS_SetOperationCallback(context
, jsOperationCallback
);
662 JS_BeginRequest(context
);
664 JSObject
*global
= threadPool
->getHooks()->newGlobalObject(context
);
665 JSObject
*post
, *proto
, *ctor
;
668 JS_SetGlobalObject(context
, global
);
670 // Because the Worker is completely isolated from the rest of the
671 // runtime, and because any pending events on a Worker keep the Worker
672 // alive, this postMessage function cannot be called after the Worker
673 // is collected. Therefore it's safe to stash a pointer (a weak
674 // reference) to the C++ Worker object in the reserved slot.
675 post
= JS_GetFunctionObject(
676 js::DefineFunctionWithReserved(context
, global
, "postMessage",
677 (JSNative
) jsPostMessageToParent
, 1, 0));
681 js::SetFunctionNativeReserved(post
, 0, PRIVATE_TO_JSVAL(this));
683 proto
= js::InitClassWithReserved(context
, global
, NULL
, &jsWorkerClass
, jsConstruct
, 1,
684 NULL
, jsMethods
, NULL
, NULL
);
688 ctor
= JS_GetConstructor(context
, proto
);
692 js::SetFunctionNativeReserved(post
, 0, PRIVATE_TO_JSVAL(this));
694 JS_EndRequest(context
);
698 JS_EndRequest(context
);
699 JS_DestroyContext(context
);
704 static void jsTraceWorker(JSTracer
*trc
, JSObject
*obj
) {
705 JS_ASSERT(JS_GetClass(obj
) == &jsWorkerClass
);
706 if (Worker
*w
= (Worker
*) JS_GetPrivate(obj
)) {
707 w
->parent
->trace(trc
);
708 w
->events
.trace(trc
);
710 w
->current
->trace(trc
);
711 JS_CALL_OBJECT_TRACER(trc
, JS_GetGlobalObject(w
->context
), "Worker global");
715 static void jsFinalize(JSFreeOp
*fop
, JSObject
*obj
) {
716 JS_ASSERT(JS_GetClass(obj
) == &jsWorkerClass
);
717 if (Worker
*w
= (Worker
*) JS_GetPrivate(obj
))
721 static JSBool
jsOperationCallback(JSContext
*cx
) {
722 Worker
*w
= (Worker
*) JS_GetContextPrivate(cx
);
723 JSAutoSuspendRequest
suspend(cx
); // avoid nesting w->lock in a request
724 return !w
->checkTermination();
727 static JSBool
jsResolveGlobal(JSContext
*cx
, JSObject
*obj
, jsid id
, unsigned flags
,
732 if (!JS_ResolveStandardClass(cx
, obj
, id
, &resolved
))
740 static JSBool
jsPostMessageToParent(JSContext
*cx
, unsigned argc
, jsval
*vp
);
741 static JSBool
jsPostMessageToChild(JSContext
*cx
, unsigned argc
, jsval
*vp
);
742 static JSBool
jsTerminate(JSContext
*cx
, unsigned argc
, jsval
*vp
);
744 bool checkTermination() {
746 return lockedCheckTermination();
749 bool lockedCheckTermination() {
750 if (terminateFlag
|| threadPool
->isTerminating()) {
757 // Caller must hold the lock.
758 void terminateSelf() {
760 while (!events
.empty())
761 events
.pop()->destroy(context
);
763 // Tell the children to shut down too. An arbitrarily silly amount of
764 // processing could happen before the whole tree is terminated; but
765 // this way we don't have to worry about blowing the C stack.
766 for (ChildSet::Enum
e(children
); !e
.empty(); e
.popFront())
767 e
.front()->setTerminateFlag(); // note: nesting locks here
773 parent
->removeChild(this);
779 while (!events
.empty())
780 events
.pop()->destroy(context
);
782 PR_DestroyLock(lock
);
786 JS_SetRuntimeThread(runtime
);
788 JS_DestroyContextNoGC(context
);
792 JS_DestroyRuntime(runtime
);
797 // Do not call parent->removeChild(). This is called either from
798 // ~Worker, which calls it for us; or from parent->disposeChildren or
799 // Worker::create, which require that it not be called.
804 static Worker
*create(JSContext
*parentcx
, WorkerParent
*parent
,
805 JSString
*scriptName
, JSObject
*obj
);
807 JSObject
*asObject() { return object
; }
809 JSObject
*getGlobal() { return JS_GetGlobalObject(context
); }
811 WorkerParent
*getParent() { return parent
; }
813 virtual PRLock
*getLock() { return lock
; }
815 virtual ThreadPool
*getThreadPool() { return threadPool
; }
817 bool post(Event
*event
) {
821 if (!current
&& events
.empty() && !threadPool
->getWorkerQueue()->post(this))
823 return events
.push(event
);
826 void setTerminateFlag() {
828 terminateFlag
= true;
830 JS_TriggerOperationCallback(runtime
);
833 void notifyTerminating() {
835 WorkerParent::notifyTerminating();
838 void processOneEvent();
840 /* Trace method to be called from C++. */
841 void trace(JSTracer
*trc
) {
842 // Just mark the JSObject. If we haven't already been marked,
843 // jsTraceWorker will be called, at which point we'll trace referents.
844 JS_CALL_OBJECT_TRACER(trc
, object
, "queued Worker");
847 static bool getWorkerParentFromConstructor(JSContext
*cx
, JSObject
*ctor
, WorkerParent
**p
) {
848 jsval v
= js::GetFunctionNativeReserved(ctor
, 0);
849 if (JSVAL_IS_VOID(v
)) {
850 // This means ctor is the root Worker constructor (created in
851 // Worker::initWorkers as opposed to Worker::createContext, which sets up
852 // Worker sandboxes) and nothing is initialized yet.
853 v
= js::GetFunctionNativeReserved(ctor
, 1);
854 ThreadPool
*threadPool
= (ThreadPool
*) JSVAL_TO_PRIVATE(v
);
855 if (!threadPool
->start(cx
))
857 WorkerParent
*parent
= threadPool
->getMainQueue();
858 js::SetFunctionNativeReserved(ctor
, 0, PRIVATE_TO_JSVAL(parent
));
862 *p
= (WorkerParent
*) JSVAL_TO_PRIVATE(v
);
866 static JSBool
jsConstruct(JSContext
*cx
, unsigned argc
, jsval
*vp
) {
868 * We pretend to implement write barriers on shell workers (by setting
869 * the JSCLASS_IMPLEMENTS_BARRIERS), but we don't. So we immediately
870 * disable incremental GC if shell workers are ever used.
872 js::DisableIncrementalGC(JS_GetRuntime(cx
));
874 WorkerParent
*parent
;
875 if (!getWorkerParentFromConstructor(cx
, JSVAL_TO_OBJECT(JS_CALLEE(cx
, vp
)), &parent
))
879 JSString
*scriptName
= JS_ValueToString(cx
, argc
? JS_ARGV(cx
, vp
)[0] : JSVAL_VOID
);
883 JSObject
*obj
= JS_NewObject(cx
, &jsWorkerClass
, NULL
, NULL
);
884 if (!obj
|| !create(cx
, parent
, scriptName
, obj
))
886 JS_SET_RVAL(cx
, vp
, OBJECT_TO_JSVAL(obj
));
890 static JSFunctionSpec jsMethods
[3];
891 static JSFunctionSpec jsStaticMethod
[2];
893 static ThreadPool
*initWorkers(JSContext
*cx
, WorkerHooks
*hooks
, JSObject
*global
,
895 // Create the ThreadPool object and its JSObject wrapper.
896 ThreadPool
*threadPool
= ThreadPool::create(cx
, hooks
);
900 // Root the ThreadPool JSObject early.
901 *objp
= threadPool
->asObject();
903 // Create the Worker constructor.
904 JSObject
*proto
= js::InitClassWithReserved(cx
, global
, NULL
, &jsWorkerClass
,
906 NULL
, jsMethods
, NULL
, NULL
);
910 // Stash a pointer to the ThreadPool in constructor reserved slot 1.
911 // It will be used later when lazily creating the MainQueue.
912 JSObject
*ctor
= JS_GetConstructor(cx
, proto
);
913 js::SetFunctionNativeReserved(ctor
, 1, PRIVATE_TO_JSVAL(threadPool
));
919 class InitEvent
: public Event
922 static InitEvent
*create(JSContext
*cx
, Worker
*worker
, JSString
*scriptName
) {
923 return createEvent
<InitEvent
>(cx
, worker
, worker
, STRING_TO_JSVAL(scriptName
));
926 Result
process(JSContext
*cx
) {
928 if (!deserializeData(cx
, &s
))
930 JS_ASSERT(JSVAL_IS_STRING(s
));
931 JSAutoByteString
filename(cx
, JSVAL_TO_STRING(s
));
935 JSScript
*script
= JS_CompileUTF8File(cx
, child
->getGlobal(), filename
.ptr());
939 AutoValueRooter
rval(cx
);
940 JSBool ok
= JS_ExecuteScript(cx
, child
->getGlobal(), script
, rval
.addr());
945 class DownMessageEvent
: public Event
948 static DownMessageEvent
*create(JSContext
*cx
, Worker
*child
, jsval data
) {
949 return createEvent
<DownMessageEvent
>(cx
, child
, child
, data
);
952 Result
process(JSContext
*cx
) {
953 return dispatch(cx
, child
->getGlobal(), "data", "onmessage", ok
);
957 class UpMessageEvent
: public Event
960 static UpMessageEvent
*create(JSContext
*cx
, Worker
*child
, jsval data
) {
961 return createEvent
<UpMessageEvent
>(cx
, child
->getParent(), child
, data
);
964 Result
process(JSContext
*cx
) {
965 return dispatch(cx
, child
->asObject(), "data", "onmessage", ok
);
969 class ErrorEvent
: public Event
972 static ErrorEvent
*create(JSContext
*cx
, Worker
*child
) {
973 JSString
*data
= NULL
;
975 if (JS_GetPendingException(cx
, &exc
)) {
976 AutoValueRooter
tvr(cx
, exc
);
977 JS_ClearPendingException(cx
);
979 // Determine what error message to put in the error event.
980 // If exc.message is a string, use that; otherwise use String(exc).
981 // (This is a little different from what web workers do.)
982 if (JSVAL_IS_OBJECT(exc
)) {
984 if (!JS_GetProperty(cx
, JSVAL_TO_OBJECT(exc
), "message", &msg
))
985 JS_ClearPendingException(cx
);
986 else if (JSVAL_IS_STRING(msg
))
987 data
= JSVAL_TO_STRING(msg
);
990 data
= JS_ValueToString(cx
, exc
);
995 return createEvent
<ErrorEvent
>(cx
, child
->getParent(), child
,
996 data
? STRING_TO_JSVAL(data
) : JSVAL_VOID
);
999 Result
process(JSContext
*cx
) {
1000 return dispatch(cx
, child
->asObject(), "message", "onerror", forwardToParent
);
1004 } /* namespace workers */
1005 } /* namespace js */
1007 using namespace js::workers
;
1010 WorkerParent::disposeChildren()
1012 for (ChildSet::Enum
e(children
); !e
.empty(); e
.popFront()) {
1013 e
.front()->dispose();
1019 WorkerParent::notifyTerminating()
1021 AutoLock
hold(getLock());
1022 for (ChildSet::Range r
= children
.all(); !r
.empty(); r
.popFront())
1023 r
.front()->notifyTerminating();
1027 MainQueue::shouldStop()
1029 // Note: This deliberately nests WorkerQueue::lock in MainQueue::lock.
1030 // Releasing MainQueue::lock would risk a race -- isIdle() could return
1031 // false, but the workers could become idle before we reacquire
1032 // MainQueue::lock and go to sleep, and we would wait on the condvar
1034 return closed
|| threadPool
->getWorkerQueue()->isIdle();
1038 MainQueue::trace(JSTracer
*trc
)
1040 JS_CALL_OBJECT_TRACER(trc
, threadPool
->asObject(), "MainQueue");
1044 WorkerQueue::work() {
1045 AutoLock
hold(lock
);
1048 while (take(&w
)) { // can block outside the mutex
1050 w
->processOneEvent(); // enters request on w->context
1054 if (lockedIsIdle()) {
1070 template <class Ch
> bool
1071 IsAbsolute(const Ch
*filename
)
1073 return filename
[0] == '/' ||
1074 (mswin
&& (filename
[0] == '\\' || (filename
[0] != '\0' && filename
[1] == ':')));
1077 // Note: base is a filename, not a directory name.
1079 ResolveRelativePath(JSContext
*cx
, const char *base
, JSString
*filename
)
1081 size_t fileLen
= JS_GetStringLength(filename
);
1082 const jschar
*fileChars
= JS_GetStringCharsZ(cx
, filename
);
1086 if (IsAbsolute(fileChars
))
1089 // Strip off the filename part of base.
1091 for (size_t i
= 0; base
[i
]; i
++) {
1092 if (base
[i
] == '/' || (mswin
&& base
[i
] == '\\'))
1096 // If base is relative and contains no directories, use filename unchanged.
1097 if (!IsAbsolute(base
) && dirLen
== (size_t) -1)
1100 // Otherwise return base[:dirLen + 1] + filename.
1101 js::Vector
<jschar
, 0> result(cx
);
1103 if (!JS_DecodeBytes(cx
, base
, dirLen
+ 1, NULL
, &nchars
))
1105 if (!result
.reserve(dirLen
+ 1 + fileLen
))
1107 JS_ALWAYS_TRUE(result
.resize(dirLen
+ 1));
1108 if (!JS_DecodeBytes(cx
, base
, dirLen
+ 1, result
.begin(), &nchars
))
1110 result
.infallibleAppend(fileChars
, fileLen
);
1111 return JS_NewUCStringCopyN(cx
, result
.begin(), result
.length());
1115 Worker::create(JSContext
*parentcx
, WorkerParent
*parent
, JSString
*scriptName
, JSObject
*obj
)
1117 Worker
*w
= new Worker();
1118 if (!w
|| !w
->init(parentcx
, parent
, obj
)) {
1124 JS_DescribeScriptedCaller(parentcx
, &script
, NULL
);
1125 const char *base
= JS_GetScriptFilename(parentcx
, script
);
1126 JSString
*scriptPath
= ResolveRelativePath(parentcx
, base
, scriptName
);
1130 // Post an InitEvent to run the initialization script.
1131 Event
*event
= InitEvent::create(parentcx
, w
, scriptPath
);
1134 if (!w
->events
.push(event
) || !w
->threadPool
->getWorkerQueue()->post(w
)) {
1135 event
->destroy(parentcx
);
1136 JS_ReportOutOfMemory(parentcx
);
1144 Worker::processOneEvent()
1146 Event
*event
= NULL
; /* init to shut GCC up */
1148 AutoLock
hold1(lock
);
1149 if (lockedCheckTermination() || events
.empty())
1152 event
= current
= events
.pop();
1155 JS_SetRuntimeThread(runtime
);
1157 Event::Result result
;
1159 JSAutoRequest
ar(context
);
1160 result
= event
->process(context
);
1163 // Note: we have to leave the above request before calling parent->post or
1164 // checkTermination, both of which acquire locks.
1165 if (result
== Event::forwardToParent
) {
1166 event
->setChildAndRecipient(this, parent
);
1167 if (parent
->post(event
)) {
1168 event
= NULL
; // to prevent it from being deleted below
1170 JS_ReportOutOfMemory(context
);
1171 result
= Event::fail
;
1174 if (result
== Event::fail
&& !checkTermination()) {
1175 JSAutoRequest
ar(context
);
1176 Event
*err
= ErrorEvent::create(context
, this);
1177 if (err
&& !parent
->post(err
)) {
1178 JS_ReportOutOfMemory(context
);
1179 err
->destroy(context
);
1183 // FIXME - out of memory, probably should panic
1188 event
->destroy(context
);
1189 JS_ClearRuntimeThread(runtime
);
1192 AutoLock
hold2(lock
);
1194 if (!lockedCheckTermination() && !events
.empty()) {
1195 // Re-enqueue this worker. OOM here effectively kills the worker.
1196 if (!threadPool
->getWorkerQueue()->post(this))
1197 JS_ReportOutOfMemory(context
);
1203 Worker::jsPostMessageToParent(JSContext
*cx
, unsigned argc
, jsval
*vp
)
1205 jsval workerval
= js::GetFunctionNativeReserved(JSVAL_TO_OBJECT(JS_CALLEE(cx
, vp
)), 0);
1206 Worker
*w
= (Worker
*) JSVAL_TO_PRIVATE(workerval
);
1209 JSAutoSuspendRequest
suspend(cx
); // avoid nesting w->lock in a request
1210 if (w
->checkTermination())
1214 jsval data
= argc
> 0 ? JS_ARGV(cx
, vp
)[0] : JSVAL_VOID
;
1215 Event
*event
= UpMessageEvent::create(cx
, w
, data
);
1218 if (!w
->parent
->post(event
)) {
1220 JS_ReportOutOfMemory(cx
);
1223 JS_SET_RVAL(cx
, vp
, JSVAL_VOID
);
1228 Worker::jsPostMessageToChild(JSContext
*cx
, unsigned argc
, jsval
*vp
)
1230 JSObject
*workerobj
= JS_THIS_OBJECT(cx
, vp
);
1233 Worker
*w
= (Worker
*) JS_GetInstancePrivate(cx
, workerobj
, &jsWorkerClass
, JS_ARGV(cx
, vp
));
1235 if (!JS_IsExceptionPending(cx
))
1236 JS_ReportError(cx
, "Worker was shut down");
1240 jsval data
= argc
> 0 ? JS_ARGV(cx
, vp
)[0] : JSVAL_VOID
;
1241 Event
*event
= DownMessageEvent::create(cx
, w
, data
);
1244 if (!w
->post(event
)) {
1245 JS_ReportOutOfMemory(cx
);
1248 JS_SET_RVAL(cx
, vp
, JSVAL_VOID
);
1253 Worker::jsTerminate(JSContext
*cx
, unsigned argc
, jsval
*vp
)
1255 JS_SET_RVAL(cx
, vp
, JSVAL_VOID
);
1257 JSObject
*workerobj
= JS_THIS_OBJECT(cx
, vp
);
1260 Worker
*w
= (Worker
*) JS_GetInstancePrivate(cx
, workerobj
, &jsWorkerClass
, JS_ARGV(cx
, vp
));
1262 return !JS_IsExceptionPending(cx
); // ok to terminate twice
1264 JSAutoSuspendRequest
suspend(cx
);
1265 w
->setTerminateFlag();
1270 Event::trace(JSTracer
*trc
)
1273 recipient
->trace(trc
);
1275 JS_CALL_OBJECT_TRACER(trc
, child
->asObject(), "worker");
1278 JSClass
ThreadPool::jsClass
= {
1279 "ThreadPool", JSCLASS_HAS_PRIVATE
| JSCLASS_IMPLEMENTS_BARRIERS
,
1280 JS_PropertyStub
, JS_PropertyStub
, JS_PropertyStub
, JS_StrictPropertyStub
,
1281 JS_EnumerateStub
, JS_ResolveStub
, JS_ConvertStub
, jsFinalize
,
1282 NULL
, NULL
, NULL
, NULL
, jsTraceThreadPool
1285 JSClass
Worker::jsWorkerClass
= {
1286 "Worker", JSCLASS_HAS_PRIVATE
| JSCLASS_IMPLEMENTS_BARRIERS
,
1287 JS_PropertyStub
, JS_PropertyStub
, JS_PropertyStub
, JS_StrictPropertyStub
,
1288 JS_EnumerateStub
, JS_ResolveStub
, JS_ConvertStub
, jsFinalize
,
1289 NULL
, NULL
, NULL
, NULL
, jsTraceWorker
1292 JSFunctionSpec
Worker::jsMethods
[3] = {
1293 JS_FN("postMessage", Worker::jsPostMessageToChild
, 1, 0),
1294 JS_FN("terminate", Worker::jsTerminate
, 0, 0),
1299 js::workers::init(JSContext
*cx
, WorkerHooks
*hooks
, JSObject
*global
, JSObject
**rootp
)
1301 return Worker::initWorkers(cx
, hooks
, global
, rootp
);
1305 js::workers::terminateAll(ThreadPool
*tp
)
1311 js::workers::finish(JSContext
*cx
, ThreadPool
*tp
)
1313 if (MainQueue
*mq
= tp
->getMainQueue()) {
1314 JS_ALWAYS_TRUE(mq
->mainThreadWork(cx
, true));
1319 #endif /* JS_THREADSAFE */