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-06-02 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) != NULL;
     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           18405 :     explicit ThreadPool(WorkerHooks *hooks) : hooks(hooks), mq(NULL), wq(NULL), terminating(0) {
     474          128835 :         for (int i = 0; i < threadCount; i++)
     475          110430 :             threads[i] = NULL;
     476           18405 :     }
     477                 : 
     478                 :   public:
     479           18405 :     ~ThreadPool() {
     480           18405 :         JS_ASSERT(!mq);
     481           18405 :         JS_ASSERT(!wq);
     482           18405 :         JS_ASSERT(!threads[0]);
     483           18405 :     }
     484                 : 
     485           18405 :     static ThreadPool *create(JSContext *cx, WorkerHooks *hooks) {
     486           18405 :         ThreadPool *tp = new ThreadPool(hooks);
     487           18405 :         if (!tp) {
     488               0 :             JS_ReportOutOfMemory(cx);
     489               0 :             return NULL;
     490                 :         }
     491                 : 
     492           18405 :         JSObject *obj = JS_NewObject(cx, &jsClass, NULL, NULL);
     493           18405 :         if (!obj) {
     494               0 :             delete tp;
     495               0 :             return NULL;
     496                 :         }
     497           18405 :         JS_SetPrivate(obj, tp);
     498           18405 :         tp->obj = obj;
     499           18405 :         return tp;
     500                 :     }
     501                 : 
     502           18405 :     JSObject *asObject() { return obj; }
     503               0 :     WorkerHooks *getHooks() { return hooks; }
     504               0 :     WorkerQueue *getWorkerQueue() { return wq; }
     505           18405 :     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           21032 :     static void jsTraceThreadPool(JSTracer *trc, JSObject *obj) {
     571           21032 :         ThreadPool *tp = unwrap(obj);
     572           21032 :         if (tp->mq) {
     573               0 :             tp->mq->traceChildren(trc);
     574               0 :             tp->wq->trace(trc);
     575                 :         }
     576           21032 :     }
     577                 : 
     578                 : 
     579           18405 :     static void jsFinalize(JSContext *cx, JSObject *obj) {
     580           18405 :         if (ThreadPool *tp = unwrap(obj))
     581           18405 :             delete tp;
     582           18405 :     }
     583                 : 
     584                 :   public:
     585           39437 :     static ThreadPool *unwrap(JSObject *obj) {
     586           39437 :         JS_ASSERT(JS_GetClass(obj) == &jsClass);
     587           39437 :         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           21032 :     static void jsTraceWorker(JSTracer *trc, JSObject *obj) {
     705           21032 :         JS_ASSERT(JS_GetClass(obj) == &jsWorkerClass);
     706           21032 :         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           21032 :     }
     714                 : 
     715           18405 :     static void jsFinalize(JSContext *cx, JSObject *obj) {
     716           18405 :         JS_ASSERT(JS_GetClass(obj) == &jsWorkerClass);
     717           18405 :         if (Worker *w = (Worker *) JS_GetPrivate(obj))
     718               0 :             delete w;
     719           18405 :     }
     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           18405 :     static ThreadPool *initWorkers(JSContext *cx, WorkerHooks *hooks, JSObject *global,
     894                 :                                    JSObject **objp) {
     895                 :         // Create the ThreadPool object and its JSObject wrapper.
     896           18405 :         ThreadPool *threadPool = ThreadPool::create(cx, hooks);
     897           18405 :         if (!threadPool)
     898               0 :             return NULL;
     899                 : 
     900                 :         // Root the ThreadPool JSObject early.
     901           18405 :         *objp = threadPool->asObject();
     902                 : 
     903                 :         // Create the Worker constructor.
     904                 :         JSObject *proto = js::InitClassWithReserved(cx, global, NULL, &jsWorkerClass,
     905                 :                                                     jsConstruct, 1,
     906           18405 :                                                     NULL, jsMethods, NULL, NULL);
     907           18405 :         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           18405 :         JSObject *ctor = JS_GetConstructor(cx, proto);
     913           18405 :         js::SetFunctionNativeReserved(ctor, 1, PRIVATE_TO_JSVAL(threadPool));
     914                 : 
     915           18405 :         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           18405 : js::workers::init(JSContext *cx, WorkerHooks *hooks, JSObject *global, JSObject **rootp)
    1300                 : {
    1301           18405 :     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           18405 : js::workers::finish(JSContext *cx, ThreadPool *tp)
    1312                 : {
    1313           18405 :     if (MainQueue *mq = tp->getMainQueue()) {
    1314               0 :         JS_ALWAYS_TRUE(mq->mainThreadWork(cx, true));
    1315               0 :         tp->shutdown(cx);
    1316                 :     }
    1317           18405 : }
    1318                 : 
    1319                 : #endif /* JS_THREADSAFE */

Generated by: LCOV version 1.7