pike.git
/
lib
/
modules
/
Concurrent.pmod
version
»
Context lines:
10
20
40
80
file
none
3
pike.git/lib/modules/Concurrent.pmod:1:
+
#pike __REAL_VERSION__
-
+
//! Module for handling multiple concurrent events.
+
//!
+
//! The @[Future] and @[Promise] API was inspired by
+
//! @url{https://github.com/couchdeveloper/FutureLib@}.
+
+
protected enum State {
+
STATE_PENDING = 0,
+
STATE_FULFILLED,
+
STATE_REJECTED,
+
};
+
+
protected Thread.Mutex mux = Thread.Mutex();
+
protected Thread.Condition cond = Thread.Condition();
+
+
//! Global failure callback, called when a promise without failure
+
//! callback fails. This is useful to log exceptions, so they are not
+
//! just silently caught and ignored.
+
void on_failure(function(mixed : void) f)
+
{
+
global_on_failure = f;
+
}
+
protected function(mixed : void) global_on_failure;
+
+
//! @param enable
+
//! Setting this to @expr{false@} causes all @[Concurrent] callbacks
+
//! (except for timeouts)
+
//! to be called directly, without using a backend.
+
//!
+
//! @note
+
//! As long as the backend hasn't started, it will default to @expr{false@}.
+
//! Upon startup of the backend, it will change to @expr{true@} unless you
+
//! explicitly called @[use_backend()] before that.
+
//!
+
//! @note
+
//! (Un)setting this typically alters the order in which some callbacks
+
//! are called (depending on what happens in a callback).
+
final void use_backend(int enable)
+
{
+
callout = enable ? call_out : callnow;
+
remove_call_out(auto_use_backend);
+
}
+
+
private mixed
+
callnow(function(mixed ...:void) f, int|float delay, mixed ... args)
+
{
+
mixed err = catch (f(@args));
+
if (err)
+
master()->handle_error(err);
+
return 0;
+
}
+
+
private void auto_use_backend()
+
{
+
callout = call_out;
+
}
+
+
protected function(function(mixed ...:void), int|float, mixed ...:mixed)
+
callout;
+
+
private void create()
+
{
+
callout = callnow;
+
call_out(auto_use_backend, 0);
+
}
+
+
//! Value that will be provided asynchronously
+
//! sometime in the future.
+
//!
+
//! @seealso
+
//! @[Promise]
+
class Future
+
{
+
mixed result;
+
State state;
+
+
protected array(array(function(mixed, mixed ...: void)|mixed))
+
success_cbs = ({});
+
protected array(array(function(mixed, mixed ...: void)|mixed))
+
failure_cbs = ({});
+
+
//! Wait for fulfillment and return the value.
+
//!
+
//! @throws
+
//! Throws on rejection.
+
mixed get()
+
{
+
State s = state;
+
mixed res = result;
+
if (!s) {
+
Thread.MutexKey key = mux->lock();
+
while (!state) {
+
cond->wait(key);
+
}
+
+
s = state;
+
res = result;
+
}
+
+
if (s == STATE_REJECTED) {
+
throw(res);
+
}
+
return res;
+
}
+
+
//! Register a callback that is to be called on fulfillment.
+
//!
+
//! @param cb
+
//! Function to be called. The first argument will be the
+
//! result of the @[Future].
+
//!
+
//! @param extra
+
//! Any extra context needed for @[cb]. They will be provided
+
//! as arguments two and onwards when @[cb] is called.
+
//!
+
//! @note
+
//! @[cb] will always be called from the main backend.
+
//!
+
//! @seealso
+
//! @[on_failure()]
+
this_program on_success(function(mixed, mixed ... : void) cb, mixed ... extra)
+
{
+
switch (state) {
+
case STATE_FULFILLED:
+
callout(cb, 0, result, @extra);
+
break;
+
case STATE_PENDING:
+
// Rely on interpreter lock to add to success_cbs before state changes
+
// again
+
success_cbs += ({ ({ cb, @extra }) });
+
}
+
return this_program::this;
+
}
+
+
//! Register a callback that is to be called on failure.
+
//!
+
//! @param cb
+
//! Function to be called. The first argument will be the
+
//! failure result of the @[Future].
+
//!
+
//! @param extra
+
//! Any extra context needed for @[cb]. They will be provided
+
//! as arguments two and onwards when @[cb] is called.
+
//!
+
//! @note
+
//! @[cb] will always be called from the main backend.
+
//!
+
//! @seealso
+
//! @[on_success()]
+
this_program on_failure(function(mixed, mixed ... : void) cb, mixed ... extra)
+
{
+
switch (state) {
+
case STATE_REJECTED:
+
callout(cb, 0, result, @extra);
+
break;
+
case STATE_PENDING:
+
// Rely on interpreter lock to add to failure_cbs before state changes
+
// again
+
failure_cbs += ({ ({ cb, @extra }) });
+
}
+
return this_program::this;
+
}
+
+
//! Apply @[fun] with @[val] followed by the contents of @[ctx],
+
//! and update @[p] with the result.
+
protected void apply(mixed val, Promise p,
+
function(mixed, mixed ... : mixed) fun,
+
array(mixed) ctx)
+
{
+
mixed f;
+
if (mixed err = catch (f = fun(val, @ctx)))
+
p->failure(err);
+
else
+
p->success(f);
+
}
+
+
//! Apply @[fun] with @[val] followed by the contents of @[ctx],
+
//! and update @[p] with the eventual result.
+
protected void apply_flat(mixed val, Promise p,
+
function(mixed, mixed ... : Future) fun,
+
array(mixed) ctx)
+
{
+
Future f;
+
{
+
if (mixed err = catch (f = fun(val, @ctx))) {
+
p->failure(err);
+
return;
+
}
+
}
+
if (!objectp(f) || !f->on_failure || !f->on_success)
+
error("Expected %O to return a Future. Got: %O.\n", fun, f);
+
f->on_failure(p->failure)->on_success(p->success);
+
}
+
+
//! Apply @[fun] with @[val] followed by the contents of @[ctx],
+
//! and update @[p] with the eventual result.
+
protected void apply_smart(mixed val, Promise p,
+
function(mixed, mixed ... : mixed|Future) fun,
+
array(mixed) ctx)
+
{
+
mixed|Future f;
+
{
+
if (mixed err = catch (f = fun(val, @ctx))) {
+
p->failure(err);
+
return;
+
}
+
}
+
if (!objectp(f)
+
|| !functionp(f->on_failure) || !functionp(f->on_success))
+
p->success(f);
+
else
+
f->on_failure(p->failure)->on_success(p->success);
+
}
+
+
//! Apply @[fun] with @[val] followed by the contents of @[ctx],
+
//! and update @[p] with @[val] if @[fun] didn't return false.
+
//! If @[fun] returned false, fail @[p] with @expr{0@} as result.
+
protected void apply_filter(mixed val, Promise p,
+
function(mixed, mixed ... : int(0..1)) fun,
+
array(mixed) ctx)
+
{
+
int bool;
+
mixed err;
+
if (!(err = catch (bool = fun(val, @ctx))) && bool)
+
p->success(val);
+
else
+
p->failure(err);
+
}
+
+
//! This specifies a callback that is only called on success, and
+
//! allows you to alter the future.
+
//!
+
//! @param fun
+
//! Function to be called. The first argument will be the
+
//! @b{success@} result of @b{this@} @[Future].
+
//! The return value will be the success result of the new @[Future].
+
//!
+
//! @param extra
+
//! Any extra context needed for
+
//! @expr{fun@}. They will be provided
+
//! as arguments two and onwards when the callback is called.
+
//!
+
//! @returns
+
//! The new @[Future].
+
//!
+
//! @note
+
//! This method is used if your @[fun] returns a regular value (i.e.
+
//! @b{not@} a @[Future]).
+
//!
+
//! @seealso
+
//! @[map_with()], @[transform()], @[recover()]
+
this_program map(function(mixed, mixed ... : mixed) fun, mixed ... extra)
+
{
+
Promise p = Promise();
+
on_failure(p->failure);
+
on_success(apply, p, fun, extra);
+
return p->future();
+
}
+
+
//! This specifies a callback that is only called on success, and
+
//! allows you to alter the future.
+
//!
+
//! @param fun
+
//! Function to be called. The first argument will be the
+
//! @b{success@} result of @b{this@} @[Future].
+
//! The return value must be a @[Future] that promises
+
//! the new result.
+
//!
+
//! @param extra
+
//! Any extra context needed for
+
//! @expr{fun@}. They will be provided
+
//! as arguments two and onwards when the callback is called.
+
//!
+
//! @returns
+
//! The new @[Future].
+
//!
+
//! @note
+
//! This method is used if your @[fun] returns a @[Future] again.
+
//!
+
//! @seealso
+
//! @[map()], @[transform_with()], @[recover_with()], @[flat_map]
+
this_program map_with(function(mixed, mixed ... : this_program) fun,
+
mixed ... extra)
+
{
+
Promise p = Promise();
+
on_failure(p->failure);
+
on_success(apply_flat, p, fun, extra);
+
return p->future();
+
}
+
+
//! This is an alias for @[map_with()].
+
//!
+
//! @seealso
+
//! @[map_with()]
+
inline this_program flat_map(function(mixed, mixed ... : this_program) fun,
+
mixed ... extra)
+
{
+
return map_with(fun, @extra);
+
}
+
+
//! This specifies a callback that is only called on failure, and
+
//! allows you to alter the future into a success.
+
//!
+
//! @param fun
+
//! Function to be called. The first argument will be the
+
//! @b{failure@} result of @b{this@} @[Future].
+
//! The return value will be the success result of the new @[Future].
+
//!
+
//! @param extra
+
//! Any extra context needed for
+
//! @expr{fun@}. They will be provided
+
//! as arguments two and onwards when the callback is called.
+
//!
+
//! @returns
+
//! The new @[Future].
+
//!
+
//! @note
+
//! This method is used if your callbacks return a regular value (i.e.
+
//! @b{not@} a @[Future]).
+
//!
+
//! @seealso
+
//! @[recover_with()], @[map()], @[transform()]
+
this_program recover(function(mixed, mixed ... : mixed) fun,
+
mixed ... extra)
+
{
+
Promise p = Promise();
+
on_success(p->success);
+
on_failure(apply, p, fun, extra);
+
return p->future();
+
}
+
+
//! This specifies a callback that is only called on failure, and
+
//! allows you to alter the future into a success.
+
//!
+
//! @param fun
+
//! Function to be called. The first argument will be the
+
//! @b{failure@} result of @b{this@} @[Future].
+
//! The return value must be a @[Future] that promises
+
//! the new success result.
+
//!
+
//! @param extra
+
//! Any extra context needed for
+
//! @expr{fun@}. They will be provided
+
//! as arguments two and onwards when the callback is called.
+
//!
+
//! @returns
+
//! The new @[Future].
+
//!
+
//! @note
+
//! This method is used if your callbacks return a @[Future] again.
+
//!
+
//! @seealso
+
//! @[recover()], @[map_with()], @[transform_with()]
+
this_program recover_with(function(mixed, mixed ... : this_program) fun,
+
mixed ... extra)
+
{
+
Promise p = Promise();
+
on_success(p->success);
+
on_failure(apply_flat, p, fun, extra);
+
return p->future();
+
}
+
+
//! This specifies a callback that is only called on success, and
+
//! allows you to selectively alter the future into a failure.
+
//!
+
//! @param fun
+
//! Function to be called. The first argument will be the
+
//! @b{success@} result of @b{this@} @[Future].
+
//! If the return value is @expr{true@}, the future succeeds with
+
//! the original success result.
+
//! If the return value is @expr{false@}, the future fails with
+
//! an @[UNDEFINED] result.
+
//!
+
//! @param extra
+
//! Any extra context needed for
+
//! @expr{fun@}. They will be provided
+
//! as arguments two and onwards when the callback is called.
+
//!
+
//! @returns
+
//! The new @[Future].
+
//!
+
//! @seealso
+
//! @[transform()]
+
this_program filter(function(mixed, mixed ... : int(0..1)) fun,
+
mixed ... extra)
+
{
+
Promise p = Promise();
+
on_failure(p->failure);
+
on_success(apply_filter, p, fun, extra);
+
return p->future();
+
}
+
+
//! This specifies callbacks that allow you to alter the future.
+
//!
+
//! @param success
+
//! Function to be called. The first argument will be the
+
//! @b{success@} result of @b{this@} @[Future].
+
//! The return value will be the success result of the new @[Future].
+
//!
+
//! @param failure
+
//! Function to be called. The first argument will be the
+
//! @b{failure@} result of @b{this@} @[Future].
+
//! The return value will be the success result of the new @[Future].
+
//! If this callback is omitted, it will default to the same callback as
+
//! @expr{success@}.
+
//!
+
//! @param extra
+
//! Any extra context needed for
+
//! @expr{success@} and @expr{failure@}. They will be provided
+
//! as arguments two and onwards when the callbacks are called.
+
//!
+
//! @returns
+
//! The new @[Future].
+
//!
+
//! @note
+
//! This method is used if your callbacks return a regular value (i.e.
+
//! @b{not@} a @[Future]).
+
//!
+
//! @seealso
+
//! @[transform_with()], @[map()], @[recover()]
+
this_program transform(function(mixed, mixed ... : mixed) success,
+
function(mixed, mixed ... : mixed)|void failure,
+
mixed ... extra)
+
{
+
Promise p = Promise();
+
on_success(apply, p, success, extra);
+
on_failure(apply, p, failure || success, extra);
+
return p->future();
+
}
+
+
//! This specifies callbacks that allow you to alter the future.
+
//!
+
//! @param success
+
//! Function to be called. The first argument will be the
+
//! @b{success@} result of @b{this@} @[Future].
+
//! The return value must be a @[Future] that promises
+
//! the new result.
+
//!
+
//! @param failure
+
//! Function to be called. The first argument will be the
+
//! @b{failure@} result of @b{this@} @[Future].
+
//! The return value must be a @[Future] that promises
+
//! the new success result.
+
//! If this callback is omitted, it will default to the same callback as
+
//! @expr{success@}.
+
//!
+
//! @param extra
+
//! Any extra context needed for
+
//! @expr{success@} and @expr{failure@}. They will be provided
+
//! as arguments two and onwards when the callbacks are called.
+
//!
+
//! @returns
+
//! The new @[Future].
+
//!
+
//! @note
+
//! This method is used if your callbacks return a @[Future] again.
+
//!
+
//! @seealso
+
//! @[transform()], @[map_with()], @[recover_with]
+
this_program transform_with(function(mixed, mixed ... : this_program) success,
+
function(mixed, mixed ... : this_program)|void failure,
+
mixed ... extra)
+
{
+
Promise p = Promise();
+
on_success(apply_flat, p, success, extra);
+
on_failure(apply_flat, p, failure || success, extra);
+
return p->future();
+
}
+
+
//! @param others
+
//! The other futures (results) you want to append.
+
//!
+
//! @returns
+
//! A new @[Future] that will be fulfilled with an
+
//! array of the fulfilled result of this object followed
+
//! by the fulfilled results of other futures.
+
//!
+
//! @seealso
+
//! @[results()]
+
this_program zip(array(this_program) others)
+
{
+
if (sizeof(others))
+
return results(({ this_program::this }) + others);
+
return this_program::this;
+
}
+
inline variant this_program zip(this_program ... others)
+
{
+
return zip(others);
+
}
+
+
//! JavaScript Promise API close but not identical equivalent
+
//! of a combined @[transform()] and @[transform_with()].
+
//!
+
//! @param onfulfilled
+
//! Function to be called on fulfillment. The first argument will be the
+
//! result of @b{this@} @[Future].
+
//! The return value will be the result of the new @[Future].
+
//! If the return value already is a @[Future], pass it as-is.
+
//!
+
//! @param onrejected
+
//! Function to be called on failure. The first argument will be the
+
//! failure result of @b{this@} @[Future].
+
//! The return value will be the failure result of the new @[Future].
+
//! If the return value already is a @[Future], pass it as-is.
+
//!
+
//! @param extra
+
//! Any extra context needed for @expr{onfulfilled@} and
+
//! @expr{onrejected@}. They will be provided
+
//! as arguments two and onwards when the callbacks are called.
+
//!
+
//! @returns
+
//! The new @[Future].
+
//!
+
//! @seealso
+
//! @[transform()], @[transform_with()], @[thencatch()],
+
//! @[on_success()], @[Promise.success()],
+
//! @[on_failure()], @[Promise.failure()],
+
//! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
+
this_program then(void|function(mixed, mixed ... : mixed) onfulfilled,
+
void|function(mixed, mixed ... : mixed) onrejected,
+
mixed ... extra) {
+
Promise p = Promise();
+
if (onfulfilled)
+
on_success(apply_smart, p, onfulfilled, extra);
+
else
+
on_success(p->success);
+
if (onrejected)
+
on_failure(apply_smart, p, onrejected, extra);
+
else
+
on_failure(p->failure);
+
return p->future();
+
}
+
+
//! JavaScript Promise API equivalent of a combination of @[recover()]
+
//! and @[recover_with()].
+
//!
+
//! @param onrejected
+
//! Function to be called. The first argument will be the
+
//! failure result of @b{this@} @[Future].
+
//! The return value will the failure result of the new @[Future].
+
//! If the return value already is a @[Future], pass it as-is.
+
//!
+
//! @param extra
+
//! Any extra context needed for
+
//! @expr{onrejected@}. They will be provided
+
//! as arguments two and onwards when the callback is called.
+
//!
+
//! @returns
+
//! The new @[Future].
+
//!
+
//! @seealso
+
//! @[recover()], @[recover_with()], @[then()], @[on_failure()],
+
//! @[Promise.failure()],
+
//! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
+
inline this_program thencatch(function(mixed, mixed ... : mixed) onrejected,
+
mixed ... extra) {
+
return then(0, onrejected, @extra);
+
}
+
+
//! Return a @[Future] that will either be fulfilled with the fulfilled
+
//! result of this @[Future], or be failed after @[seconds] have expired.
+
this_program timeout(int|float seconds)
+
{
+
Promise p = Promise();
+
on_failure(p->failure);
+
on_success(p->success);
+
call_out(p->try_failure, seconds, ({ "Timeout.\n", backtrace() }));
+
return p->future();
+
}
+
+
protected string _sprintf(int t)
+
{
+
return t=='O' && sprintf("%O(%s,%O)", this_program,
+
([ STATE_PENDING : "pending",
+
STATE_REJECTED : "rejected",
+
STATE_FULFILLED : "fulfilled" ])[state],
+
result);
+
}
+
}
+
+
protected class AggregateState
+
{
+
private Promise promise;
+
private int(0..) promises;
+
private int(0..) succeeded, failed;
+
final array(mixed) results;
+
final int(0..) min_failures;
+
final int(-1..) max_failures;
+
final mixed accumulator;
+
final function(mixed, mixed, mixed ... : mixed) fold_fun;
+
final array(mixed) extra;
+
+
private void create(Promise p)
+
{
+
if (p->_materialised || p->_materialised++)
+
error("Cannot materialise a Promise more than once.\n");
+
promise = p;
+
}
+
+
final void materialise()
+
{
+
if (promise->_astate)
+
{
+
promise->_astate = 0;
+
if (results)
+
{
+
promises = sizeof(results);
+
array(Future) futures = results;
+
if (fold_fun)
+
results = 0;
+
foreach(futures; int idx; Future f)
+
f->on_failure(cb_failure, idx)->on_success(cb_success, idx);
+
}
+
}
+
}
+
+
private void fold_one(mixed val)
+
{
+
mixed err = catch (accumulator = fold_fun(val, accumulator, @extra));
+
if (err && promise)
+
promise->failure(err);
+
}
+
+
private void fold(function(mixed:void) failsucc)
+
{
+
failsucc(fold_fun ? accumulator : results);
+
results = 0; // Free memory
+
}
+
+
private void cb_failure(mixed value, int idx)
+
{
+
Promise p; // Cache it, to cover a failure race
+
if (p = promise)
+
{
+
Thread.MutexKey key = mux->lock();
+
do
+
{
+
if (!p->state)
+
{
+
++failed;
+
if (max_failures < failed && max_failures >= 0)
+
{
+
key = 0;
+
p->try_failure(value);
+
break;
+
}
+
int success = succeeded + failed == promises;
+
key = 0;
+
if (results)
+
results[idx] = value;
+
else
+
fold_one(value);
+
if (success)
+
{
+
fold(failed >= min_failures ? p->success : p->failure);
+
break;
+
}
+
}
+
return;
+
} while (0);
+
promise = 0; // Free backreference
+
}
+
}
+
+
private void cb_success(mixed value, int idx)
+
{
+
Promise p; // Cache it, to cover a failure race
+
if (p = promise)
+
{
+
Thread.MutexKey key = mux->lock();
+
do
+
{
+
if (!p->state)
+
{
+
++succeeded;
+
if (promises - min_failures < succeeded)
+
{
+
key = 0;
+
p->try_failure(value);
+
break;
+
}
+
int success = succeeded + failed == promises;
+
key = 0;
+
if (results)
+
results[idx] = value;
+
else
+
fold_one(value);
+
if (success)
+
{
+
fold(p->success);
+
break;
+
}
+
}
+
return;
+
} while (0);
+
promise = 0; // Free backreference
+
}
+
}
+
}
+
+
//! Promise to provide a @[Future] value.
+
//!
+
//! Objects of this class are typically kept internal to the
+
//! code that provides the @[Future] value. The only thing
+
//! that is directly returned to the user is the return
+
//! value from @[future()].
+
//!
+
//! @seealso
+
//! @[Future], @[future()]
+
class Promise
+
{
+
inherit Future;
+
+
final int _materialised;
+
final AggregateState _astate;
+
+
//! Creates a new promise, optionally initialised from a traditional callback
+
//! driven method via @expr{executor(resolve, reject, extra ... )@}.
+
//!
+
//! @seealso
+
//! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
+
protected void create(void|
+
function(function(mixed:void),
+
function(mixed:void), mixed ...:void) executor, mixed ... extra)
+
{
+
if (executor)
+
executor(success, failure, @extra);
+
}
+
+
Future on_success(function(mixed, mixed ... : void) cb, mixed ... extra)
+
{
+
if (_astate)
+
_astate->materialise();
+
return ::on_success(cb, @extra);
+
}
+
+
Future on_failure(function(mixed, mixed ... : void) cb, mixed ... extra)
+
{
+
if (_astate)
+
_astate->materialise();
+
return ::on_failure(cb, @extra);
+
}
+
+
mixed get()
+
{
+
if (_astate)
+
_astate->materialise();
+
return ::get();
+
}
+
+
//! The future value that we promise.
+
Future future()
+
{
+
return Future::this;
+
}
+
+
protected this_program finalise(State newstate, mixed value, int try,
+
array(array(function(mixed, mixed ...: void)|array(mixed))) cbs,
+
void|function(mixed : void) globalfailure)
+
{
+
Thread.MutexKey key = mux->lock();
+
if (!state)
+
{
+
state = newstate;
+
result = value;
+
key = 0;
+
cond->broadcast();
+
if (sizeof(cbs))
+
{
+
foreach(cbs; ; array cb)
+
if (cb)
+
callout(cb[0], 0, value, @cb[1..]);
+
}
+
else if (globalfailure)
+
callout(globalfailure, 0, value);
+
failure_cbs = success_cbs = 0; // Free memory and references
+
}
+
else
+
{
+
key = 0;
+
if (!try)
+
error("Promise has already been finalised.\n");
+
}
+
return this_program::this;
+
}
+
+
//! @decl this_program success(mixed value)
+
//!
+
//! Fulfill the @[Future].
+
//!
+
//! @param value
+
//! Result of the @[Future].
+
//!
+
//! @throws
+
//! Throws an error if the @[Future] already has been fulfilled
+
//! or failed.
+
//!
+
//! Mark the @[Future] as fulfilled, and schedule the @[on_success()]
+
//! callbacks to be called as soon as possible.
+
//!
+
//! @seealso
+
//! @[try_success()], @[try_failure()], @[failure()], @[on_success()]
+
this_program success(mixed value, void|int try)
+
{
+
return finalise(STATE_FULFILLED, value, try, success_cbs);
+
}
+
+
//! Fulfill the @[Future] if it hasn't been fulfilled or failed already.
+
//!
+
//! @param value
+
//! Result of the @[Future].
+
//!
+
//! Mark the @[Future] as fulfilled if it hasn't already been fulfilled
+
//! or failed, and in that case schedule the @[on_success()] callbacks
+
//! to be called as soon as possible.
+
//!
+
//! @seealso
+
//! @[success()], @[try_failure()], @[failure()], @[on_success()]
+
inline this_program try_success(mixed value)
+
{
+
return state ? this_program::this : success(value, 1);
+
}
+
+
//! @decl this_program failure(mixed value)
+
//!
+
//! Reject the @[Future] value.
+
//!
+
//! @param value
+
//! Failure result of the @[Future].
+
//!
+
//! @throws
+
//! Throws an error if the @[Future] already has been fulfilled
+
//! or failed.
+
//!
+
//! Mark the @[Future] as failed, and schedule the @[on_failure()]
+
//! callbacks to be called as soon as possible.
+
//!
+
//! @seealso
+
//! @[try_failure()], @[success()], @[on_failure()]
+
this_program failure(mixed value, void|int try)
+
{
+
return
+
finalise(STATE_REJECTED, value, try, failure_cbs, global_on_failure);
+
}
+
+
//! Maybe reject the @[Future] value.
+
//!
+
//! @param value
+
//! Failure result of the @[Future].
+
//!
+
//! Mark the @[Future] as failed if it hasn't already been fulfilled,
+
//! and in that case schedule the @[on_failure()] callbacks to be
+
//! called as soon as possible.
+
//!
+
//! @seealso
+
//! @[failure()], @[success()], @[on_failure()]
+
inline this_program try_failure(mixed value)
+
{
+
return state ? this_program::this : failure(value, 1);
+
}
+
+
inline private void fill_astate()
+
{
+
if (!_astate)
+
_astate = AggregateState(this);
+
}
+
+
//! Add futures to the list of futures which the current object depends upon.
+
//!
+
//! If called without arguments it will produce a new @[Future]
+
//! from a new @[Promise] which is implictly added to the dependency list.
+
//!
+
//! @param futures
+
//! The list of @expr{futures@} we want to add to the list we depend upon.
+
//!
+
//! @returns
+
//! The new @[Promise].
+
//!
+
//! @note
+
//! Can be called multiple times to add more.
+
//!
+
//! @note
+
//! Once the promise has been materialised (when either @[on_success()],
+
//! @[on_failure()] or @[get()] has been called on this object), it is
+
//! not possible to call @[depend()] anymore.
+
//!
+
//! @seealso
+
//! @[fold()], @[first_completed()], @[max_failures()], @[min_failures()],
+
//! @[any_results()], @[Concurrent.results()], @[Concurrent.all()]
+
this_program depend(array(Future) futures)
+
{
+
if (sizeof(futures)) {
+
fill_astate();
+
_astate->results += futures;
+
}
+
return this_program::this;
+
}
+
inline variant this_program depend(Future ... futures)
+
{
+
return depend(futures);
+
}
+
variant this_program depend()
+
{
+
Promise p = Promise();
+
depend(p->future());
+
return p;
+
}
+
+
//! @param initial
+
//! Initial value of the accumulator.
+
//!
+
//! @param fun
+
//! Function to apply. The first argument is the result of
+
//! one of the @[futures]. The second argument is the current value
+
//! of the accumulator.
+
//!
+
//! @param extra
+
//! Any extra context needed for @[fun]. They will be provided
+
//! as arguments three and onwards when @[fun] is called.
+
//!
+
//! @returns
+
//! The new @[Promise].
+
//!
+
//! @note
+
//! If @[fun] throws an error it will fail the @[Future].
+
//!
+
//! @note
+
//! @[fun] may be called in any order, and will be called
+
//! once for every @[Future] it depends upon, unless one of the
+
//! calls fails in which case no further calls will be
+
//! performed.
+
//!
+
//! @seealso
+
//! @[depend()], @[Concurrent.fold()]
+
this_program fold(mixed initial,
+
function(mixed, mixed, mixed ... : mixed) fun,
+
mixed ... extra)
+
{
+
if (_astate) {
+
_astate->accumulator = initial;
+
_astate->fold_fun = fun;
+
_astate->extra = extra;
+
_astate->materialise();
+
} else
+
success(initial);
+
return this_program::this;
+
}
+
+
//! It evaluates to the first future that completes of the list
+
//! of futures it depends upon.
+
//!
+
//! @returns
+
//! The new @[Promise].
+
//!
+
//! @seealso
+
//! @[depend()], @[Concurrent.first_completed()]
+
this_program first_completed()
+
{
+
if (_astate) {
+
_astate->results->on_failure(try_failure)->on_success(try_success);
+
_astate = 0;
+
} else
+
success(0);
+
return this_program::this;
+
}
+
+
//! @param max
+
//! Specifies the maximum number of failures to be accepted in
+
//! the list of futures this promise depends upon.
+
//!
+
//! @expr{-1@} means unlimited.
+
//!
+
//! Defaults to @expr{0@}.
+
//!
+
//! @returns
+
//! The new @[Promise].
+
//!
+
//! @seealso
+
//! @[depend()], @[min_failures()], @[any_results()]
+
this_program max_failures(int(-1..) max)
+
{
+
fill_astate();
+
_astate->max_failures = max;
+
return this_program::this;
+
}
+
+
//! @param min
+
//! Specifies the minimum number of failures to be required in
+
//! the list of futures this promise depends upon. Defaults
+
//! to @expr{0@}.
+
//!
+
//! @returns
+
//! The new @[Promise].
+
//!
+
//! @seealso
+
//! @[depend()], @[max_failures()]
+
this_program min_failures(int(0..) min)
+
{
+
fill_astate();
+
_astate->min_failures = min;
+
return this_program::this;
+
}
+
+
//! Sets the number of failures to be accepted in the list of futures
+
//! this promise
+
//! depends upon to unlimited. It is equivalent to @expr{max_failures(-1)@}.
+
//!
+
//! @returns
+
//! The new @[Promise].
+
//!
+
//! @seealso
+
//! @[depend()], @[max_failures()]
+
this_program any_results()
+
{
+
return max_failures(-1);
+
}
+
+
protected void _destruct()
+
{
+
if (!state)
+
try_failure(({ "Promise broken.\n", backtrace() }));
+
}
+
}
+
+
//! @returns
+
//! A @[Future] that represents the first
+
//! of the @expr{futures@} that completes.
+
//!
+
//! @seealso
+
//! @[race()], @[Promise.first_completed()]
+
variant Future first_completed(array(Future) futures)
+
{
+
return Promise()->depend(futures)->first_completed()->future();
+
}
+
variant inline Future first_completed(Future ... futures)
+
{
+
return first_completed(futures);
+
}
+
+
//! JavaScript Promise API equivalent of @[first_completed()].
+
//!
+
//! @seealso
+
//! @[first_completed()], @[Promise.first_completed()]
+
//! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
+
variant inline Future race(array(Future) futures)
+
{
+
return first_completed(futures);
+
}
+
variant inline Future race(Future ... futures)
+
{
+
return first_completed(futures);
+
}
+
+
//! @returns
+
//! A @[Future] that represents the array of all the completed @expr{futures@}.
+
//!
+
//! @seealso
+
//! @[all()], @[Promise.depend()]
+
variant Future results(array(Future) futures)
+
{
+
if(!sizeof(futures))
+
return resolve(({}));
+
+
return Promise()->depend(futures)->future();
+
}
+
inline variant Future results(Future ... futures)
+
{
+
return results(futures);
+
}
+
+
//! JavaScript Promise API equivalent of @[results()].
+
//!
+
//! @seealso
+
//! @[results()], @[Promise.depend()]
+
//! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
+
inline variant Future all(array(Future) futures)
+
{
+
return results(futures);
+
}
+
inline variant Future all(Future ... futures)
+
{
+
return results(futures);
+
}
+
+
//! @returns
+
//! A new @[Future] that has already failed for the specified @expr{reason@}.
+
//!
+
//! @seealso
+
//! @[Future.on_failure()], @[Promise.failure()]
+
//! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
+
Future reject(mixed reason)
+
{
+
return Promise()->failure(reason)->future();
+
}
+
+
//! @returns
+
//! A new @[Future] that has already been fulfilled with @expr{value@}
+
//! as result. If @expr{value@} is an object which already
+
//! has @[on_failure] and @[on_success] methods, return it unchanged.
+
//!
+
//! @note
+
//! This function can be used to ensure values are futures.
+
//!
+
//! @seealso
+
//! @[Future.on_success()], @[Promise.success()]
+
//! @url{https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise@}
+
Future resolve(mixed value)
+
{
+
if (objectp(value) && value->on_failure && value->on_success)
+
return value;
+
return Promise()->success(value)->future();
+
}
+
+
//! Return a @[Future] that represents the array of mapping @[fun]
+
//! over the results of the completed @[futures].
+
Future traverse(array(Future) futures,
+
function(mixed, mixed ... : mixed) fun,
+
mixed ... extra)
+
{
+
return results(futures->map(fun, @extra));
+
}
+
+
//! Return a @[Future] that represents the accumulated results of
+
//! applying @[fun] to the results of the @[futures] in turn.
+
//!
+
//! @param initial
+
//! Initial value of the accumulator.
+
//!
+
//! @param fun
+
//! Function to apply. The first argument is the result of
+
//! one of the @[futures], the second the current accumulated
+
//! value, and any further from @[extra].
+
//!
+
//! @note
+
//! If @[fun] throws an error it will fail the @[Future].
+
//!
+
//! @note
+
//! @[fun] may be called in any order, and will be called
+
//! once for every @[Future] in @[futures], unless one of
+
//! calls fails in which case no further calls will be
+
//! performed.
+
Future fold(array(Future) futures,
+
mixed initial,
+
function(mixed, mixed, mixed ... : mixed) fun,
+
mixed ... extra)
+
{
+
return Promise()->depend(futures)->fold(initial, fun, extra)->future();
+
}
Newline at end of file added.