2 * (C) Copyright 2008, 2014 Jeremy Maitin-Shepard
4 * Use, modification, and distribution are subject to the terms specified in the
9 * Coroutine (i.e. cooperative multithreading) implementation in
10 * JavaScript based on Mozilla JavaScript 1.7 generators.
12 * This is very similar to the Task mechanism implemented in Task.jsm:
13 * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Task.jsm
15 * Like Task.jsm, Conkeror's coroutines integrate with Promises:
16 * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm
18 * Conkeror uses resource://gre/modules/Promise.jsm if it is available (Gecko >=
19 * 25); otherwise, a copy of Gecko 26 Promise.jsm file in
20 * modules/compat/Promise.jsm is used.
22 * Before trying to understand this file, first read about generators
24 * https://developer.mozilla.org/en/New_in_JavaScript_1.7
26 * Additionally, here is a document describing another implementation
27 * of coroutines/cooperative multithreading in JavaScript based on
28 * generators that may be of interest:
29 * http://www.neilmix.com/2007/02/07/threading-in-javascript-17/
31 * === Introduction ===
33 * For the purposes of Conkeror, a coroutine is a generator function
34 * (i.e. a function that uses the yield keyword) that adheres to
35 * certain practices (described later in this file).
37 * As described in the "New in JavaScript 1.7" document, although a
38 * generator function `foo' can be invoked using the same syntax as
39 * any other function, i.e.:
43 * this "function call" merely serves to bind the arguments (including
44 * the special `this' argument) without actually running any of the
45 * code specified in the defintion of foo, and return a special
46 * generator object. The generator object has three methods, `next',
47 * 'send', and 'close', that can be called to actually run code
48 * specified in the definition of the generator function.
50 * In Conkeror, a `coroutine' refers to a generator function that
51 * adheres to the practices described later in this file. In a
52 * JavaScript program, a coroutine (or any generator function)
53 * unfortunately cannot be distinguished from a normal function
54 * without actually invoking it. A `prepared coroutine' refers to a
55 * generator object obtained from calling a coroutine
56 * function. Generally when using this coroutine library, none of the
57 * methods of these generator objects should be called directly. The
58 * `is_coroutine' function can be used to check whether a given value
59 * is a generator object (not a generator function). This library
60 * generally assumes that any generator objects it is passed are
61 * proper prepared coroutines. If a generator function that does not
62 * adhere to the practices required of a coroutine is used with this
63 * library as a coroutine, undefined (and generally undesirable)
66 * === Requirements for a coroutine ===
68 * In most ways, a coroutine function can be written like a normal
69 * function. Arbitrary computation can be done, including arbitrary
70 * calls to normal functions, exceptions can be thrown, and exceptions
71 * can be handled using try-catch-finally blocks.
73 * --- Return values ---
75 * One of the main differences from a normal function is that the
76 * `return' keyword cannot be used to return a value to the caller
77 * (which is necessarily either another coroutine function, or the
78 * coroutine was itself started in a new "thread" in which case the
79 * return value is ignored). The `return' keyword can still be used to
80 * return `undefined' to the caller. In order to return a value,
81 * though, the special syntax:
83 * yield co_return(<expr>);
85 * must be used in place of the normal syntax:
89 * --- Invoking another coroutine function synchronously ---
91 * Another very important operation is calling another coroutine
92 * function synchronously, meaning that control will not return to the
93 * caller (the current coroutine) until the specified coroutine has
94 * either returned or thrown an exception. Conceptually, the specified
95 * coroutine is run in the same "thread" as the current coroutine, as
96 * opposed to being invoked asynchronously, in which case it would be
97 * run in a new "thread". This is done using the syntax:
99 * yield <prepared-coroutine-expr>
101 * where <prepared-coroutine-expr> is some expression that evaluates to
102 * a generator object, most typically a direct call to a coroutine
103 * function in the form of
109 * yield obj.foo(a,b,c)
111 * in the case that "foo" is a coroutine method of some object
114 * If the specified coroutine returns a value normally, the yield
115 * expression evaluates to that value. That is, using the the syntax
117 * var x = yield foo(a,b,c);
119 * if foo is a coroutine and returns a normal value, that value will
122 * Alternatively, if the specified coroutine throws an exception, the
123 * exception will be propagated out of the yield expression, and can
124 * optionally be handled using a try-catch-finally block. If it is not
125 * handled, it will be propagated to the caller of the current
126 * coroutine in the same way.
128 * Note that it is safe to invoke a normal function using `yield' as
129 * well as if it were a coroutine. That is, the syntax
133 * can likewise be used if foo is a normal function, and the same
134 * return value and exception propagation semantics
135 * apply. (Technically, what is actually happenining is that if yield
136 * is passed a value that is not a generator object or one of the
137 * several other types of values that are handled specially like the
138 * return value of co_return, it simply returns the value back
139 * untouched to the coroutine function. Thus, if foo is a normal
140 * function and returns a value, the return value is passed to yield,
141 * which immediately passes it back. If it throws an exception, then
142 * due to the normal exception propagation, yield is never even
145 * --- Integration with Promise API ---
147 * Promises provide a simple, standard interface for asynchronous operations.
148 * Coroutines can wait synchronously for the result of a Promise by using yield:
152 * Promises are detected by presence of a `then' member of type function. If
153 * the Promise is resolved, the yield expression will evaluate to the resolved
154 * value. Otherwise, if the Promise is rejected, the yield expression will
155 * cause the rejection exception to be thrown.
157 * In effect, a function that starts an asyncronous operation and returns a
158 * Promise can be called synchronously from a coroutine, in the same way that
159 * another coroutine can be called synchronously, by using:
161 * yield function_that_returns_a_promise()
163 * --- [[deprecated]] Current continutation/"thread" handle ---
165 * Note: This API is deprecated because it is error-prone. Instead, use the
166 * Promise integration.
170 * var cc = yield CONTINUATION;
172 * can be used to obtain a special "continuation" object that serves
173 * as a sort of "handle" to the current thread. Note that while in a
174 * single "thread", even if not in the same coroutine function, the
175 * exact same "continuation" object will always be returned.
177 * The continuation object is used in conjuction with another special
182 * This operation suspends execution of the current "thread" until it
183 * is resumed using a reference to the continuation object for the
184 * "thread". There are two ways to resume executation. To resume
185 * execution normally, the syntax:
191 * cc() (equivalent to cc(undefined))
193 * can be used. This resumes execution and causes the yield SUSPEND
194 * expression to evaluate to the specified value. Alternatively, the
199 * can be used. This causes the specified exception `e' to be thrown
200 * from the yield SUSPEND expression.
202 * It is not valid to use either of these two operations on a
203 * continutation corresponding to a thread that is either currently
204 * running or has already terminated.
206 * Generally, any coroutine function that suspends the current
207 * "thread" should also arrange using some other asynchronous
208 * facility, such as a timer with a callback or an event handler, to
209 * resume the "thread" later. It should also arrange to resume the
210 * "thread" with an exception if an error of some sort occurs in lieu
211 * of simply not resuming the "thread" at all.
213 * It is not technically necessary to resume a "thread" after
214 * suspending it, but it generally should always be done, as otherwise
215 * important error handling code, including code in `finally' blocks,
218 * === Invoking a coroutine asynchronously/spawning a new thread ===
220 * A coroutine function can be called asynchronously from either a
221 * normal function or a coroutine function. Conceptually, this is
222 * equivalent to spawning a new "thread" to run the specified
223 * coroutine function. This operation is done using the spawn
224 * function as follows:
226 * spawn(<prepared-coroutine-expr>)
234 * spawn(function (){ yield foo(a,b,c); }())
236 * The `spawn' function returns a Promise representing the result of the
239 * As a convenience, if the argument to `spawn' is a Promise instead of a
240 * prepared coroutine (i.e. a generator), it is returned as is, such that
241 * spawn can be used transparently with both generator functions and functions
242 * returning Promises.
244 * === Cancelation ===
246 * Conkeror's coroutines support an extension to the Promise API for
247 * cancelation: a Promise may have a `cancel' member of type function, which
248 * attempts to cancel the corresponding asynchronous operation.
250 * The `cancel' function should not make use of the implicit `this' argument.
251 * The `cancel' function takes one optional argument (defaults to
252 * task_canceled()), which specifies the exception with which the Promise should
253 * be rejected if the cancelation is successful.
255 * The `cancel' function is asynchronous and returns without providing any
256 * indication of whether the cancelation was successful. There is no guarantee
257 * that cancelation will be possible or even successful, for instance the
258 * Promise may have already been resolved or rejected, or the asynchronous
259 * operation may not be in a cancelable state, or the cancelation notification
260 * may be ignored for some reason. However, if the cancelation is successful,
261 * this should be indicated by rejecting the Promise with the specified
264 * The helper functions `make_cancelable' and `make_simple_cancelable' are
265 * useful for creating Promises that support the cancelation API.
267 * The Promise returned by `spawn' supports cancelation, and works as follows:
269 * The `cancel' function in the returned Promise marks the "thread" for
270 * cancelation. While a "thread" is marked for cancelation, any Promise the
271 * thread waits on synchronously (including one in progress when `cancel' is
272 * called) will be immediately canceled if it supports cancelation. The
273 * "thread" will remain marked for cancelation until one of the cancelation
274 * requests is successful, i.e. one of the canceled Promises is rejected with
275 * the cancelation exception.
279 Components.utils.import("resource://gre/modules/Promise.jsm");
282 Components.utils.import("chrome://conkeror/content/compat/Promise.jsm");
285 function _return_value (x) {
289 function co_return (x) {
290 return new _return_value(x);
293 const CONTINUATION = { toString: function () "[object CONTINUATION]" };
294 const SUSPEND = { toString: function () "[object SUSPEND]" };
297 * Returns true if the `obj' is a generator object. Returns false
298 * otherwise. It is assumed that only generator objects that are
299 * actually `prepared coroutines' (see above) will be used with this
302 function is_coroutine (obj) {
303 return obj != null &&
304 typeof(obj) == "object" &&
305 typeof(obj.next) == "function" &&
306 typeof(obj.send) == "function";
310 * Returns an object that behaves like a Promise but also has a
311 * `cancel` method, which invokes the specified `canceler` function.
312 * The returned object has the specified promise as its prototype.
313 * Note that we have to create a new object to add the `cancel` method
314 * because Promise objects are sealed.
316 * The canceler function must take one argument `e`, a cancelation
317 * exception. The canceler function should arrange so that the
318 * promise is rejected with `e` if the cancelation is successful. Any
319 * other result of the promise indicates that the cancelation was not
320 * successfully delivered. If `e` is undefined, it defaults to
323 * This protocol is important for coroutines as it makes it possible
324 * to retry delivering the cancelation notification until it is
325 * delivered successfully at least once.
327 function make_cancelable (promise, canceler) {
328 return { __proto__: promise,
329 cancel: function (e) {
338 * Returns a Promise that supports cancelation by simply rejecting the specified
339 * `deferred` object with the cancelation exception.
341 * This will likely leave the asynchronous operation running, and a proper
342 * cancelation function that actually stops the asynchronous operation should be
343 * used instead when possible.
345 function make_simple_cancelable(deferred) {
346 return make_cancelable(deferred.promise, deferred.reject);
349 function task_canceled () {
350 let e = new Error("task_canceled");
351 e.__proto__ = task_canceled.prototype;
354 task_canceled.prototype.__proto__ = Error.prototype;
356 function _co_impl (f) {
358 // Current generator function currently at top of call stack
361 * Stack of (partially-run) prepared coroutines/generator objects
362 * that specifies the call-chain of the current coroutine function
363 * `f'. Conceptually, `f' is at the top of the stack.
368 * Deferred object used to return the result of the coroutine.
370 this.deferred = Promise.defer();
373 * The current canceler function, used to interrupt this coroutine. If null, then any cancelation will be delayed.
375 this.canceler = null;
378 * A pending cancelation.
380 this.pending_cancelation = undefined;
383 _co_impl.prototype = {
384 constructor: _co_impl,
385 cancel: function _co_impl__cancel (e) {
386 this.pending_cancelation = e;
391 send: function _co_impl__send(throw_value, y) {
392 if (this.canceler === undefined) {
393 let e = new Error("Programming error: _co_impl.send called on already-running coroutine.");
397 this.canceler = undefined;
399 // Cancelation has been successfully delivered, remove pending cancelation
400 if (throw_value && this.pending_cancelation == y && y !== undefined)
401 this.pending_cancelation = undefined;
404 try { // We must capture any exception thrown by `f'
407 * If `f' yields again after being resumed, the value
408 * passed to yield will be stored in `x'.
413 x = this.f.throw(y); // f.throw returns the next value passed to yield
415 x = this.f.send(y); // f.send also returns the next value passed to yield
418 // The promise API should be used instead.
419 if (x === CONTINUATION) {
421 * The coroutine (`f') asked us to pass it a reference
422 * to the current continuation. We don't need to make
423 * any adjustments to the call stack.
425 let cc = this.send.bind(this, false);
426 cc.throw = this.send.bind(this, true);
433 // The promise API should be used instead.
435 this.canceler = null;
439 if (is_coroutine(x)) {
440 // `f' wants to synchronously call the coroutine `x'
441 this.stack[this.stack.length] = this.f; // push the current coroutine `f' onto the call stack
442 this.f = x; // make `x' the new top of the stack
443 y = undefined; // `x` is a new coroutine so we must start it by passing `undefined'
447 if (x && typeof(x.then) == "function") {
448 // x is assumed to be a Promise
449 // Wait for result before returning to caller
450 if (typeof(x.cancel) == "function") {
451 if (this.pending_cancelation !== undefined)
452 x.cancel(this.pending_cancelation);
453 this.canceler = x.cancel;
455 this.canceler = null;
457 x.then(this.send.bind(this, false), this.send.bind(this, true));
461 if (x instanceof _return_value) {
462 // `f' wants to return a value
464 if (this.stack.length == 0) {
466 * `f' doesn't have a caller, so we resolve
467 * this.deferred with the return value and
468 * terminate the coroutine.
470 this.deferred.resolve(x.value);
473 // Pop the caller of `f' off the top of the stack
474 this.f = this.stack[this.stack.length - 1];
476 // Pass the return value to the caller, which is now the current coroutine
482 * `f' yielded to us a value without any special
483 * interpretation. Most likely, this is due to `f' calling
484 * a normal function as if it were a coroutine, in which
485 * case `x' simply contains the return value of that
486 * normal function. Just return the value back to `f'.
491 * `f' threw an exception. If `e' is a StopIteration
492 * exception, then `f' exited without returning a value
493 * (equivalent to returning a value of
494 * `undefined'). Otherwise, `f' threw or propagted a real
497 if (this.stack.length == 0) {
499 * `f' doesn't have a caller, so we resolve/reject
500 * this.deferred and terminate the coroutine.
502 if (e instanceof StopIteration)
503 this.deferred.resolve(undefined);
505 this.deferred.reject(e);
508 // Pop the caller of `f' off the top of the stack
509 this.f = this.stack[this.stack.length - 1];
511 if (e instanceof StopIteration)
512 y = undefined; // propagate a return value of `undefined' to the caller
514 // propagate the exception to the caller
524 * Runs the specified coroutine asynchronously. Returns a potentially-cancelable
525 * Promise representing the result.
527 * In the normal case, the `f` is a prepared coroutine (i.e. generator).
529 * If `f` is instead a Promise, it is returned as is, with a no-op canceler.
531 * If `f` is some other value, this returns `Promise.resolve(f)` with a no-op canceler.
534 if (!is_coroutine(f)) {
535 if (f && typeof(f.then) == "function") {
536 // f is a Promise, just return as is
537 if (typeof(f.cancel) != "function")
538 return make_cancelable(f, function () {} );
541 return make_cancelable(Promise.resolve(f), function () {});
544 let x = new _co_impl(f);
545 x.send(false, undefined); // initialize
547 return make_cancelable(x.deferred.promise, x.cancel.bind(x));
551 * [[deprecated]] Runs the specified coroutine asynchronously.
552 * Returns the corresponding continuation object.
554 * Use `spawn' instead, which returns a Promise rather than a continuation object.
556 function co_call (f) {
557 if (!is_coroutine(f))
560 let x = new _co_impl(f);
561 x.send(false, undefined); // initialize
563 let cc = x.send.bind(this, false);
564 cc.throw = x.send.bind(this, true);
568 provide("coroutine");