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