Bug 752461 - Hide click-to-play overlays when choosing "never activate plugins.....
[gecko.git] / js / src / shell / jsworkers.cpp
blob3317cf6e59ac98cbcb3bc9c35fbb5aeeb048da5d
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
15 * License.
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.
24 * Contributor(s):
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 ***** */
41 #ifdef JS_THREADSAFE
43 #include "mozilla/Attributes.h"
45 #include <string.h>
46 #include "prthread.h"
47 #include "prlock.h"
48 #include "prcvar.h"
49 #include "jsapi.h"
50 #include "jscntxt.h"
51 #include "jsdbgapi.h"
52 #include "jsfriendapi.h"
53 #include "jslock.h"
54 #include "jsworkers.h"
56 extern size_t gMaxStackSize;
58 class AutoLock
60 private:
61 PRLock *lock;
63 public:
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
98 * everyone else.
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.)
107 namespace js {
108 namespace workers {
110 template <class T, class AllocPolicy>
111 class Queue {
112 typedef Vector<T, 4, AllocPolicy> Vec;
113 Vec v1;
114 Vec v2;
115 Vec *front;
116 Vec *back;
118 Queue(const Queue &) MOZ_DELETE;
119 Queue & operator=(const Queue &) MOZ_DELETE;
121 public:
122 Queue() : front(&v1), back(&v2) {}
123 bool push(T t) { return back->append(t); }
124 bool empty() { return front->empty() && back->empty(); }
126 T pop() {
127 if (front->empty()) {
128 js::Reverse(back->begin(), back->end());
129 Vec *tmp = front;
130 front = back;
131 back = tmp;
133 T item = front->back();
134 front->popBack();
135 return item;
138 void clear() {
139 v1.clear();
140 v2.clear();
143 void trace(JSTracer *trc) {
144 for (T *p = v1.begin(); p != v1.end(); p++)
145 (*p)->trace(trc);
146 for (T *p = v2.begin(); p != v2.end(); p++)
147 (*p)->trace(trc);
151 class Event;
152 class ThreadPool;
153 class Worker;
155 class WorkerParent {
156 protected:
157 typedef HashSet<Worker *, DefaultHasher<Worker *>, SystemAllocPolicy> ChildSet;
158 ChildSet children;
160 bool initWorkerParent() { return children.init(8); }
162 public:
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);
177 JS_ASSERT(p);
178 children.remove(p);
181 void disposeChildren();
182 void notifyTerminating();
185 template <class T>
186 class ThreadSafeQueue
188 protected:
189 Queue<T, SystemAllocPolicy> queue;
190 PRLock *lock;
191 PRCondVar *condvar;
192 bool closed;
194 private:
195 Vector<T, 8, SystemAllocPolicy> busy;
197 protected:
198 ThreadSafeQueue() : lock(NULL), condvar(NULL), closed(false) {}
200 ~ThreadSafeQueue() {
201 if (condvar)
202 PR_DestroyCondVar(condvar);
203 if (lock)
204 PR_DestroyLock(lock);
207 // Called by take() with the lock held.
208 virtual bool shouldStop() { return closed; }
210 public:
211 bool initThreadSafeQueue() {
212 JS_ASSERT(!lock);
213 JS_ASSERT(!condvar);
214 return (lock = PR_NewLock()) && (condvar = PR_NewCondVar(lock));
217 bool post(T t) {
218 AutoLock hold(lock);
219 if (closed)
220 return false;
221 if (queue.empty())
222 PR_NotifyAllCondVar(condvar);
223 return queue.push(t);
226 void close() {
227 AutoLock hold(lock);
228 closed = true;
229 queue.clear();
230 PR_NotifyAllCondVar(condvar);
233 // The caller must hold the lock.
234 bool take(T *t) {
235 while (queue.empty()) {
236 if (shouldStop())
237 return false;
238 PR_WaitCondVar(condvar, PR_INTERVAL_NO_TIMEOUT);
240 *t = queue.pop();
241 busy.append(*t);
242 return true;
245 // The caller must hold the lock.
246 void drop(T item) {
247 for (T *p = busy.begin(); p != busy.end(); p++) {
248 if (*p == item) {
249 *p = busy.back();
250 busy.popBack();
251 return;
254 JS_NOT_REACHED("removeBusy");
257 bool lockedIsIdle() { return busy.empty() && queue.empty(); }
259 bool isIdle() {
260 AutoLock hold(lock);
261 return lockedIsIdle();
264 void wake() {
265 AutoLock hold(lock);
266 PR_NotifyAllCondVar(condvar);
269 void trace(JSTracer *trc) {
270 AutoLock hold(lock);
271 for (T *p = busy.begin(); p != busy.end(); p++)
272 (*p)->trace(trc);
273 queue.trace(trc);
277 class MainQueue;
279 class Event
281 protected:
282 virtual ~Event() { JS_ASSERT(!data); }
284 WorkerParent *recipient;
285 Worker *child;
286 uint64_t *data;
287 size_t nbytes;
289 public:
290 enum Result { fail = JS_FALSE, ok = JS_TRUE, forwardToParent };
292 virtual void destroy(JSContext *cx) {
293 JS_free(cx, data);
294 #ifdef DEBUG
295 data = NULL;
296 #endif
297 delete this;
300 void setChildAndRecipient(Worker *aChild, WorkerParent *aRecipient) {
301 child = aChild;
302 recipient = aRecipient;
305 bool deserializeData(JSContext *cx, jsval *vp) {
306 return !!JS_ReadStructuredClone(cx, data, nbytes, JS_STRUCTURED_CLONE_VERSION, vp,
307 NULL, NULL);
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,
316 jsval v)
318 uint64_t *data;
319 size_t nbytes;
320 if (!JS_WriteStructuredClone(cx, v, &data, &nbytes, NULL, NULL))
321 return NULL;
323 EventType *event = new EventType;
324 if (!event) {
325 JS_ReportOutOfMemory(cx);
326 return NULL;
328 event->recipient = recipient;
329 event->child = child;
330 event->data = data;
331 event->nbytes = nbytes;
332 return event;
335 Result dispatch(JSContext *cx, JSObject *thisobj, const char *dataPropName,
336 const char *methodName, Result noHandler)
338 if (!data)
339 return fail;
341 JSBool found;
342 if (!JS_HasProperty(cx, thisobj, methodName, &found))
343 return fail;
344 if (!found)
345 return noHandler;
347 // Create event object.
348 jsval v;
349 if (!deserializeData(cx, &v))
350 return fail;
351 JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL);
352 if (!obj || !JS_DefineProperty(cx, obj, dataPropName, v, NULL, NULL, 0))
353 return fail;
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
366 private:
367 ThreadPool *threadPool;
369 public:
370 explicit MainQueue(ThreadPool *tp) : threadPool(tp) {}
372 ~MainQueue() {
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);
381 delete this;
384 virtual PRLock *getLock() { return lock; }
385 virtual ThreadPool *getThreadPool() { return threadPool; }
387 protected:
388 virtual bool shouldStop();
390 public:
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);
399 AutoLock hold(lock);
401 Event *event;
402 while (take(&event)) {
403 PR_Unlock(lock);
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
410 jsval data;
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());
416 } else {
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);
424 result = Event::ok;
427 PR_Lock(lock);
428 drop(event);
429 event->destroy(cx);
430 if (result != Event::ok)
431 return false;
433 return true;
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 *>
445 private:
446 MainQueue *main;
448 public:
449 explicit WorkerQueue(MainQueue *main) : main(main) {}
451 void work();
454 /* The top-level object that owns everything else. */
455 class ThreadPool
457 private:
458 enum { threadCount = 6 };
460 JSObject *obj;
461 WorkerHooks *hooks;
462 MainQueue *mq;
463 WorkerQueue *wq;
464 PRThread *threads[threadCount];
465 int32_t terminating;
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++)
475 threads[i] = NULL;
478 public:
479 ~ThreadPool() {
480 JS_ASSERT(!mq);
481 JS_ASSERT(!wq);
482 JS_ASSERT(!threads[0]);
485 static ThreadPool *create(JSContext *cx, WorkerHooks *hooks) {
486 ThreadPool *tp = new ThreadPool(hooks);
487 if (!tp) {
488 JS_ReportOutOfMemory(cx);
489 return NULL;
492 JSObject *obj = JS_NewObject(cx, &jsClass, NULL, NULL);
493 if (!obj) {
494 delete tp;
495 return NULL;
497 JS_SetPrivate(obj, tp);
498 tp->obj = obj;
499 return 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()) {
516 mq->destroy(cx);
517 mq = NULL;
518 return false;
520 wq = new WorkerQueue(mq);
521 if (!wq || !wq->initThreadSafeQueue()) {
522 delete wq;
523 wq = NULL;
524 mq->destroy(cx);
525 mq = NULL;
526 return false;
528 JSAutoSuspendRequest suspend(cx);
529 bool ok = true;
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);
533 if (!threads[i]) {
534 shutdown(cx);
535 ok = false;
536 break;
539 return ok;
542 void terminateAll() {
543 // See comment about JS_ATOMIC_SET in the implementation of
544 // JS_TriggerOperationCallback.
545 JS_ATOMIC_SET(&terminating, 1);
546 if (mq)
547 mq->notifyTerminating();
550 /* This context is used only to free memory. */
551 void shutdown(JSContext *cx) {
552 wq->close();
553 for (int i = 0; i < threadCount; i++) {
554 if (threads[i]) {
555 PR_JoinThread(threads[i]);
556 threads[i] = NULL;
560 delete wq;
561 wq = NULL;
563 mq->disposeChildren();
564 mq->destroy(cx);
565 mq = NULL;
566 terminating = 0;
569 private:
570 static void jsTraceThreadPool(JSTracer *trc, JSObject *obj) {
571 ThreadPool *tp = unwrap(obj);
572 if (tp->mq) {
573 tp->mq->traceChildren(trc);
574 tp->wq->trace(trc);
579 static void jsFinalize(JSFreeOp *fop, JSObject *obj) {
580 if (ThreadPool *tp = unwrap(obj))
581 delete tp;
584 public:
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
604 private:
605 ThreadPool *threadPool;
606 WorkerParent *parent;
607 JSObject *object; // Worker object exposed to parent
608 JSRuntime *runtime;
609 JSContext *context;
610 PRLock *lock;
611 Queue<Event *, SystemAllocPolicy> events; // owning pointers to pending events
612 Event *current;
613 bool terminated;
614 int32_t terminateFlag;
616 static JSClass jsWorkerClass;
618 Worker()
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))
626 return false;
627 threadPool = parent->getThreadPool();
628 this->parent = parent;
629 this->object = obj;
630 lock = PR_NewLock();
631 if (!lock || !createRuntime(parentcx) || !createContext(parentcx, parent))
632 return false;
633 JS_SetPrivate(obj, this);
634 return true;
637 bool createRuntime(JSContext *parentcx) {
638 runtime = JS_NewRuntime(1L * 1024L * 1024L);
639 if (!runtime) {
640 JS_ReportOutOfMemory(parentcx);
641 return false;
643 JS_ClearRuntimeThread(runtime);
644 return true;
647 bool createContext(JSContext *parentcx, WorkerParent *parent) {
648 JSAutoSetRuntimeThread guard(runtime);
649 context = JS_NewContext(runtime, 8192);
650 if (!context)
651 return false;
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;
666 if (!global)
667 goto bad;
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));
678 if (!post)
679 goto bad;
681 js::SetFunctionNativeReserved(post, 0, PRIVATE_TO_JSVAL(this));
683 proto = js::InitClassWithReserved(context, global, NULL, &jsWorkerClass, jsConstruct, 1,
684 NULL, jsMethods, NULL, NULL);
685 if (!proto)
686 goto bad;
688 ctor = JS_GetConstructor(context, proto);
689 if (!ctor)
690 goto bad;
692 js::SetFunctionNativeReserved(post, 0, PRIVATE_TO_JSVAL(this));
694 JS_EndRequest(context);
695 return true;
697 bad:
698 JS_EndRequest(context);
699 JS_DestroyContext(context);
700 context = NULL;
701 return false;
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);
709 if (w->current)
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))
718 delete w;
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,
728 JSObject **objp)
730 JSBool resolved;
732 if (!JS_ResolveStandardClass(cx, obj, id, &resolved))
733 return false;
734 if (resolved)
735 *objp = obj;
737 return true;
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() {
745 AutoLock hold(lock);
746 return lockedCheckTermination();
749 bool lockedCheckTermination() {
750 if (terminateFlag || threadPool->isTerminating()) {
751 terminateSelf();
752 terminateFlag = 0;
754 return terminated;
757 // Caller must hold the lock.
758 void terminateSelf() {
759 terminated = true;
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
770 public:
771 ~Worker() {
772 if (parent)
773 parent->removeChild(this);
774 dispose();
777 void dispose() {
778 JS_ASSERT(!current);
779 while (!events.empty())
780 events.pop()->destroy(context);
781 if (lock) {
782 PR_DestroyLock(lock);
783 lock = NULL;
785 if (runtime)
786 JS_SetRuntimeThread(runtime);
787 if (context) {
788 JS_DestroyContextNoGC(context);
789 context = NULL;
791 if (runtime) {
792 JS_DestroyRuntime(runtime);
793 runtime = NULL;
795 object = NULL;
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.
800 parent = NULL;
801 disposeChildren();
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) {
818 AutoLock hold(lock);
819 if (terminated)
820 return false;
821 if (!current && events.empty() && !threadPool->getWorkerQueue()->post(this))
822 return false;
823 return events.push(event);
826 void setTerminateFlag() {
827 AutoLock hold(lock);
828 terminateFlag = true;
829 if (current)
830 JS_TriggerOperationCallback(runtime);
833 void notifyTerminating() {
834 setTerminateFlag();
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))
856 return false;
857 WorkerParent *parent = threadPool->getMainQueue();
858 js::SetFunctionNativeReserved(ctor, 0, PRIVATE_TO_JSVAL(parent));
859 *p = parent;
860 return true;
862 *p = (WorkerParent *) JSVAL_TO_PRIVATE(v);
863 return true;
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))
876 return false;
879 JSString *scriptName = JS_ValueToString(cx, argc ? JS_ARGV(cx, vp)[0] : JSVAL_VOID);
880 if (!scriptName)
881 return false;
883 JSObject *obj = JS_NewObject(cx, &jsWorkerClass, NULL, NULL);
884 if (!obj || !create(cx, parent, scriptName, obj))
885 return false;
886 JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
887 return true;
890 static JSFunctionSpec jsMethods[3];
891 static JSFunctionSpec jsStaticMethod[2];
893 static ThreadPool *initWorkers(JSContext *cx, WorkerHooks *hooks, JSObject *global,
894 JSObject **objp) {
895 // Create the ThreadPool object and its JSObject wrapper.
896 ThreadPool *threadPool = ThreadPool::create(cx, hooks);
897 if (!threadPool)
898 return NULL;
900 // Root the ThreadPool JSObject early.
901 *objp = threadPool->asObject();
903 // Create the Worker constructor.
904 JSObject *proto = js::InitClassWithReserved(cx, global, NULL, &jsWorkerClass,
905 jsConstruct, 1,
906 NULL, jsMethods, NULL, NULL);
907 if (!proto)
908 return 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));
915 return threadPool;
919 class InitEvent : public Event
921 public:
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) {
927 jsval s;
928 if (!deserializeData(cx, &s))
929 return fail;
930 JS_ASSERT(JSVAL_IS_STRING(s));
931 JSAutoByteString filename(cx, JSVAL_TO_STRING(s));
932 if (!filename)
933 return fail;
935 JSScript *script = JS_CompileUTF8File(cx, child->getGlobal(), filename.ptr());
936 if (!script)
937 return fail;
939 AutoValueRooter rval(cx);
940 JSBool ok = JS_ExecuteScript(cx, child->getGlobal(), script, rval.addr());
941 return Result(ok);
945 class DownMessageEvent : public Event
947 public:
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
959 public:
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
971 public:
972 static ErrorEvent *create(JSContext *cx, Worker *child) {
973 JSString *data = NULL;
974 jsval exc;
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)) {
983 jsval msg;
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);
989 if (!data) {
990 data = JS_ValueToString(cx, exc);
991 if (!data)
992 return NULL;
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;
1009 void
1010 WorkerParent::disposeChildren()
1012 for (ChildSet::Enum e(children); !e.empty(); e.popFront()) {
1013 e.front()->dispose();
1014 e.removeFront();
1018 void
1019 WorkerParent::notifyTerminating()
1021 AutoLock hold(getLock());
1022 for (ChildSet::Range r = children.all(); !r.empty(); r.popFront())
1023 r.front()->notifyTerminating();
1026 bool
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
1033 // forever.
1034 return closed || threadPool->getWorkerQueue()->isIdle();
1037 void
1038 MainQueue::trace(JSTracer *trc)
1040 JS_CALL_OBJECT_TRACER(trc, threadPool->asObject(), "MainQueue");
1043 void
1044 WorkerQueue::work() {
1045 AutoLock hold(lock);
1047 Worker *w;
1048 while (take(&w)) { // can block outside the mutex
1049 PR_Unlock(lock);
1050 w->processOneEvent(); // enters request on w->context
1051 PR_Lock(lock);
1052 drop(w);
1054 if (lockedIsIdle()) {
1055 PR_Unlock(lock);
1056 main->wake();
1057 PR_Lock(lock);
1062 const bool mswin =
1063 #ifdef XP_WIN
1064 true
1065 #else
1066 false
1067 #endif
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.
1078 static JSString *
1079 ResolveRelativePath(JSContext *cx, const char *base, JSString *filename)
1081 size_t fileLen = JS_GetStringLength(filename);
1082 const jschar *fileChars = JS_GetStringCharsZ(cx, filename);
1083 if (!fileChars)
1084 return NULL;
1086 if (IsAbsolute(fileChars))
1087 return filename;
1089 // Strip off the filename part of base.
1090 size_t dirLen = -1;
1091 for (size_t i = 0; base[i]; i++) {
1092 if (base[i] == '/' || (mswin && base[i] == '\\'))
1093 dirLen = i;
1096 // If base is relative and contains no directories, use filename unchanged.
1097 if (!IsAbsolute(base) && dirLen == (size_t) -1)
1098 return filename;
1100 // Otherwise return base[:dirLen + 1] + filename.
1101 js::Vector<jschar, 0> result(cx);
1102 size_t nchars;
1103 if (!JS_DecodeBytes(cx, base, dirLen + 1, NULL, &nchars))
1104 return NULL;
1105 if (!result.reserve(dirLen + 1 + fileLen))
1106 return NULL;
1107 JS_ALWAYS_TRUE(result.resize(dirLen + 1));
1108 if (!JS_DecodeBytes(cx, base, dirLen + 1, result.begin(), &nchars))
1109 return NULL;
1110 result.infallibleAppend(fileChars, fileLen);
1111 return JS_NewUCStringCopyN(cx, result.begin(), result.length());
1114 Worker *
1115 Worker::create(JSContext *parentcx, WorkerParent *parent, JSString *scriptName, JSObject *obj)
1117 Worker *w = new Worker();
1118 if (!w || !w->init(parentcx, parent, obj)) {
1119 delete w;
1120 return NULL;
1123 JSScript *script;
1124 JS_DescribeScriptedCaller(parentcx, &script, NULL);
1125 const char *base = JS_GetScriptFilename(parentcx, script);
1126 JSString *scriptPath = ResolveRelativePath(parentcx, base, scriptName);
1127 if (!scriptPath)
1128 return NULL;
1130 // Post an InitEvent to run the initialization script.
1131 Event *event = InitEvent::create(parentcx, w, scriptPath);
1132 if (!event)
1133 return NULL;
1134 if (!w->events.push(event) || !w->threadPool->getWorkerQueue()->post(w)) {
1135 event->destroy(parentcx);
1136 JS_ReportOutOfMemory(parentcx);
1137 w->dispose();
1138 return NULL;
1140 return w;
1143 void
1144 Worker::processOneEvent()
1146 Event *event = NULL; /* init to shut GCC up */
1148 AutoLock hold1(lock);
1149 if (lockedCheckTermination() || events.empty())
1150 return;
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
1169 } else {
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);
1180 err = NULL;
1182 if (!err) {
1183 // FIXME - out of memory, probably should panic
1187 if (event)
1188 event->destroy(context);
1189 JS_ClearRuntimeThread(runtime);
1192 AutoLock hold2(lock);
1193 current = NULL;
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);
1202 JSBool
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())
1211 return false;
1214 jsval data = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
1215 Event *event = UpMessageEvent::create(cx, w, data);
1216 if (!event)
1217 return false;
1218 if (!w->parent->post(event)) {
1219 event->destroy(cx);
1220 JS_ReportOutOfMemory(cx);
1221 return false;
1223 JS_SET_RVAL(cx, vp, JSVAL_VOID);
1224 return true;
1227 JSBool
1228 Worker::jsPostMessageToChild(JSContext *cx, unsigned argc, jsval *vp)
1230 JSObject *workerobj = JS_THIS_OBJECT(cx, vp);
1231 if (!workerobj)
1232 return false;
1233 Worker *w = (Worker *) JS_GetInstancePrivate(cx, workerobj, &jsWorkerClass, JS_ARGV(cx, vp));
1234 if (!w) {
1235 if (!JS_IsExceptionPending(cx))
1236 JS_ReportError(cx, "Worker was shut down");
1237 return false;
1240 jsval data = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
1241 Event *event = DownMessageEvent::create(cx, w, data);
1242 if (!event)
1243 return false;
1244 if (!w->post(event)) {
1245 JS_ReportOutOfMemory(cx);
1246 return false;
1248 JS_SET_RVAL(cx, vp, JSVAL_VOID);
1249 return true;
1252 JSBool
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);
1258 if (!workerobj)
1259 return false;
1260 Worker *w = (Worker *) JS_GetInstancePrivate(cx, workerobj, &jsWorkerClass, JS_ARGV(cx, vp));
1261 if (!w)
1262 return !JS_IsExceptionPending(cx); // ok to terminate twice
1264 JSAutoSuspendRequest suspend(cx);
1265 w->setTerminateFlag();
1266 return true;
1269 void
1270 Event::trace(JSTracer *trc)
1272 if (recipient)
1273 recipient->trace(trc);
1274 if (child)
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),
1295 JS_FS_END
1298 ThreadPool *
1299 js::workers::init(JSContext *cx, WorkerHooks *hooks, JSObject *global, JSObject **rootp)
1301 return Worker::initWorkers(cx, hooks, global, rootp);
1304 void
1305 js::workers::terminateAll(ThreadPool *tp)
1307 tp->terminateAll();
1310 void
1311 js::workers::finish(JSContext *cx, ThreadPool *tp)
1313 if (MainQueue *mq = tp->getMainQueue()) {
1314 JS_ALWAYS_TRUE(mq->mainThreadWork(cx, true));
1315 tp->shutdown(cx);
1319 #endif /* JS_THREADSAFE */