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 ***** */
50 #include "jshashtable.h"
54 #include "jsworkers.h"
56 extern size_t gMaxStackSize;
59 * JavaScript shell workers.
61 * == Object lifetime rules ==
63 * - The ThreadPool lasts from init() to finish().
65 * - The ThreadPool owns the MainQueue and the WorkerQueue. Those live from
66 * the time the first Worker is created until finish().
68 * - Each JS Worker object has the same lifetime as the corresponding C++
69 * Worker object. A Worker is live if (a) the Worker JSObject is still
70 * live; (b) the Worker has an incoming event pending or running; (c) it
71 * has sent an outgoing event to its parent that is still pending; or (d)
72 * it has any live child Workers.
74 * - finish() continues to wait for events until all threads are idle.
76 * Event objects, however, are basically C++-only. The JS Event objects are
77 * just plain old JSObjects. They don't keep anything alive.
79 * == Locking scheme ==
81 * When mixing mutexes and the JSAPI request model, there are two choices:
83 * - Always nest the mutexes in requests. Since threads in requests are not
84 * supposed to block, this means the mutexes must be only briefly held.
86 * - Never nest the mutexes in requests. Since this allows threads to race
87 * with the GC, trace() methods must go through the mutexes just like
90 * This code uses the latter approach for all locks.
92 * In one case, a thread holding a Worker's mutex can acquire the mutex of one
93 * of its child Workers. See Worker::terminateSelf. (This can't deadlock because
94 * the parent-child relationship is a partial order.)
100 template <class T, class AllocPolicy>
103 typedef Vector<T, 4, AllocPolicy> Vec;
109 // Queue is not copyable.
110 Queue(const Queue &);
111 Queue & operator=(const Queue &);
114 Queue() : front(&v1), back(&v2) {}
115 bool push(T t) { return back->append(t); }
116 bool empty() { return front->empty() && back->empty(); }
119 if (front->empty()) {
120 std::reverse(back->begin(), back->end());
125 T item = front->back();
135 void trace(JSTracer *trc) {
136 for (T *p = v1.begin(); p != v1.end(); p++)
138 for (T *p = v2.begin(); p != v2.end(); p++)
149 typedef HashSet<Worker *, DefaultHasher<Worker *>, SystemAllocPolicy> ChildSet;
152 bool initWorkerParent() { return children.init(8); }
155 virtual JSLock *getLock() = 0;
156 virtual ThreadPool *getThreadPool() = 0;
157 virtual bool post(Event *item) = 0; // false on OOM or queue closed
158 virtual void trace(JSTracer *trc) = 0;
160 bool addChild(Worker *w) {
161 AutoLock hold(getLock());
162 return children.put(w) != NULL;
165 // This must be called only from GC or when all threads are shut down. It
166 // does not bother with locking.
167 void removeChild(Worker *w) {
168 ChildSet::Ptr p = children.lookup(w);
173 void disposeChildren();
177 class ThreadSafeQueue
180 Queue<T, SystemAllocPolicy> queue;
186 Vector<T, 8, SystemAllocPolicy> busy;
189 ThreadSafeQueue() : lock(NULL), condvar(NULL), closed(false) {}
193 JS_DESTROY_CONDVAR(condvar);
195 JS_DESTROY_LOCK(lock);
198 // Called by take() with the lock held.
199 virtual bool shouldStop() { return closed; }
202 bool initThreadSafeQueue() {
205 return (lock = JS_NEW_LOCK()) && (condvar = JS_NEW_CONDVAR(lock));
213 JS_NOTIFY_ALL_CONDVAR(condvar);
214 return queue.push(t);
221 JS_NOTIFY_ALL_CONDVAR(condvar);
224 // The caller must hold the lock.
226 while (queue.empty()) {
229 JS_WAIT_CONDVAR(condvar, JS_NO_TIMEOUT);
236 // The caller must hold the lock.
238 for (T *p = busy.begin(); p != busy.end(); p++) {
245 JS_NOT_REACHED("removeBusy");
248 bool lockedIsIdle() { return busy.empty() && queue.empty(); }
252 return lockedIsIdle();
257 JS_NOTIFY_ALL_CONDVAR(condvar);
260 void trace(JSTracer *trc) {
262 for (T *p = busy.begin(); p != busy.end(); p++)
273 virtual ~Event() { JS_ASSERT(!data); }
275 WorkerParent *recipient;
281 enum Result { fail = JS_FALSE, ok = JS_TRUE, forwardToParent };
283 virtual void destroy(JSContext *cx) {
291 void setChildAndRecipient(Worker *aChild, WorkerParent *aRecipient) {
293 recipient = aRecipient;
296 bool deserializeData(JSContext *cx, jsval *vp) {
297 return !!JS_ReadStructuredClone(cx, data, nbytes, JS_STRUCTURED_CLONE_VERSION, vp,
301 virtual Result process(JSContext *cx) = 0;
303 inline void trace(JSTracer *trc);
305 template <class EventType>
306 static EventType *createEvent(JSContext *cx, WorkerParent *recipient, Worker *child,
311 if (!JS_WriteStructuredClone(cx, v, &data, &nbytes, NULL, NULL))
314 EventType *event = new EventType;
316 JS_ReportOutOfMemory(cx);
319 event->recipient = recipient;
320 event->child = child;
322 event->nbytes = nbytes;
326 Result dispatch(JSContext *cx, JSObject *thisobj, const char *dataPropName,
327 const char *methodName, Result noHandler)
333 if (!JS_HasProperty(cx, thisobj, methodName, &found))
338 // Create event object.
340 if (!deserializeData(cx, &v))
342 JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL);
343 if (!obj || !JS_DefineProperty(cx, obj, dataPropName, v, NULL, NULL, 0))
346 // Call event handler.
347 jsval argv[1] = { OBJECT_TO_JSVAL(obj) };
348 jsval rval = JSVAL_VOID;
349 return Result(JS_CallFunctionName(cx, thisobj, methodName, 1, argv, &rval));
353 typedef ThreadSafeQueue<Event *> EventQueue;
355 class MainQueue : public EventQueue, public WorkerParent
358 ThreadPool *threadPool;
361 explicit MainQueue(ThreadPool *tp) : threadPool(tp) {}
364 JS_ASSERT(queue.empty());
367 bool init() { return initThreadSafeQueue() && initWorkerParent(); }
369 void destroy(JSContext *cx) {
370 while (!queue.empty())
371 queue.pop()->destroy(cx);
375 virtual JSLock *getLock() { return lock; }
376 virtual ThreadPool *getThreadPool() { return threadPool; }
379 virtual bool shouldStop();
382 virtual bool post(Event *event) { return EventQueue::post(event); }
384 virtual void trace(JSTracer *trc);
386 void traceChildren(JSTracer *trc) { EventQueue::trace(trc); }
388 JSBool mainThreadWork(JSContext *cx, bool continueOnError) {
389 JSAutoSuspendRequest suspend(cx);
393 while (take(&event)) {
394 JS_RELEASE_LOCK(lock);
395 Event::Result result;
397 JSAutoRequest req(cx);
398 result = event->process(cx);
399 if (result == Event::forwardToParent) {
400 // FIXME - pointlessly truncates the string to 8 bits
402 JSAutoByteString bytes;
403 if (event->deserializeData(cx, &data) &&
404 JSVAL_IS_STRING(data) &&
405 bytes.encode(cx, JSVAL_TO_STRING(data))) {
406 JS_ReportError(cx, "%s", bytes.ptr());
408 JS_ReportOutOfMemory(cx);
410 result = Event::fail;
412 if (result == Event::fail && continueOnError) {
413 if (JS_IsExceptionPending(cx) && !JS_ReportPendingException(cx))
414 JS_ClearPendingException(cx);
418 JS_ACQUIRE_LOCK(lock);
421 if (result != Event::ok)
429 * A queue of workers.
431 * We keep a queue of workers with pending events, rather than a queue of
432 * events, so that two threads won't try to run a Worker at the same time.
434 class WorkerQueue : public ThreadSafeQueue<Worker *>
440 explicit WorkerQueue(MainQueue *main) : main(main) {}
445 /* The top-level object that owns everything else. */
449 enum { threadCount = 6 };
455 PRThread *threads[threadCount];
458 static JSClass jsClass;
460 static void start(void* arg) {
461 ((WorkerQueue *) arg)->work();
464 explicit ThreadPool(WorkerHooks *hooks) : hooks(hooks), mq(NULL), wq(NULL), terminating(0) {
465 for (int i = 0; i < threadCount; i++)
473 JS_ASSERT(!threads[0]);
476 static ThreadPool *create(JSContext *cx, WorkerHooks *hooks) {
477 ThreadPool *tp = new ThreadPool(hooks);
479 JS_ReportOutOfMemory(cx);
483 JSObject *obj = JS_NewObject(cx, &jsClass, NULL, NULL);
484 if (!obj || !JS_SetPrivate(cx, obj, tp)) {
492 JSObject *asObject() { return obj; }
493 WorkerHooks *getHooks() { return hooks; }
494 WorkerQueue *getWorkerQueue() { return wq; }
495 MainQueue *getMainQueue() { return mq; }
496 bool isTerminating() { return terminating != 0; }
499 * Main thread only. Requires request (to prevent GC, which could see the
500 * object in an inconsistent state).
502 bool start(JSContext *cx) {
503 JS_ASSERT(!mq && !wq);
504 mq = new MainQueue(this);
505 if (!mq || !mq->init()) {
510 wq = new WorkerQueue(mq);
511 if (!wq || !wq->initThreadSafeQueue()) {
518 JSAutoSuspendRequest suspend(cx);
520 for (int i = 0; i < threadCount; i++) {
521 threads[i] = PR_CreateThread(PR_USER_THREAD, start, wq, PR_PRIORITY_NORMAL,
522 PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
532 void terminateAll(JSRuntime *rt) {
533 // See comment about JS_ATOMIC_SET in the implementation of
534 // JS_TriggerOperationCallback.
535 JS_ATOMIC_SET(&terminating, 1);
536 JS_TriggerAllOperationCallbacks(rt);
539 /* This context is used only to free memory. */
540 void shutdown(JSContext *cx) {
542 for (int i = 0; i < threadCount; i++) {
544 PR_JoinThread(threads[i]);
552 mq->disposeChildren();
559 static void jsTraceThreadPool(JSTracer *trc, JSObject *obj) {
560 ThreadPool *tp = unwrap(trc->context, obj);
562 tp->mq->traceChildren(trc);
568 static void jsFinalize(JSContext *cx, JSObject *obj) {
569 if (ThreadPool *tp = unwrap(cx, obj))
574 static ThreadPool *unwrap(JSContext *cx, JSObject *obj) {
575 JS_ASSERT(JS_GET_CLASS(cx, obj) == &jsClass);
576 return (ThreadPool *) JS_GetPrivate(cx, obj);
581 * A Worker is always in one of 4 states, except when it is being initialized
582 * or destroyed, or its lock is held:
583 * - idle (!terminated && current == NULL && events.empty())
584 * - enqueued (!terminated && current == NULL && !events.empty())
585 * - busy (!terminated && current != NULL)
586 * - terminated (terminated && current == NULL && events.empty())
588 * Separately, there is a terminateFlag that other threads can set
589 * asynchronously to tell the Worker to terminate.
591 class Worker : public WorkerParent
594 ThreadPool *threadPool;
595 WorkerParent *parent;
596 JSObject *object; // Worker object exposed to parent
599 Queue<Event *, SystemAllocPolicy> events; // owning pointers to pending events
602 int32_t terminateFlag;
604 static JSClass jsWorkerClass;
607 : threadPool(NULL), parent(NULL), object(NULL),
608 context(NULL), lock(NULL), current(NULL), terminated(false), terminateFlag(0) {}
610 bool init(JSContext *parentcx, WorkerParent *parent, JSObject *obj) {
611 JS_ASSERT(!threadPool && !this->parent && !object && !lock);
613 if (!initWorkerParent() || !parent->addChild(this))
615 threadPool = parent->getThreadPool();
616 this->parent = parent;
618 lock = JS_NEW_LOCK();
620 createContext(parentcx, parent) &&
621 JS_SetPrivate(parentcx, obj, this);
624 bool createContext(JSContext *parentcx, WorkerParent *parent) {
625 JSRuntime *rt = JS_GetRuntime(parentcx);
626 context = JS_NewContext(rt, 8192);
630 // The Worker has a strong reference to the global; see jsTraceWorker.
631 // JSOPTION_UNROOTED_GLOBAL ensures that when the worker becomes
632 // unreachable, it and its global object can be collected. Otherwise
633 // the cx->globalObject root would keep them both alive forever.
634 JS_SetOptions(context, JS_GetOptions(parentcx) | JSOPTION_UNROOTED_GLOBAL |
635 JSOPTION_DONT_REPORT_UNCAUGHT);
636 JS_SetVersion(context, JS_GetVersion(parentcx));
637 JS_SetContextPrivate(context, this);
638 JS_SetOperationCallback(context, jsOperationCallback);
639 JS_BeginRequest(context);
641 JSObject *global = threadPool->getHooks()->newGlobalObject(context);
642 JSObject *post, *proto, *ctor;
645 JS_SetGlobalObject(context, global);
647 // Because the Worker is completely isolated from the rest of the
648 // runtime, and because any pending events on a Worker keep the Worker
649 // alive, this postMessage function cannot be called after the Worker
650 // is collected. Therefore it's safe to stash a pointer (a weak
651 // reference) to the C++ Worker object in the reserved slot.
652 post = JS_GetFunctionObject(JS_DefineFunction(context, global, "postMessage",
653 (JSNative) jsPostMessageToParent, 1, 0));
654 if (!post || !JS_SetReservedSlot(context, post, 0, PRIVATE_TO_JSVAL(this)))
657 proto = JS_InitClass(context, global, NULL, &jsWorkerClass, jsConstruct, 1,
658 NULL, jsMethods, NULL, NULL);
662 ctor = JS_GetConstructor(context, proto);
663 if (!ctor || !JS_SetReservedSlot(context, ctor, 0, PRIVATE_TO_JSVAL(this)))
666 JS_EndRequest(context);
667 JS_ClearContextThread(context);
671 JS_EndRequest(context);
672 JS_DestroyContext(context);
677 static void jsTraceWorker(JSTracer *trc, JSObject *obj) {
678 JS_ASSERT(JS_GET_CLASS(trc->context, obj) == &jsWorkerClass);
679 if (Worker *w = (Worker *) JS_GetPrivate(trc->context, obj)) {
680 w->parent->trace(trc);
681 w->events.trace(trc);
683 w->current->trace(trc);
684 JS_CALL_OBJECT_TRACER(trc, JS_GetGlobalObject(w->context), "Worker global");
688 static void jsFinalize(JSContext *cx, JSObject *obj) {
689 JS_ASSERT(JS_GET_CLASS(cx, obj) == &jsWorkerClass);
690 if (Worker *w = (Worker *) JS_GetPrivate(cx, obj))
694 static JSBool jsOperationCallback(JSContext *cx) {
695 Worker *w = (Worker *) JS_GetContextPrivate(cx);
696 JSAutoSuspendRequest suspend(cx); // avoid nesting w->lock in a request
697 return !w->checkTermination();
700 static JSBool jsResolveGlobal(JSContext *cx, JSObject *obj, jsid id, uintN flags,
705 if (!JS_ResolveStandardClass(cx, obj, id, &resolved))
713 static JSBool jsPostMessageToParent(JSContext *cx, uintN argc, jsval *vp);
714 static JSBool jsPostMessageToChild(JSContext *cx, uintN argc, jsval *vp);
715 static JSBool jsTerminate(JSContext *cx, uintN argc, jsval *vp);
717 bool checkTermination() {
719 return lockedCheckTermination();
722 bool lockedCheckTermination() {
723 if (terminateFlag || threadPool->isTerminating()) {
730 // Caller must hold the lock.
731 void terminateSelf() {
733 while (!events.empty())
734 events.pop()->destroy(context);
736 // Tell the children to shut down too. An arbitrarily silly amount of
737 // processing could happen before the whole tree is terminated; but
738 // this way we don't have to worry about blowing the C stack.
739 for (ChildSet::Enum e(children); !e.empty(); e.popFront())
740 e.front()->setTerminateFlag(); // note: nesting locks here
746 parent->removeChild(this);
752 while (!events.empty())
753 events.pop()->destroy(context);
755 JS_DESTROY_LOCK(lock);
759 JS_SetContextThread(context);
760 JS_DestroyContextNoGC(context);
765 // Do not call parent->removeChild(). This is called either from
766 // ~Worker, which calls it for us; or from parent->disposeChildren or
767 // Worker::create, which require that it not be called.
772 static Worker *create(JSContext *parentcx, WorkerParent *parent,
773 JSString *scriptName, JSObject *obj);
775 JSObject *asObject() { return object; }
777 JSObject *getGlobal() { return JS_GetGlobalObject(context); }
779 WorkerParent *getParent() { return parent; }
781 virtual JSLock *getLock() { return lock; }
783 virtual ThreadPool *getThreadPool() { return threadPool; }
785 bool post(Event *event) {
789 if (!current && events.empty() && !threadPool->getWorkerQueue()->post(this))
791 return events.push(event);
794 void setTerminateFlag() {
796 terminateFlag = true;
798 JS_TriggerOperationCallback(context);
801 void processOneEvent();
803 /* Trace method to be called from C++. */
804 void trace(JSTracer *trc) {
805 // Just mark the JSObject. If we haven't already been marked,
806 // jsTraceWorker will be called, at which point we'll trace referents.
807 JS_CALL_OBJECT_TRACER(trc, object, "queued Worker");
810 static bool getWorkerParentFromConstructor(JSContext *cx, JSObject *ctor, WorkerParent **p) {
812 if (!JS_GetReservedSlot(cx, ctor, 0, &v))
814 if (JSVAL_IS_VOID(v)) {
815 // This means ctor is the root Worker constructor (created in
816 // Worker::initWorkers as opposed to Worker::createContext, which sets up
817 // Worker sandboxes) and nothing is initialized yet.
818 if (!JS_GetReservedSlot(cx, ctor, 1, &v))
820 ThreadPool *threadPool = (ThreadPool *) JSVAL_TO_PRIVATE(v);
821 if (!threadPool->start(cx))
823 WorkerParent *parent = threadPool->getMainQueue();
824 if (!JS_SetReservedSlot(cx, ctor, 0, PRIVATE_TO_JSVAL(parent))) {
825 threadPool->shutdown(cx);
831 *p = (WorkerParent *) JSVAL_TO_PRIVATE(v);
835 static JSBool jsConstruct(JSContext *cx, uintN argc, jsval *vp) {
836 WorkerParent *parent;
837 if (!getWorkerParentFromConstructor(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), &parent))
841 JSString *scriptName = JS_ValueToString(cx, argc ? JS_ARGV(cx, vp)[0] : JSVAL_VOID);
845 JSObject *obj = JS_NewObject(cx, &jsWorkerClass, NULL, NULL);
846 if (!obj || !create(cx, parent, scriptName, obj))
848 JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
852 static JSFunctionSpec jsMethods[3];
853 static JSFunctionSpec jsStaticMethod[2];
855 static ThreadPool *initWorkers(JSContext *cx, WorkerHooks *hooks, JSObject *global,
857 // Create the ThreadPool object and its JSObject wrapper.
858 ThreadPool *threadPool = ThreadPool::create(cx, hooks);
862 // Root the ThreadPool JSObject early.
863 *objp = threadPool->asObject();
865 // Create the Worker constructor.
866 JSObject *proto = JS_InitClass(cx, global, NULL, &jsWorkerClass,
868 NULL, jsMethods, NULL, NULL);
872 // Stash a pointer to the ThreadPool in constructor reserved slot 1.
873 // It will be used later when lazily creating the MainQueue.
874 JSObject *ctor = JS_GetConstructor(cx, proto);
875 if (!JS_SetReservedSlot(cx, ctor, 1, PRIVATE_TO_JSVAL(threadPool)))
882 class InitEvent : public Event
885 static InitEvent *create(JSContext *cx, Worker *worker, JSString *scriptName) {
886 return createEvent<InitEvent>(cx, worker, worker, STRING_TO_JSVAL(scriptName));
889 Result process(JSContext *cx) {
891 if (!deserializeData(cx, &s))
893 JS_ASSERT(JSVAL_IS_STRING(s));
894 JSAutoByteString filename(cx, JSVAL_TO_STRING(s));
898 JSObject *scriptObj = JS_CompileFile(cx, child->getGlobal(), filename.ptr());
902 AutoValueRooter rval(cx);
903 JSBool ok = JS_ExecuteScript(cx, child->getGlobal(), scriptObj, Jsvalify(rval.addr()));
908 class DownMessageEvent : public Event
911 static DownMessageEvent *create(JSContext *cx, Worker *child, jsval data) {
912 return createEvent<DownMessageEvent>(cx, child, child, data);
915 Result process(JSContext *cx) {
916 return dispatch(cx, child->getGlobal(), "data", "onmessage", ok);
920 class UpMessageEvent : public Event
923 static UpMessageEvent *create(JSContext *cx, Worker *child, jsval data) {
924 return createEvent<UpMessageEvent>(cx, child->getParent(), child, data);
927 Result process(JSContext *cx) {
928 return dispatch(cx, child->asObject(), "data", "onmessage", ok);
932 class ErrorEvent : public Event
935 static ErrorEvent *create(JSContext *cx, Worker *child) {
936 JSString *data = NULL;
938 if (JS_GetPendingException(cx, &exc)) {
939 AutoValueRooter tvr(cx, Valueify(exc));
940 JS_ClearPendingException(cx);
942 // Determine what error message to put in the error event.
943 // If exc.message is a string, use that; otherwise use String(exc).
944 // (This is a little different from what web workers do.)
945 if (JSVAL_IS_OBJECT(exc)) {
947 if (!JS_GetProperty(cx, JSVAL_TO_OBJECT(exc), "message", &msg))
948 JS_ClearPendingException(cx);
949 else if (JSVAL_IS_STRING(msg))
950 data = JSVAL_TO_STRING(msg);
953 data = JS_ValueToString(cx, exc);
958 return createEvent<ErrorEvent>(cx, child->getParent(), child,
959 data ? STRING_TO_JSVAL(data) : JSVAL_VOID);
962 Result process(JSContext *cx) {
963 return dispatch(cx, child->asObject(), "message", "onerror", forwardToParent);
967 } /* namespace workers */
970 using namespace js::workers;
973 WorkerParent::disposeChildren()
975 for (ChildSet::Enum e(children); !e.empty(); e.popFront()) {
976 e.front()->dispose();
982 MainQueue::shouldStop()
984 // Note: This deliberately nests WorkerQueue::lock in MainQueue::lock.
985 // Releasing MainQueue::lock would risk a race -- isIdle() could return
986 // false, but the workers could become idle before we reacquire
987 // MainQueue::lock and go to sleep, and we would wait on the condvar
989 return closed || threadPool->getWorkerQueue()->isIdle();
993 MainQueue::trace(JSTracer *trc)
995 JS_CALL_OBJECT_TRACER(trc, threadPool->asObject(), "MainQueue");
999 WorkerQueue::work() {
1000 AutoLock hold(lock);
1003 while (take(&w)) { // can block outside the mutex
1004 JS_RELEASE_LOCK(lock);
1005 w->processOneEvent(); // enters request on w->context
1006 JS_ACQUIRE_LOCK(lock);
1009 if (lockedIsIdle()) {
1010 JS_RELEASE_LOCK(lock);
1012 JS_ACQUIRE_LOCK(lock);
1025 template <class Ch> bool
1026 IsAbsolute(const Ch *filename)
1028 return filename[0] == '/' ||
1029 (mswin && (filename[0] == '\\' || (filename[0] != '\0' && filename[1] == ':')));
1032 // Note: base is a filename, not a directory name.
1034 ResolveRelativePath(JSContext *cx, const char *base, JSString *filename)
1036 size_t fileLen = JS_GetStringLength(filename);
1037 const jschar *fileChars = JS_GetStringCharsZ(cx, filename);
1041 if (IsAbsolute(fileChars))
1044 // Strip off the filename part of base.
1046 for (size_t i = 0; base[i]; i++) {
1047 if (base[i] == '/' || (mswin && base[i] == '\\'))
1051 // If base is relative and contains no directories, use filename unchanged.
1052 if (!IsAbsolute(base) && dirLen == (size_t) -1)
1055 // Otherwise return base[:dirLen + 1] + filename.
1056 js::Vector<jschar, 0, js::ContextAllocPolicy> result(cx);
1058 if (!JS_DecodeBytes(cx, base, dirLen + 1, NULL, &nchars))
1060 if (!result.reserve(dirLen + 1 + fileLen)) {
1061 JS_ReportOutOfMemory(cx);
1064 JS_ALWAYS_TRUE(result.resize(dirLen + 1));
1065 if (!JS_DecodeBytes(cx, base, dirLen + 1, result.begin(), &nchars))
1067 JS_ALWAYS_TRUE(result.append(fileChars, fileLen));
1068 return JS_NewUCStringCopyN(cx, result.begin(), result.length());
1072 Worker::create(JSContext *parentcx, WorkerParent *parent, JSString *scriptName, JSObject *obj)
1074 Worker *w = new Worker();
1075 if (!w || !w->init(parentcx, parent, obj)) {
1080 JSStackFrame *frame = JS_GetScriptedCaller(parentcx, NULL);
1081 const char *base = JS_GetScriptFilename(parentcx, JS_GetFrameScript(parentcx, frame));
1082 JSString *scriptPath = ResolveRelativePath(parentcx, base, scriptName);
1086 // Post an InitEvent to run the initialization script.
1087 Event *event = InitEvent::create(parentcx, w, scriptPath);
1090 if (!w->events.push(event) || !w->threadPool->getWorkerQueue()->post(w)) {
1091 event->destroy(parentcx);
1092 JS_ReportOutOfMemory(parentcx);
1100 Worker::processOneEvent()
1104 AutoLock hold1(lock);
1105 if (lockedCheckTermination() || events.empty())
1108 event = current = events.pop();
1111 JS_SetContextThread(context);
1112 JS_SetNativeStackQuota(context, gMaxStackSize);
1114 Event::Result result;
1116 JSAutoRequest req(context);
1117 result = event->process(context);
1120 // Note: we have to leave the above request before calling parent->post or
1121 // checkTermination, both of which acquire locks.
1122 if (result == Event::forwardToParent) {
1123 event->setChildAndRecipient(this, parent);
1124 if (parent->post(event)) {
1125 event = NULL; // to prevent it from being deleted below
1127 JS_ReportOutOfMemory(context);
1128 result = Event::fail;
1131 if (result == Event::fail && !checkTermination()) {
1132 JSAutoRequest req(context);
1133 Event *err = ErrorEvent::create(context, this);
1134 if (err && !parent->post(err)) {
1135 JS_ReportOutOfMemory(context);
1136 err->destroy(context);
1140 // FIXME - out of memory, probably should panic
1145 event->destroy(context);
1146 JS_ClearContextThread(context);
1149 AutoLock hold2(lock);
1151 if (!lockedCheckTermination() && !events.empty()) {
1152 // Re-enqueue this worker. OOM here effectively kills the worker.
1153 if (!threadPool->getWorkerQueue()->post(this))
1154 JS_ReportOutOfMemory(context);
1160 Worker::jsPostMessageToParent(JSContext *cx, uintN argc, jsval *vp)
1163 if (!JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), 0, &workerval))
1165 Worker *w = (Worker *) JSVAL_TO_PRIVATE(workerval);
1168 JSAutoSuspendRequest suspend(cx); // avoid nesting w->lock in a request
1169 if (w->checkTermination())
1173 jsval data = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
1174 Event *event = UpMessageEvent::create(cx, w, data);
1177 if (!w->parent->post(event)) {
1179 JS_ReportOutOfMemory(cx);
1182 JS_SET_RVAL(cx, vp, JSVAL_VOID);
1187 Worker::jsPostMessageToChild(JSContext *cx, uintN argc, jsval *vp)
1189 JSObject *workerobj = JS_THIS_OBJECT(cx, vp);
1192 Worker *w = (Worker *) JS_GetInstancePrivate(cx, workerobj, &jsWorkerClass, JS_ARGV(cx, vp));
1194 if (!JS_IsExceptionPending(cx))
1195 JS_ReportError(cx, "Worker was shut down");
1199 jsval data = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
1200 Event *event = DownMessageEvent::create(cx, w, data);
1203 if (!w->post(event)) {
1204 JS_ReportOutOfMemory(cx);
1207 JS_SET_RVAL(cx, vp, JSVAL_VOID);
1212 Worker::jsTerminate(JSContext *cx, uintN argc, jsval *vp)
1214 JS_SET_RVAL(cx, vp, JSVAL_VOID);
1216 JSObject *workerobj = JS_THIS_OBJECT(cx, vp);
1219 Worker *w = (Worker *) JS_GetInstancePrivate(cx, workerobj, &jsWorkerClass, JS_ARGV(cx, vp));
1221 return !JS_IsExceptionPending(cx); // ok to terminate twice
1223 JSAutoSuspendRequest suspend(cx);
1224 w->setTerminateFlag();
1229 Event::trace(JSTracer *trc)
1232 recipient->trace(trc);
1234 JS_CALL_OBJECT_TRACER(trc, child->asObject(), "worker");
1237 JSClass ThreadPool::jsClass = {
1238 "ThreadPool", JSCLASS_HAS_PRIVATE | JSCLASS_MARK_IS_TRACE,
1239 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
1240 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, jsFinalize,
1241 NULL, NULL, NULL, NULL,
1242 NULL, NULL, JS_CLASS_TRACE(jsTraceThreadPool), NULL
1245 JSClass Worker::jsWorkerClass = {
1246 "Worker", JSCLASS_HAS_PRIVATE | JSCLASS_MARK_IS_TRACE,
1247 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
1248 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, jsFinalize,
1249 NULL, NULL, NULL, NULL,
1250 NULL, NULL, JS_CLASS_TRACE(jsTraceWorker), NULL
1253 JSFunctionSpec Worker::jsMethods[3] = {
1254 JS_FN("postMessage", Worker::jsPostMessageToChild, 1, 0),
1255 JS_FN("terminate", Worker::jsTerminate, 0, 0),
1260 js::workers::init(JSContext *cx, WorkerHooks *hooks, JSObject *global, JSObject **rootp)
1262 return Worker::initWorkers(cx, hooks, global, rootp);
1266 js::workers::terminateAll(JSRuntime *rt, ThreadPool *tp)
1268 tp->terminateAll(rt);
1272 js::workers::finish(JSContext *cx, ThreadPool *tp)
1274 if (MainQueue *mq = tp->getMainQueue()) {
1275 JS_ALWAYS_TRUE(mq->mainThreadWork(cx, true));
1280 #endif /* JS_THREADSAFE */