LCOV - app.info - js/src/shell/jsworkers.cpp

LCOV - code coverage report
| Current view: | directory - js/src/shell - jsworkers.cpp (source / functions) |  | Found | Hit | Coverage |
| Test: | app.info | Lines: | 628 | 59 | 9.4 %
| Date: | 2012-04-07 | Functions: | 138 | 15 | 10.9 %

           1                 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
           2                 :  * vim: set ts=8 sw=4 et tw=99:
           3                 :  *
           4                 :  * ***** BEGIN LICENSE BLOCK *****
           5                 :  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
           6                 :  *
           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/
          11                 :  *
          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.
          16                 :  *
          17                 :  * The Original Code is JavaScript shell workers.
          18                 :  *
          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.
          23                 :  *
          24                 :  * Contributor(s):
          25                 :  *   Jason Orendorff <jorendorff@mozilla.com>
          26                 :  *
          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.
          38                 :  *
          39                 :  * ***** END LICENSE BLOCK ***** */
          40                 :
          41                 : #ifdef JS_THREADSAFE
          42                 :
          43                 : #include "mozilla/Attributes.h"
          44                 :
          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"
          55                 :
          56                 : extern size_t gMaxStackSize;
          57                 :
          58                 : class AutoLock
          59                 : {
          60                 :   private:
          61                 :     PRLock *lock;
          62                 :
          63                 :   public:
          64               0 :     AutoLock(PRLock *lock) : lock(lock) { PR_Lock(lock); }
          65               0 :     ~AutoLock() { PR_Unlock(lock); }
          66                 : };
          67                 :
          68                 : /*
          69                 :  * JavaScript shell workers.
          70                 :  *
          71                 :  * == Object lifetime rules ==
          72                 :  *
          73                 :  *   - The ThreadPool lasts from init() to finish().
          74                 :  *
          75                 :  *   - The ThreadPool owns the MainQueue and the WorkerQueue. Those live from
          76                 :  *     the time the first Worker is created until finish().
          77                 :  *
          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.
          83                 :  *
          84                 :  *   - finish() continues to wait for events until all threads are idle.
          85                 :  *
          86                 :  * Event objects, however, are basically C++-only. The JS Event objects are
          87                 :  * just plain old JSObjects. They don't keep anything alive.
          88                 :  *
          89                 :  * == Locking scheme ==
          90                 :  *
          91                 :  * When mixing mutexes and the JSAPI request model, there are two choices:
          92                 :  *
          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.
          95                 :  *
          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.
          99                 :  *
         100                 :  * This code uses the latter approach for all locks.
         101                 :  *
         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.)
         105                 :  */
         106                 :
         107                 : namespace js {
         108                 : namespace workers {
         109                 :
         110                 : template <class T, class AllocPolicy>
         111               0 : class Queue {
         112                 :     typedef Vector<T, 4, AllocPolicy> Vec;
         113                 :     Vec v1;
         114                 :     Vec v2;
         115                 :     Vec *front;
         116                 :     Vec *back;
         117                 :
         118                 :     Queue(const Queue &) MOZ_DELETE;
         119                 :     Queue & operator=(const Queue &) MOZ_DELETE;
         120                 :
         121                 :   public:
         122               0 :     Queue() : front(&v1), back(&v2) {}
         123               0 :     bool push(T t) { return back->append(t); }
         124               0 :     bool empty() { return front->empty() && back->empty(); }
         125                 :
         126               0 :     T pop() {
         127               0 :         if (front->empty()) {
         128               0 :             js::Reverse(back->begin(), back->end());
         129               0 :             Vec *tmp = front;
         130               0 :             front = back;
         131               0 :             back = tmp;
         132                 :         }
         133               0 :         T item = front->back();
         134               0 :         front->popBack();
         135               0 :         return item;
         136                 :     }
         137                 :
         138               0 :     void clear() {
         139               0 :         v1.clear();
         140               0 :         v2.clear();
         141               0 :     }
         142                 :
         143               0 :     void trace(JSTracer *trc) {
         144               0 :         for (T *p = v1.begin(); p != v1.end(); p++)
         145               0 :             (*p)->trace(trc);
         146               0 :         for (T *p = v2.begin(); p != v2.end(); p++)
         147               0 :             (*p)->trace(trc);
         148               0 :     }
         149                 : };
         150                 :
         151                 : class Event;
         152                 : class ThreadPool;
         153                 : class Worker;
         154                 :
         155               0 : class WorkerParent {
         156                 :   protected:
         157                 :     typedef HashSet<Worker *, DefaultHasher<Worker *>, SystemAllocPolicy> ChildSet;
         158                 :     ChildSet children;
         159                 :
         160               0 :     bool initWorkerParent() { return children.init(8); }
         161                 :
         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;
         167                 :
         168               0 :     bool addChild(Worker *w) {
         169               0 :         AutoLock hold(getLock());
         170               0 :         return children.put(w);
         171                 :     }
         172                 :
         173                 :     // This must be called only from GC or when all threads are shut down. It
         174                 :     // does not bother with locking.
         175               0 :     void removeChild(Worker *w) {
         176               0 :         ChildSet::Ptr p = children.lookup(w);
         177               0 :         JS_ASSERT(p);
         178               0 :         children.remove(p);
         179               0 :     }
         180                 :
         181                 :     void disposeChildren();
         182                 :     void notifyTerminating();
         183                 : };
         184                 :
         185                 : template <class T>
         186                 : class ThreadSafeQueue
         187                 : {
         188                 :   protected:
         189                 :     Queue<T, SystemAllocPolicy> queue;
         190                 :     PRLock *lock;
         191                 :     PRCondVar *condvar;
         192                 :     bool closed;
         193                 :
         194                 :   private:
         195                 :     Vector<T, 8, SystemAllocPolicy> busy;
         196                 :
         197                 :   protected:
         198               0 :     ThreadSafeQueue() : lock(NULL), condvar(NULL), closed(false) {}
         199                 :
         200               0 :     ~ThreadSafeQueue() {
         201               0 :         if (condvar)
         202               0 :             PR_DestroyCondVar(condvar);
         203               0 :         if (lock)
         204               0 :             PR_DestroyLock(lock);
         205               0 :     }
         206                 :
         207                 :     // Called by take() with the lock held.
         208               0 :     virtual bool shouldStop() { return closed; }
         209                 :
         210                 :   public:
         211               0 :     bool initThreadSafeQueue() {
         212               0 :         JS_ASSERT(!lock);
         213               0 :         JS_ASSERT(!condvar);
         214               0 :         return (lock = PR_NewLock()) && (condvar = PR_NewCondVar(lock));
         215                 :     }
         216                 :
         217               0 :     bool post(T t) {
         218               0 :         AutoLock hold(lock);
         219               0 :         if (closed)
         220               0 :             return false;
         221               0 :         if (queue.empty())
         222               0 :             PR_NotifyAllCondVar(condvar);
         223               0 :         return queue.push(t);
         224                 :     }
         225                 :
         226               0 :     void close() {
         227               0 :         AutoLock hold(lock);
         228               0 :         closed = true;
         229               0 :         queue.clear();
         230               0 :         PR_NotifyAllCondVar(condvar);
         231               0 :     }
         232                 :
         233                 :     // The caller must hold the lock.
         234               0 :     bool take(T *t) {
         235               0 :         while (queue.empty()) {
         236               0 :             if (shouldStop())
         237               0 :                 return false;
         238               0 :             PR_WaitCondVar(condvar, PR_INTERVAL_NO_TIMEOUT);
         239                 :         }
         240               0 :         *t = queue.pop();
         241               0 :         busy.append(*t);
         242               0 :         return true;
         243                 :     }
         244                 :
         245                 :     // The caller must hold the lock.
         246               0 :     void drop(T item) {
         247               0 :         for (T *p = busy.begin(); p != busy.end(); p++) {
         248               0 :             if (*p == item) {
         249               0 :                 *p = busy.back();
         250               0 :                 busy.popBack();
         251                 :                 return;
         252                 :             }
         253                 :         }
         254               0 :         JS_NOT_REACHED("removeBusy");
         255                 :     }
         256                 :
         257               0 :     bool lockedIsIdle() { return busy.empty() && queue.empty(); }
         258                 :
         259               0 :     bool isIdle() {
         260               0 :         AutoLock hold(lock);
         261               0 :         return lockedIsIdle();
         262                 :     }
         263                 :
         264               0 :     void wake() {
         265               0 :         AutoLock hold(lock);
         266               0 :         PR_NotifyAllCondVar(condvar);
         267               0 :     }
         268                 :
         269               0 :     void trace(JSTracer *trc) {
         270               0 :         AutoLock hold(lock);
         271               0 :         for (T *p = busy.begin(); p != busy.end(); p++)
         272               0 :             (*p)->trace(trc);
         273               0 :         queue.trace(trc);
         274               0 :     }
         275                 : };
         276                 :
         277                 : class MainQueue;
         278                 :
         279                 : class Event
         280               0 : {
         281                 :   protected:
         282               0 :     virtual ~Event() { JS_ASSERT(!data); }
         283                 :
         284                 :     WorkerParent *recipient;
         285                 :     Worker *child;
         286                 :     uint64_t *data;
         287                 :     size_t nbytes;
         288                 :
         289                 :   public:
         290                 :     enum Result { fail = JS_FALSE, ok = JS_TRUE, forwardToParent };
         291                 :
         292               0 :     virtual void destroy(JSContext *cx) {
         293               0 :         JS_free(cx, data);
         294                 : #ifdef DEBUG
         295               0 :         data = NULL;
         296                 : #endif
         297               0 :         delete this;
         298               0 :     }
         299                 :
         300               0 :     void setChildAndRecipient(Worker *aChild, WorkerParent *aRecipient) {
         301               0 :         child = aChild;
         302               0 :         recipient = aRecipient;
         303               0 :     }
         304                 :
         305               0 :     bool deserializeData(JSContext *cx, jsval *vp) {
         306                 :         return !!JS_ReadStructuredClone(cx, data, nbytes, JS_STRUCTURED_CLONE_VERSION, vp,
         307               0 :                                         NULL, NULL);
         308                 :     }
         309                 :
         310                 :     virtual Result process(JSContext *cx) = 0;
         311                 :
         312                 :     inline void trace(JSTracer *trc);
         313                 :
         314                 :     template <class EventType>
         315               0 :     static EventType *createEvent(JSContext *cx, WorkerParent *recipient, Worker *child,
         316                 :                                   jsval v)
         317                 :     {
         318                 :         uint64_t *data;
         319                 :         size_t nbytes;
         320               0 :         if (!JS_WriteStructuredClone(cx, v, &data, &nbytes, NULL, NULL))
         321               0 :             return NULL;
         322                 :
         323               0 :         EventType *event = new EventType;
         324               0 :         if (!event) {
         325               0 :             JS_ReportOutOfMemory(cx);
         326               0 :             return NULL;
         327                 :         }
         328               0 :         event->recipient = recipient;
         329               0 :         event->child = child;
         330               0 :         event->data = data;
         331               0 :         event->nbytes = nbytes;
         332               0 :         return event;
         333                 :     }
         334                 :
         335               0 :     Result dispatch(JSContext *cx, JSObject *thisobj, const char *dataPropName,
         336                 :                     const char *methodName, Result noHandler)
         337                 :     {
         338               0 :         if (!data)
         339               0 :             return fail;
         340                 :
         341                 :         JSBool found;
         342               0 :         if (!JS_HasProperty(cx, thisobj, methodName, &found))
         343               0 :             return fail;
         344               0 :         if (!found)
         345               0 :             return noHandler;
         346                 :
         347                 :         // Create event object.
         348                 :         jsval v;
         349               0 :         if (!deserializeData(cx, &v))
         350               0 :             return fail;
         351               0 :         JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL);
         352               0 :         if (!obj || !JS_DefineProperty(cx, obj, dataPropName, v, NULL, NULL, 0))
         353               0 :             return fail;
         354                 :
         355                 :         // Call event handler.
         356               0 :         jsval argv[1] = { OBJECT_TO_JSVAL(obj) };
         357               0 :         jsval rval = JSVAL_VOID;
         358               0 :         return Result(JS_CallFunctionName(cx, thisobj, methodName, 1, argv, &rval));
         359                 :     }
         360                 : };
         361                 :
         362                 : typedef ThreadSafeQueue<Event *> EventQueue;
         363                 :
         364                 : class MainQueue MOZ_FINAL : public EventQueue, public WorkerParent
         365                 : {
         366                 :   private:
         367                 :     ThreadPool *threadPool;
         368                 :
         369                 :   public:
         370               0 :     explicit MainQueue(ThreadPool *tp) : threadPool(tp) {}
         371                 :
         372               0 :     ~MainQueue() {
         373               0 :         JS_ASSERT(queue.empty());
         374               0 :     }
         375                 :
         376               0 :     bool init() { return initThreadSafeQueue() && initWorkerParent(); }
         377                 :
         378               0 :     void destroy(JSContext *cx) {
         379               0 :         while (!queue.empty())
         380               0 :             queue.pop()->destroy(cx);
         381               0 :         delete this;
         382               0 :     }
         383                 :
         384               0 :     virtual PRLock *getLock() { return lock; }
         385               0 :     virtual ThreadPool *getThreadPool() { return threadPool; }
         386                 :
         387                 :   protected:
         388                 :     virtual bool shouldStop();
         389                 :
         390                 :   public:
         391               0 :     virtual bool post(Event *event) { return EventQueue::post(event); }
         392                 :
         393                 :     virtual void trace(JSTracer *trc);
         394                 :
         395               0 :     void traceChildren(JSTracer *trc) { EventQueue::trace(trc); }
         396                 :
         397               0 :     JSBool mainThreadWork(JSContext *cx, bool continueOnError) {
         398               0 :         JSAutoSuspendRequest suspend(cx);
         399               0 :         AutoLock hold(lock);
         400                 :
         401                 :         Event *event;
         402               0 :         while (take(&event)) {
         403               0 :             PR_Unlock(lock);
         404                 :             Event::Result result;
         405                 :             {
         406               0 :                 JSAutoRequest req(cx);
         407               0 :                 result = event->process(cx);
         408               0 :                 if (result == Event::forwardToParent) {
         409                 :                     // FIXME - pointlessly truncates the string to 8 bits
         410                 :                     jsval data;
         411               0 :                     JSAutoByteString bytes;
         412               0 :                     if (event->deserializeData(cx, &data) &&
         413               0 :                         JSVAL_IS_STRING(data) &&
         414               0 :                         bytes.encode(cx, JSVAL_TO_STRING(data))) {
         415               0 :                         JS_ReportError(cx, "%s", bytes.ptr());
         416                 :                     } else {
         417               0 :                         JS_ReportOutOfMemory(cx);
         418                 :                     }
         419               0 :                     result = Event::fail;
         420                 :                 }
         421               0 :                 if (result == Event::fail && continueOnError) {
         422               0 :                     if (JS_IsExceptionPending(cx) && !JS_ReportPendingException(cx))
         423               0 :                         JS_ClearPendingException(cx);
         424               0 :                     result = Event::ok;
         425                 :                 }
         426                 :             }
         427               0 :             PR_Lock(lock);
         428               0 :             drop(event);
         429               0 :             event->destroy(cx);
         430               0 :             if (result != Event::ok)
         431               0 :                 return false;
         432                 :         }
         433               0 :         return true;
         434                 :     }
         435                 : };
         436                 :
         437                 : /*
         438                 :  * A queue of workers.
         439                 :  *
         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.
         442                 :  */
         443                 : class WorkerQueue MOZ_FINAL : public ThreadSafeQueue<Worker *>
         444               0 : {
         445                 :   private:
         446                 :     MainQueue *main;
         447                 :
         448                 :   public:
         449               0 :     explicit WorkerQueue(MainQueue *main) : main(main) {}
         450                 :
         451                 :     void work();
         452                 : };
         453                 :
         454                 : /* The top-level object that owns everything else. */
         455                 : class ThreadPool
         456                 : {
         457                 :   private:
         458                 :     enum { threadCount = 6 };
         459                 :
         460                 :     JSObject *obj;
         461                 :     WorkerHooks *hooks;
         462                 :     MainQueue *mq;
         463                 :     WorkerQueue *wq;
         464                 :     PRThread *threads[threadCount];
         465                 :     int32_t terminating;
         466                 :
         467                 :     static JSClass jsClass;
         468                 :
         469               0 :     static void start(void* arg) {
         470               0 :         ((WorkerQueue *) arg)->work();
         471               0 :     }
         472                 :
         473           18666 :     explicit ThreadPool(WorkerHooks *hooks) : hooks(hooks), mq(NULL), wq(NULL), terminating(0) {
         474          130662 :         for (int i = 0; i < threadCount; i++)
         475          111996 :             threads[i] = NULL;
         476           18666 :     }
         477                 :
         478                 :   public:
         479           18666 :     ~ThreadPool() {
         480           18666 :         JS_ASSERT(!mq);
         481           18666 :         JS_ASSERT(!wq);
         482           18666 :         JS_ASSERT(!threads[0]);
         483           18666 :     }
         484                 :
         485           18666 :     static ThreadPool *create(JSContext *cx, WorkerHooks *hooks) {
         486           18666 :         ThreadPool *tp = new ThreadPool(hooks);
         487           18666 :         if (!tp) {
         488               0 :             JS_ReportOutOfMemory(cx);
         489               0 :             return NULL;
         490                 :         }
         491                 :
         492           18666 :         JSObject *obj = JS_NewObject(cx, &jsClass, NULL, NULL);
         493           18666 :         if (!obj) {
         494               0 :             delete tp;
         495               0 :             return NULL;
         496                 :         }
         497           18666 :         JS_SetPrivate(obj, tp);
         498           18666 :         tp->obj = obj;
         499           18666 :         return tp;
         500                 :     }
         501                 :
         502           18666 :     JSObject *asObject() { return obj; }
         503               0 :     WorkerHooks *getHooks() { return hooks; }
         504               0 :     WorkerQueue *getWorkerQueue() { return wq; }
         505           18666 :     MainQueue *getMainQueue() { return mq; }
         506               0 :     bool isTerminating() { return terminating != 0; }
         507                 :
         508                 :     /*
         509                 :      * Main thread only. Requires request (to prevent GC, which could see the
         510                 :      * object in an inconsistent state).
         511                 :      */
         512               0 :     bool start(JSContext *cx) {
         513               0 :         JS_ASSERT(!mq && !wq);
         514               0 :         mq = new MainQueue(this);
         515               0 :         if (!mq || !mq->init()) {
         516               0 :             mq->destroy(cx);
         517               0 :             mq = NULL;
         518               0 :             return false;
         519                 :         }
         520               0 :         wq = new WorkerQueue(mq);
         521               0 :         if (!wq || !wq->initThreadSafeQueue()) {
         522               0 :             delete wq;
         523               0 :             wq = NULL;
         524               0 :             mq->destroy(cx);
         525               0 :             mq = NULL;
         526               0 :             return false;
         527                 :         }
         528               0 :         JSAutoSuspendRequest suspend(cx);
         529               0 :         bool ok = true;
         530               0 :         for (int i = 0; i < threadCount; i++) {
         531                 :             threads[i] = PR_CreateThread(PR_USER_THREAD, start, wq, PR_PRIORITY_NORMAL,
         532               0 :                                          PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
         533               0 :             if (!threads[i]) {
         534               0 :                 shutdown(cx);
         535               0 :                 ok = false;
         536               0 :                 break;
         537                 :             }
         538                 :         }
         539               0 :         return ok;
         540                 :     }
         541                 :
         542              27 :     void terminateAll() {
         543                 :         // See comment about JS_ATOMIC_SET in the implementation of
         544                 :         // JS_TriggerOperationCallback.
         545              27 :         JS_ATOMIC_SET(&terminating, 1);
         546              27 :         if (mq)
         547               0 :             mq->notifyTerminating();
         548              27 :     }
         549                 :
         550                 :     /* This context is used only to free memory. */
         551               0 :     void shutdown(JSContext *cx) {
         552               0 :         wq->close();
         553               0 :         for (int i = 0; i < threadCount; i++) {
         554               0 :             if (threads[i]) {
         555               0 :                 PR_JoinThread(threads[i]);
         556               0 :                 threads[i] = NULL;
         557                 :             }
         558                 :         }
         559                 :
         560               0 :         delete wq;
         561               0 :         wq = NULL;
         562                 :
         563               0 :         mq->disposeChildren();
         564               0 :         mq->destroy(cx);
         565               0 :         mq = NULL;
         566               0 :         terminating = 0;
         567               0 :     }
         568                 :
         569                 :   private:
         570           22847 :     static void jsTraceThreadPool(JSTracer *trc, JSObject *obj) {
         571           22847 :         ThreadPool *tp = unwrap(obj);
         572           22847 :         if (tp->mq) {
         573               0 :             tp->mq->traceChildren(trc);
         574               0 :             tp->wq->trace(trc);
         575                 :         }
         576           22847 :     }
         577                 :
         578                 :
         579           18666 :     static void jsFinalize(JSFreeOp *fop, JSObject *obj) {
         580           18666 :         if (ThreadPool *tp = unwrap(obj))
         581           18666 :             delete tp;
         582           18666 :     }
         583                 :
         584                 :   public:
         585           41513 :     static ThreadPool *unwrap(JSObject *obj) {
         586           41513 :         JS_ASSERT(JS_GetClass(obj) == &jsClass);
         587           41513 :         return (ThreadPool *) JS_GetPrivate(obj);
         588                 :     }
         589                 : };
         590                 :
         591                 : /*
         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())
         598                 :  *
         599                 :  * Separately, there is a terminateFlag that other threads can set
         600                 :  * asynchronously to tell the Worker to terminate.
         601                 :  */
         602                 : class Worker MOZ_FINAL : public WorkerParent
         603                 : {
         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;
         615                 :
         616                 :     static JSClass jsWorkerClass;
         617                 :
         618               0 :     Worker()
         619                 :         : threadPool(NULL), parent(NULL), object(NULL), runtime(NULL),
         620               0 :           context(NULL), lock(NULL), current(NULL), terminated(false), terminateFlag(0) {}
         621                 :
         622               0 :     bool init(JSContext *parentcx, WorkerParent *parent, JSObject *obj) {
         623               0 :         JS_ASSERT(!threadPool && !this->parent && !object && !lock);
         624                 :
         625               0 :         if (!initWorkerParent() || !parent->addChild(this))
         626               0 :             return false;
         627               0 :         threadPool = parent->getThreadPool();
         628               0 :         this->parent = parent;
         629               0 :         this->object = obj;
         630               0 :         lock = PR_NewLock();
         631               0 :         if (!lock || !createRuntime(parentcx) || !createContext(parentcx, parent))
         632               0 :             return false;
         633               0 :         JS_SetPrivate(obj, this);
         634               0 :         return true;
         635                 :     }
         636                 :
         637               0 :     bool createRuntime(JSContext *parentcx) {
         638               0 :         runtime = JS_NewRuntime(1L * 1024L * 1024L);
         639               0 :         if (!runtime) {
         640               0 :             JS_ReportOutOfMemory(parentcx);
         641               0 :             return false;
         642                 :         }
         643               0 :         JS_ClearRuntimeThread(runtime);
         644               0 :         return true;
         645                 :     }
         646                 :
         647               0 :     bool createContext(JSContext *parentcx, WorkerParent *parent) {
         648               0 :         JSAutoSetRuntimeThread guard(runtime);
         649               0 :         context = JS_NewContext(runtime, 8192);
         650               0 :         if (!context)
         651               0 :             return false;
         652                 :
         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               0 :         JS_SetOptions(context, JS_GetOptions(parentcx) | JSOPTION_UNROOTED_GLOBAL |
         658               0 :                                                          JSOPTION_DONT_REPORT_UNCAUGHT);
         659               0 :         JS_SetVersion(context, JS_GetVersion(parentcx));
         660               0 :         JS_SetContextPrivate(context, this);
         661               0 :         JS_SetOperationCallback(context, jsOperationCallback);
         662               0 :         JS_BeginRequest(context);
         663                 :
         664               0 :         JSObject *global = threadPool->getHooks()->newGlobalObject(context);
         665                 :         JSObject *post, *proto, *ctor;
         666               0 :         if (!global)
         667               0 :             goto bad;
         668               0 :         JS_SetGlobalObject(context, global);
         669                 :
         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               0 :                                                   (JSNative) jsPostMessageToParent, 1, 0));
         678               0 :         if (!post)
         679               0 :             goto bad;
         680                 :
         681               0 :         js::SetFunctionNativeReserved(post, 0, PRIVATE_TO_JSVAL(this));
         682                 :
         683                 :         proto = js::InitClassWithReserved(context, global, NULL, &jsWorkerClass, jsConstruct, 1,
         684               0 :                                           NULL, jsMethods, NULL, NULL);
         685               0 :         if (!proto)
         686               0 :             goto bad;
         687                 :
         688               0 :         ctor = JS_GetConstructor(context, proto);
         689               0 :         if (!ctor)
         690               0 :             goto bad;
         691                 :
         692               0 :         js::SetFunctionNativeReserved(post, 0, PRIVATE_TO_JSVAL(this));
         693                 :
         694               0 :         JS_EndRequest(context);
         695               0 :         return true;
         696                 :
         697                 :     bad:
         698               0 :         JS_EndRequest(context);
         699               0 :         JS_DestroyContext(context);
         700               0 :         context = NULL;
         701               0 :         return false;
         702                 :     }
         703                 :
         704           22847 :     static void jsTraceWorker(JSTracer *trc, JSObject *obj) {
         705           22847 :         JS_ASSERT(JS_GetClass(obj) == &jsWorkerClass);
         706           22847 :         if (Worker *w = (Worker *) JS_GetPrivate(obj)) {
         707               0 :             w->parent->trace(trc);
         708               0 :             w->events.trace(trc);
         709               0 :             if (w->current)
         710               0 :                 w->current->trace(trc);
         711               0 :             JS_CALL_OBJECT_TRACER(trc, JS_GetGlobalObject(w->context), "Worker global");
         712                 :         }
         713           22847 :     }
         714                 :
         715           18666 :     static void jsFinalize(JSFreeOp *fop, JSObject *obj) {
         716           18666 :         JS_ASSERT(JS_GetClass(obj) == &jsWorkerClass);
         717           18666 :         if (Worker *w = (Worker *) JS_GetPrivate(obj))
         718               0 :             delete w;
         719           18666 :     }
         720                 :
         721               0 :     static JSBool jsOperationCallback(JSContext *cx) {
         722               0 :         Worker *w = (Worker *) JS_GetContextPrivate(cx);
         723               0 :         JSAutoSuspendRequest suspend(cx);  // avoid nesting w->lock in a request
         724               0 :         return !w->checkTermination();
         725                 :     }
         726                 :
         727                 :     static JSBool jsResolveGlobal(JSContext *cx, JSObject *obj, jsid id, unsigned flags,
         728                 :                                   JSObject **objp)
         729                 :     {
         730                 :         JSBool resolved;
         731                 :
         732                 :         if (!JS_ResolveStandardClass(cx, obj, id, &resolved))
         733                 :             return false;
         734                 :         if (resolved)
         735                 :             *objp = obj;
         736                 :
         737                 :         return true;
         738                 :     }
         739                 :
         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);
         743                 :
         744               0 :     bool checkTermination() {
         745               0 :         AutoLock hold(lock);
         746               0 :         return lockedCheckTermination();
         747                 :     }
         748                 :
         749               0 :     bool lockedCheckTermination() {
         750               0 :         if (terminateFlag || threadPool->isTerminating()) {
         751               0 :             terminateSelf();
         752               0 :             terminateFlag = 0;
         753                 :         }
         754               0 :         return terminated;
         755                 :     }
         756                 :
         757                 :     // Caller must hold the lock.
         758               0 :     void terminateSelf() {
         759               0 :         terminated = true;
         760               0 :         while (!events.empty())
         761               0 :             events.pop()->destroy(context);
         762                 :
         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               0 :         for (ChildSet::Enum e(children); !e.empty(); e.popFront())
         767               0 :             e.front()->setTerminateFlag();  // note: nesting locks here
         768               0 :     }
         769                 :
         770                 :   public:
         771               0 :     ~Worker() {
         772               0 :         if (parent)
         773               0 :             parent->removeChild(this);
         774               0 :         dispose();
         775               0 :     }
         776                 :
         777               0 :     void dispose() {
         778               0 :         JS_ASSERT(!current);
         779               0 :         while (!events.empty())
         780               0 :             events.pop()->destroy(context);
         781               0 :         if (lock) {
         782               0 :             PR_DestroyLock(lock);
         783               0 :             lock = NULL;
         784                 :         }
         785               0 :         if (runtime)
         786               0 :             JS_SetRuntimeThread(runtime);
         787               0 :         if (context) {
         788               0 :             JS_DestroyContextNoGC(context);
         789               0 :             context = NULL;
         790                 :         }
         791               0 :         if (runtime) {
         792               0 :             JS_DestroyRuntime(runtime);
         793               0 :             runtime = NULL;
         794                 :         }
         795               0 :         object = NULL;
         796                 :
         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               0 :         parent = NULL;
         801               0 :         disposeChildren();
         802               0 :     }
         803                 :
         804                 :     static Worker *create(JSContext *parentcx, WorkerParent *parent,
         805                 :                           JSString *scriptName, JSObject *obj);
         806                 :
         807               0 :     JSObject *asObject() { return object; }
         808                 :
         809               0 :     JSObject *getGlobal() { return JS_GetGlobalObject(context); }
         810                 :
         811               0 :     WorkerParent *getParent() { return parent; }
         812                 :
         813               0 :     virtual PRLock *getLock() { return lock; }
         814                 :
         815               0 :     virtual ThreadPool *getThreadPool() { return threadPool; }
         816                 :
         817               0 :     bool post(Event *event) {
         818               0 :         AutoLock hold(lock);
         819               0 :         if (terminated)
         820               0 :             return false;
         821               0 :         if (!current && events.empty() && !threadPool->getWorkerQueue()->post(this))
         822               0 :             return false;
         823               0 :         return events.push(event);
         824                 :     }
         825                 :
         826               0 :     void setTerminateFlag() {
         827               0 :         AutoLock hold(lock);
         828               0 :         terminateFlag = true;
         829               0 :         if (current)
         830               0 :             JS_TriggerOperationCallback(runtime);
         831               0 :     }
         832                 :
         833               0 :     void notifyTerminating() {
         834               0 :         setTerminateFlag();
         835               0 :         WorkerParent::notifyTerminating();
         836               0 :     }
         837                 :
         838                 :     void processOneEvent();
         839                 :
         840                 :     /* Trace method to be called from C++. */
         841               0 :     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               0 :         JS_CALL_OBJECT_TRACER(trc, object, "queued Worker");
         845               0 :     }
         846                 :
         847               0 :     static bool getWorkerParentFromConstructor(JSContext *cx, JSObject *ctor, WorkerParent **p) {
         848               0 :         jsval v = js::GetFunctionNativeReserved(ctor, 0);
         849               0 :         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               0 :             v = js::GetFunctionNativeReserved(ctor, 1);
         854               0 :             ThreadPool *threadPool = (ThreadPool *) JSVAL_TO_PRIVATE(v);
         855               0 :             if (!threadPool->start(cx))
         856               0 :                 return false;
         857               0 :             WorkerParent *parent = threadPool->getMainQueue();
         858               0 :             js::SetFunctionNativeReserved(ctor, 0, PRIVATE_TO_JSVAL(parent));
         859               0 :             *p = parent;
         860               0 :             return true;
         861                 :         }
         862               0 :         *p = (WorkerParent *) JSVAL_TO_PRIVATE(v);
         863               0 :         return true;
         864                 :     }
         865                 :
         866               0 :     static JSBool jsConstruct(JSContext *cx, unsigned argc, jsval *vp) {
         867                 :         /*
         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.
         871                 :          */
         872               0 :         js::DisableIncrementalGC(JS_GetRuntime(cx));
         873                 :
         874                 :         WorkerParent *parent;
         875               0 :         if (!getWorkerParentFromConstructor(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), &parent))
         876               0 :             return false;
         877                 :
         878                 :
         879               0 :         JSString *scriptName = JS_ValueToString(cx, argc ? JS_ARGV(cx, vp)[0] : JSVAL_VOID);
         880               0 :         if (!scriptName)
         881               0 :             return false;
         882                 :
         883               0 :         JSObject *obj = JS_NewObject(cx, &jsWorkerClass, NULL, NULL);
         884               0 :         if (!obj || !create(cx, parent, scriptName, obj))
         885               0 :             return false;
         886               0 :         JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
         887               0 :         return true;
         888                 :     }
         889                 :
         890                 :     static JSFunctionSpec jsMethods[3];
         891                 :     static JSFunctionSpec jsStaticMethod[2];
         892                 :
         893           18666 :     static ThreadPool *initWorkers(JSContext *cx, WorkerHooks *hooks, JSObject *global,
         894                 :                                    JSObject **objp) {
         895                 :         // Create the ThreadPool object and its JSObject wrapper.
         896           18666 :         ThreadPool *threadPool = ThreadPool::create(cx, hooks);
         897           18666 :         if (!threadPool)
         898               0 :             return NULL;
         899                 :
         900                 :         // Root the ThreadPool JSObject early.
         901           18666 :         *objp = threadPool->asObject();
         902                 :
         903                 :         // Create the Worker constructor.
         904                 :         JSObject *proto = js::InitClassWithReserved(cx, global, NULL, &jsWorkerClass,
         905                 :                                                     jsConstruct, 1,
         906           18666 :                                                     NULL, jsMethods, NULL, NULL);
         907           18666 :         if (!proto)
         908               0 :             return NULL;
         909                 :
         910                 :         // Stash a pointer to the ThreadPool in constructor reserved slot 1.
         911                 :         // It will be used later when lazily creating the MainQueue.
         912           18666 :         JSObject *ctor = JS_GetConstructor(cx, proto);
         913           18666 :         js::SetFunctionNativeReserved(ctor, 1, PRIVATE_TO_JSVAL(threadPool));
         914                 :
         915           18666 :         return threadPool;
         916                 :     }
         917                 : };
         918                 :
         919                 : class InitEvent : public Event
         920               0 : {
         921                 :   public:
         922               0 :     static InitEvent *create(JSContext *cx, Worker *worker, JSString *scriptName) {
         923               0 :         return createEvent<InitEvent>(cx, worker, worker, STRING_TO_JSVAL(scriptName));
         924                 :     }
         925                 :
         926               0 :     Result process(JSContext *cx) {
         927                 :         jsval s;
         928               0 :         if (!deserializeData(cx, &s))
         929               0 :             return fail;
         930               0 :         JS_ASSERT(JSVAL_IS_STRING(s));
         931               0 :         JSAutoByteString filename(cx, JSVAL_TO_STRING(s));
         932               0 :         if (!filename)
         933               0 :             return fail;
         934                 :
         935               0 :         JSScript *script = JS_CompileUTF8File(cx, child->getGlobal(), filename.ptr());
         936               0 :         if (!script)
         937               0 :             return fail;
         938                 :
         939               0 :         AutoValueRooter rval(cx);
         940               0 :         JSBool ok = JS_ExecuteScript(cx, child->getGlobal(), script, rval.addr());
         941               0 :         return Result(ok);
         942                 :     }
         943                 : };
         944                 :
         945                 : class DownMessageEvent : public Event
         946               0 : {
         947                 :   public:
         948               0 :     static DownMessageEvent *create(JSContext *cx, Worker *child, jsval data) {
         949               0 :         return createEvent<DownMessageEvent>(cx, child, child, data);
         950                 :     }
         951                 :
         952               0 :     Result process(JSContext *cx) {
         953               0 :         return dispatch(cx, child->getGlobal(), "data", "onmessage", ok);
         954                 :     }
         955                 : };
         956                 :
         957                 : class UpMessageEvent : public Event
         958               0 : {
         959                 :   public:
         960               0 :     static UpMessageEvent *create(JSContext *cx, Worker *child, jsval data) {
         961               0 :         return createEvent<UpMessageEvent>(cx, child->getParent(), child, data);
         962                 :     }
         963                 :
         964               0 :     Result process(JSContext *cx) {
         965               0 :         return dispatch(cx, child->asObject(), "data", "onmessage", ok);
         966                 :     }
         967                 : };
         968                 :
         969                 : class ErrorEvent : public Event
         970               0 : {
         971                 :   public:
         972               0 :     static ErrorEvent *create(JSContext *cx, Worker *child) {
         973               0 :         JSString *data = NULL;
         974                 :         jsval exc;
         975               0 :         if (JS_GetPendingException(cx, &exc)) {
         976               0 :             AutoValueRooter tvr(cx, exc);
         977               0 :             JS_ClearPendingException(cx);
         978                 :
         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               0 :             if (JSVAL_IS_OBJECT(exc)) {
         983                 :                 jsval msg;
         984               0 :                 if (!JS_GetProperty(cx, JSVAL_TO_OBJECT(exc), "message", &msg))
         985               0 :                     JS_ClearPendingException(cx);
         986               0 :                 else if (JSVAL_IS_STRING(msg))
         987               0 :                     data = JSVAL_TO_STRING(msg);
         988                 :             }
         989               0 :             if (!data) {
         990               0 :                 data = JS_ValueToString(cx, exc);
         991               0 :                 if (!data)
         992               0 :                     return NULL;
         993                 :             }
         994                 :         }
         995                 :         return createEvent<ErrorEvent>(cx, child->getParent(), child,
         996               0 :                                        data ? STRING_TO_JSVAL(data) : JSVAL_VOID);
         997                 :     }
         998                 :
         999               0 :     Result process(JSContext *cx) {
        1000               0 :         return dispatch(cx, child->asObject(), "message", "onerror", forwardToParent);
        1001                 :     }
        1002                 : };
        1003                 :
        1004                 : } /* namespace workers */
        1005                 : } /* namespace js */
        1006                 :
        1007                 : using namespace js::workers;
        1008                 :
        1009                 : void
        1010               0 : WorkerParent::disposeChildren()
        1011                 : {
        1012               0 :     for (ChildSet::Enum e(children); !e.empty(); e.popFront()) {
        1013               0 :         e.front()->dispose();
        1014               0 :         e.removeFront();
        1015                 :     }
        1016               0 : }
        1017                 :
        1018                 : void
        1019               0 : WorkerParent::notifyTerminating()
        1020                 : {
        1021               0 :     AutoLock hold(getLock());
        1022               0 :     for (ChildSet::Range r = children.all(); !r.empty(); r.popFront())
        1023               0 :         r.front()->notifyTerminating();
        1024               0 : }
        1025                 :
        1026                 : bool
        1027               0 : MainQueue::shouldStop()
        1028                 : {
        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               0 :     return closed || threadPool->getWorkerQueue()->isIdle();
        1035                 : }
        1036                 :
        1037                 : void
        1038               0 : MainQueue::trace(JSTracer *trc)
        1039                 : {
        1040               0 :      JS_CALL_OBJECT_TRACER(trc, threadPool->asObject(), "MainQueue");
        1041               0 : }
        1042                 :
        1043                 : void
        1044               0 : WorkerQueue::work() {
        1045               0 :     AutoLock hold(lock);
        1046                 :
        1047                 :     Worker *w;
        1048               0 :     while (take(&w)) {  // can block outside the mutex
        1049               0 :         PR_Unlock(lock);
        1050               0 :         w->processOneEvent();     // enters request on w->context
        1051               0 :         PR_Lock(lock);
        1052               0 :         drop(w);
        1053                 :
        1054               0 :         if (lockedIsIdle()) {
        1055               0 :             PR_Unlock(lock);
        1056               0 :             main->wake();
        1057               0 :             PR_Lock(lock);
        1058                 :         }
        1059                 :     }
        1060               0 : }
        1061                 :
        1062                 : const bool mswin =
        1063                 : #ifdef XP_WIN
        1064                 :     true
        1065                 : #else
        1066                 :     false
        1067                 : #endif
        1068                 :     ;
        1069                 :
        1070                 : template <class Ch> bool
        1071               0 : IsAbsolute(const Ch *filename)
        1072                 : {
        1073                 :     return filename[0] == '/' ||
        1074               0 :            (mswin && (filename[0] == '\\' || (filename[0] != '\0' && filename[1] == ':')));
        1075                 : }
        1076                 :
        1077                 : // Note: base is a filename, not a directory name.
        1078                 : static JSString *
        1079               0 : ResolveRelativePath(JSContext *cx, const char *base, JSString *filename)
        1080                 : {
        1081               0 :     size_t fileLen = JS_GetStringLength(filename);
        1082               0 :     const jschar *fileChars = JS_GetStringCharsZ(cx, filename);
        1083               0 :     if (!fileChars)
        1084               0 :         return NULL;
        1085                 :
        1086               0 :     if (IsAbsolute(fileChars))
        1087               0 :         return filename;
        1088                 :
        1089                 :     // Strip off the filename part of base.
        1090               0 :     size_t dirLen = -1;
        1091               0 :     for (size_t i = 0; base[i]; i++) {
        1092               0 :         if (base[i] == '/' || (mswin && base[i] == '\\'))
        1093               0 :             dirLen = i;
        1094                 :     }
        1095                 :
        1096                 :     // If base is relative and contains no directories, use filename unchanged.
        1097               0 :     if (!IsAbsolute(base) && dirLen == (size_t) -1)
        1098               0 :         return filename;
        1099                 :
        1100                 :     // Otherwise return base[:dirLen + 1] + filename.
        1101               0 :     js::Vector<jschar, 0> result(cx);
        1102                 :     size_t nchars;
        1103               0 :     if (!JS_DecodeBytes(cx, base, dirLen + 1, NULL, &nchars))
        1104               0 :         return NULL;
        1105               0 :     if (!result.reserve(dirLen + 1 + fileLen))
        1106               0 :         return NULL;
        1107               0 :     JS_ALWAYS_TRUE(result.resize(dirLen + 1));
        1108               0 :     if (!JS_DecodeBytes(cx, base, dirLen + 1, result.begin(), &nchars))
        1109               0 :         return NULL;
        1110               0 :     result.infallibleAppend(fileChars, fileLen);
        1111               0 :     return JS_NewUCStringCopyN(cx, result.begin(), result.length());
        1112                 : }
        1113                 :
        1114                 : Worker *
        1115               0 : Worker::create(JSContext *parentcx, WorkerParent *parent, JSString *scriptName, JSObject *obj)
        1116                 : {
        1117               0 :     Worker *w = new Worker();
        1118               0 :     if (!w || !w->init(parentcx, parent, obj)) {
        1119               0 :         delete w;
        1120               0 :         return NULL;
        1121                 :     }
        1122                 :
        1123                 :     JSScript *script;
        1124               0 :     JS_DescribeScriptedCaller(parentcx, &script, NULL);
        1125               0 :     const char *base = JS_GetScriptFilename(parentcx, script);
        1126               0 :     JSString *scriptPath = ResolveRelativePath(parentcx, base, scriptName);
        1127               0 :     if (!scriptPath)
        1128               0 :         return NULL;
        1129                 :
        1130                 :     // Post an InitEvent to run the initialization script.
        1131               0 :     Event *event = InitEvent::create(parentcx, w, scriptPath);
        1132               0 :     if (!event)
        1133               0 :         return NULL;
        1134               0 :     if (!w->events.push(event) || !w->threadPool->getWorkerQueue()->post(w)) {
        1135               0 :         event->destroy(parentcx);
        1136               0 :         JS_ReportOutOfMemory(parentcx);
        1137               0 :         w->dispose();
        1138               0 :         return NULL;
        1139                 :     }
        1140               0 :     return w;
        1141                 : }
        1142                 :
        1143                 : void
        1144               0 : Worker::processOneEvent()
        1145                 : {
        1146               0 :     Event *event = NULL;    /* init to shut GCC up */
        1147                 :     {
        1148               0 :         AutoLock hold1(lock);
        1149               0 :         if (lockedCheckTermination() || events.empty())
        1150                 :             return;
        1151                 :
        1152               0 :         event = current = events.pop();
        1153                 :     }
        1154                 :
        1155               0 :     JS_SetRuntimeThread(runtime);
        1156                 :
        1157                 :     Event::Result result;
        1158                 :     {
        1159               0 :         JSAutoRequest ar(context);
        1160               0 :         result = event->process(context);
        1161                 :     }
        1162                 :
        1163                 :     // Note: we have to leave the above request before calling parent->post or
        1164                 :     // checkTermination, both of which acquire locks.
        1165               0 :     if (result == Event::forwardToParent) {
        1166               0 :         event->setChildAndRecipient(this, parent);
        1167               0 :         if (parent->post(event)) {
        1168               0 :             event = NULL;  // to prevent it from being deleted below
        1169                 :         } else {
        1170               0 :             JS_ReportOutOfMemory(context);
        1171               0 :             result = Event::fail;
        1172                 :         }
        1173                 :     }
        1174               0 :     if (result == Event::fail && !checkTermination()) {
        1175               0 :         JSAutoRequest ar(context);
        1176               0 :         Event *err = ErrorEvent::create(context, this);
        1177               0 :         if (err && !parent->post(err)) {
        1178               0 :             JS_ReportOutOfMemory(context);
        1179               0 :             err->destroy(context);
        1180               0 :             err = NULL;
        1181                 :         }
        1182                 :         if (!err) {
        1183                 :             // FIXME - out of memory, probably should panic
        1184                 :         }
        1185                 :     }
        1186                 :
        1187               0 :     if (event)
        1188               0 :         event->destroy(context);
        1189               0 :     JS_ClearRuntimeThread(runtime);
        1190                 :
        1191                 :     {
        1192               0 :         AutoLock hold2(lock);
        1193               0 :         current = NULL;
        1194               0 :         if (!lockedCheckTermination() && !events.empty()) {
        1195                 :             // Re-enqueue this worker. OOM here effectively kills the worker.
        1196               0 :             if (!threadPool->getWorkerQueue()->post(this))
        1197               0 :                 JS_ReportOutOfMemory(context);
        1198                 :         }
        1199                 :     }
        1200                 : }
        1201                 :
        1202                 : JSBool
        1203               0 : Worker::jsPostMessageToParent(JSContext *cx, unsigned argc, jsval *vp)
        1204                 : {
        1205               0 :     jsval workerval = js::GetFunctionNativeReserved(JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), 0);
        1206               0 :     Worker *w = (Worker *) JSVAL_TO_PRIVATE(workerval);
        1207                 :
        1208                 :     {
        1209               0 :         JSAutoSuspendRequest suspend(cx);  // avoid nesting w->lock in a request
        1210               0 :         if (w->checkTermination())
        1211               0 :             return false;
        1212                 :     }
        1213                 :
        1214               0 :     jsval data = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
        1215               0 :     Event *event = UpMessageEvent::create(cx, w, data);
        1216               0 :     if (!event)
        1217               0 :         return false;
        1218               0 :     if (!w->parent->post(event)) {
        1219               0 :         event->destroy(cx);
        1220               0 :         JS_ReportOutOfMemory(cx);
        1221               0 :         return false;
        1222                 :     }
        1223               0 :     JS_SET_RVAL(cx, vp, JSVAL_VOID);
        1224               0 :     return true;
        1225                 : }
        1226                 :
        1227                 : JSBool
        1228               0 : Worker::jsPostMessageToChild(JSContext *cx, unsigned argc, jsval *vp)
        1229                 : {
        1230               0 :     JSObject *workerobj = JS_THIS_OBJECT(cx, vp);
        1231               0 :     if (!workerobj)
        1232               0 :         return false;
        1233               0 :     Worker *w = (Worker *) JS_GetInstancePrivate(cx, workerobj, &jsWorkerClass, JS_ARGV(cx, vp));
        1234               0 :     if (!w) {
        1235               0 :         if (!JS_IsExceptionPending(cx))
        1236               0 :             JS_ReportError(cx, "Worker was shut down");
        1237               0 :         return false;
        1238                 :     }
        1239                 :
        1240               0 :     jsval data = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
        1241               0 :     Event *event = DownMessageEvent::create(cx, w, data);
        1242               0 :     if (!event)
        1243               0 :         return false;
        1244               0 :     if (!w->post(event)) {
        1245               0 :         JS_ReportOutOfMemory(cx);
        1246               0 :         return false;
        1247                 :     }
        1248               0 :     JS_SET_RVAL(cx, vp, JSVAL_VOID);
        1249               0 :     return true;
        1250                 : }
        1251                 :
        1252                 : JSBool
        1253               0 : Worker::jsTerminate(JSContext *cx, unsigned argc, jsval *vp)
        1254                 : {
        1255               0 :     JS_SET_RVAL(cx, vp, JSVAL_VOID);
        1256                 :
        1257               0 :     JSObject *workerobj = JS_THIS_OBJECT(cx, vp);
        1258               0 :     if (!workerobj)
        1259               0 :         return false;
        1260               0 :     Worker *w = (Worker *) JS_GetInstancePrivate(cx, workerobj, &jsWorkerClass, JS_ARGV(cx, vp));
        1261               0 :     if (!w)
        1262               0 :         return !JS_IsExceptionPending(cx);  // ok to terminate twice
        1263                 :
        1264               0 :     JSAutoSuspendRequest suspend(cx);
        1265               0 :     w->setTerminateFlag();
        1266               0 :     return true;
        1267                 : }
        1268                 :
        1269                 : void
        1270               0 : Event::trace(JSTracer *trc)
        1271                 : {
        1272               0 :     if (recipient)
        1273               0 :         recipient->trace(trc);
        1274               0 :     if (child)
        1275               0 :         JS_CALL_OBJECT_TRACER(trc, child->asObject(), "worker");
        1276               0 : }
        1277                 :
        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
        1283                 : };
        1284                 :
        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
        1290                 : };
        1291                 :
        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
        1296                 : };
        1297                 :
        1298                 : ThreadPool *
        1299           18666 : js::workers::init(JSContext *cx, WorkerHooks *hooks, JSObject *global, JSObject **rootp)
        1300                 : {
        1301           18666 :     return Worker::initWorkers(cx, hooks, global, rootp);
        1302                 : }
        1303                 :
        1304                 : void
        1305              27 : js::workers::terminateAll(ThreadPool *tp)
        1306                 : {
        1307              27 :     tp->terminateAll();
        1308              27 : }
        1309                 :
        1310                 : void
        1311           18666 : js::workers::finish(JSContext *cx, ThreadPool *tp)
        1312                 : {
        1313           18666 :     if (MainQueue *mq = tp->getMainQueue()) {
        1314               0 :         JS_ALWAYS_TRUE(mq->mainThreadWork(cx, true));
        1315               0 :         tp->shutdown(cx);
        1316                 :     }
        1317           18666 : }
        1318                 :
        1319                 : #endif /* JS_THREADSAFE */

Generated by: LCOV version 1.7