pike.git / lib / modules / Concurrent.pmod

version» Context lines:

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 { + local protected enum State { +  STATE_NO_FUTURE = -1,    STATE_PENDING = 0,    STATE_FULFILLED,    STATE_REJECTED, -  +  STATE_REJECTION_REPORTED,   };      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; + protected function(mixed : void) global_on_failure = master()->handle_error;      //! @param enable - //! Setting this to @expr{false@} causes all @[Concurrent] callbacks - //! (except for timeouts) - //! to be called directly, without using a backend. + //! @int + //! @value 0 + //! A @expr{false@} value causes all @[Concurrent] callbacks + //! (except for timeouts) to default to being called directly, + //! without using a backend. + //! @value 1 + //! A @expr{true@} value causes callbacks to default to being + //! called via @[Pike.DefaultBackend]. + //! @endint   //!   //! @note -  + //! Be very careful about running in the backend disabled mode, + //! as it may cause unlimited recursion and reentrancy issues. + //! + //! @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). -  + //! + //! @seealso + //! @[Future()->set_backend()], @[Future()->call_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));
pike.git/lib/modules/Concurrent.pmod:50:    master()->handle_error(err);    return 0;   }      private void auto_use_backend()   {    callout = call_out;   }      protected function(function(mixed ...:void), int|float, mixed ...:mixed) -  callout; +  callout = ((callout = callnow), call_out(auto_use_backend, 0), 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. +  protected Pike.Backend backend; +  +  //! Set the backend to use for calling any callbacks.    //! -  //! @throws -  //! Throws on rejection. -  mixed get() +  //! @note +  //! This overides the mode set by @[use_backend()]. +  //! +  //! @seealso +  //! @[get_backend()], @[use_backend()] +  void set_backend(Pike.Backend backend)    { -  State s = state; -  mixed res = result; -  if (!s) { +  this::backend = backend; +  } +  +  //! Get the backend (if any) used to call any callbacks. +  //! +  //! This returns the value set by @[set_backend()]. +  //! +  //! @seealso +  //! @[set_backend()] +  Pike.Backend get_backend() +  { +  return backend; +  } +  +  //! Create a new @[Promise] with the same base settings +  //! as the current object. +  //! +  //! Overload this function if you need to propagate more state +  //! to new @[Promise] objects. +  //! +  //! The default implementation copies the backend +  //! setting set with @[set_backend()] to the new @[Promise]. +  //! +  //! @seealso +  //! @[Promise], @[set_backend()] +  Promise promise_factory() +  { +  Promise res = Promise(); +  +  if (backend) { +  res->set_backend(backend); +  } +  +  return res; +  } +  +  //! Call a callback function. +  //! +  //! @param cb +  //! Callback function to call. +  //! +  //! @param args +  //! Arguments to call @[cb] with. +  //! +  //! The default implementation calls @[cb] via the +  //! backend set via @[set_backend()] (if any), and +  //! otherwise falls back the the mode set by +  //! @[use_backend()]. +  //! +  //! @seealso +  //! @[set_backend()], @[use_backend()] +  protected void call_callback(function cb, mixed ... args) +  { +  (backend ? backend->call_out : callout)(cb, 0, @args); +  } +  +  //! Wait for fulfillment. +  //! +  //! @seealso +  //! @[get()] +  this_program wait() +  { +  if (state <= STATE_PENDING) {    Thread.MutexKey key = mux->lock(); -  while (!state) { +  while (state <= STATE_PENDING) {    cond->wait(key);    } -  +  }    -  s = state; -  res = result; +  return this;    }    -  if (s == STATE_REJECTED) { -  throw(res); +  //! Wait for fulfillment and return the value. +  //! +  //! @throws +  //! Throws on rejection. +  //! +  //! @seealso +  //! @[wait()] +  mixed get() +  { +  wait(); +  +  if (state >= STATE_REJECTED) { +  throw(result);    } -  return res; +  return result;    }       //! 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
pike.git/lib/modules/Concurrent.pmod:116:    //!    //! @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); +  call_callback(cb, result, @extra);    break; -  +  case STATE_NO_FUTURE:    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.    //!
pike.git/lib/modules/Concurrent.pmod:145:    //!    //! @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); +  state = STATE_REJECTION_REPORTED; +  // FALL_THROUGH +  case STATE_REJECTION_REPORTED: +  call_callback(cb, result, @extra);    break; -  +  case STATE_NO_FUTURE:    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.
pike.git/lib/modules/Concurrent.pmod:245:    //! 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(); +  Promise p = promise_factory();    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
pike.git/lib/modules/Concurrent.pmod:276:    //! 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(); +  Promise p = promise_factory();    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,
pike.git/lib/modules/Concurrent.pmod:317:    //!    //! @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(); +  Promise p = promise_factory();    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
pike.git/lib/modules/Concurrent.pmod:348:    //! 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(); +  Promise p = promise_factory();    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
pike.git/lib/modules/Concurrent.pmod:378:    //! 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(); +  Promise p = promise_factory();    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].
pike.git/lib/modules/Concurrent.pmod:416:    //! @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(); +  Promise p = promise_factory();    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].
pike.git/lib/modules/Concurrent.pmod:455:    //!    //! @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(); +  Promise p = promise_factory();    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
pike.git/lib/modules/Concurrent.pmod:513:    //! 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(); +  Promise p = promise_factory();    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();    }
pike.git/lib/modules/Concurrent.pmod:551:    //!    //! @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);    }    +  private this_program setup_call_out(int|float seconds, void|int tout) +  { +  array call_out_handle; +  Promise p = promise_factory(); +  void cancelcout(mixed value) +  { +  (backend ? backend->remove_call_out : remove_call_out)(call_out_handle); +  p->try_success(0); +  } +  /* NB: try_* variants as the original promise may get fulfilled +  * after the timeout has occurred. +  */ +  on_failure(cancelcout); +  call_out_handle = (backend ? backend->call_out : call_out) +  (p[tout ? "try_failure" : "try_success"], seconds, +  tout && ({ "Timeout.\n", backtrace() })); +  if (tout) +  on_success(cancelcout); +  return p->future(); +  } +     //! 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(); +  return first_completed( +  ({ this_program::this, setup_call_out(seconds, 1) }) +  );    }    -  +  //! Return a @[Future] that will be fulfilled with the fulfilled +  //! result of this @[Future], but not until at least @[seconds] have passed. +  this_program delay(int|float seconds) +  { +  return results( +  ({ this_program::this, setup_call_out(seconds) }) +  )->map(`[], 0); +  } +     protected string _sprintf(int t)    {    return t=='O' && sprintf("%O(%s,%O)", this_program, -  ([ STATE_PENDING : "pending", +  ([ STATE_NO_FUTURE : "no future", +  STATE_PENDING : "pending",    STATE_REJECTED : "rejected", -  +  STATE_REJECTION_REPORTED : "rejection_reported",    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) +  protected 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)    {
pike.git/lib/modules/Concurrent.pmod:629:    }       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) +  if (p->state <= STATE_PENDING)    {    ++failed;    if (max_failures < failed && max_failures >= 0)    {    key = 0;    p->try_failure(value);    break;    }    int success = succeeded + failed == promises;    key = 0;
pike.git/lib/modules/Concurrent.pmod:664:    }       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) +  if (p->state <= STATE_PENDING)    {    ++succeeded;    if (promises - min_failures < succeeded)    {    key = 0;    p->try_failure(value);    break;    }    int success = succeeded + failed == promises;    key = 0;
pike.git/lib/modules/Concurrent.pmod:709:   //! @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 ... )@}. +  //! driven method via @expr{executor(success, failure, @@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)    { -  +  state = STATE_NO_FUTURE;    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);    }    -  +  Future wait() +  { +  if (_astate) +  _astate->materialise(); +  return ::wait(); +  } +     mixed get()    {    if (_astate)    _astate->materialise();    return ::get();    }       //! The future value that we promise.    Future future()    { -  +  if (state == STATE_NO_FUTURE) state = STATE_PENDING;    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) +  array(array(function(mixed, mixed ...: void)|array(mixed))) cbs)    {    Thread.MutexKey key = mux->lock(); -  if (!state) +  if (state <= STATE_PENDING)    {    state = newstate;    result = value;    key = 0;    cond->broadcast();    if (sizeof(cbs))    {    foreach(cbs; ; array cb)    if (cb) -  callout(cb[0], 0, value, @cb[1..]); +  call_callback(cb[0], value, @cb[1..]); +  if (newstate == STATE_REJECTED) { +  state = STATE_REJECTION_REPORTED;    } -  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;    }
pike.git/lib/modules/Concurrent.pmod:812:    //! 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); +  return (state > STATE_PENDING) ? this_program::this : success(value, 1);    }       //! @decl this_program failure(mixed value)    //!    //! Reject the @[Future] value.    //!    //! @param value    //! Failure result of the @[Future].    //!    //! @throws
pike.git/lib/modules/Concurrent.pmod:834:    //! 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); +  finalise(STATE_REJECTED, value, try, failure_cbs);    }       //! 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); +  return (state > STATE_PENDING) ? 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.    //!
pike.git/lib/modules/Concurrent.pmod:895:    _astate->results += futures;    }    return this_program::this;    }    inline variant this_program depend(Future ... futures)    {    return depend(futures);    }    variant this_program depend()    { -  Promise p = Promise(); +  Promise p = promise_factory();    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
pike.git/lib/modules/Concurrent.pmod:1011:    //!    //! @seealso    //! @[depend()], @[max_failures()]    this_program any_results()    {    return max_failures(-1);    }       protected void _destruct()    { -  if (!state) -  try_failure(({ "Promise broken.\n", backtrace() })); +  // NB: Don't complain about dropping STATE_NO_FUTURE on the floor. +  if (state == STATE_PENDING) +  try_failure(({ sprintf("%O: Promise broken.\n", this), backtrace() })); +  if ((state == STATE_REJECTED) && global_on_failure) +  call_callback(global_on_failure, result); +  result = UNDEFINED;    }   }      //! @returns   //! A @[Future] that represents the first   //! of the @expr{futures@} that completes.   //! -  + //! @note + //! The returned @[Future] does NOT have any state (eg backend) + //! propagated from the @[futures]. This must be done by hand. + //!   //! @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()].   //! -  + //! @note + //! The returned @[Future] does NOT have any state (eg backend) + //! propagated from the @[futures]. This must be done by hand. + //!   //! @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@}.   //! -  + //! @note + //! The returned @[Future] does NOT have any state (eg backend) + //! propagated from the @[futures]. This must be done by hand. + //!   //! @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()].   //! -  + //! @note + //! The returned @[Future] does NOT have any state (eg backend) + //! propagated from the @[futures]. This must be done by hand. + //!   //! @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@}.   //! -  + //! @note + //! The returned @[Future] does NOT have a backend set. + //!   //! @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.   //! -  + //! @note + //! The returned @[Future] does NOT have a backend set. + //!   //! @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]. -  + //! + //! @note + //! The returned @[Future] does NOT have any state (eg backend) + //! propagated from the @[futures]. This must be done by hand.   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.   //!
pike.git/lib/modules/Concurrent.pmod:1133:   //! 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. + //! + //! @note + //! The returned @[Future] does NOT have any state (eg backend) + //! propagated from the @[futures]. This must be done by hand.   Future fold(array(Future) futures,    mixed initial,    function(mixed, mixed, mixed ... : mixed) fun,    mixed ... extra)   {    return Promise()->depend(futures)->fold(initial, fun, extra)->future();   }