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 */
|