« Return to Thread: [future] Early draft of wait for multiple futures interface

[future] Early draft of wait for multiple futures interface

by Johan Torp :: Rate this Message:

Reply to Author | View in Thread

We've been discussing the need to implement a mechanism to wait for multiple futures in a separate thread: www.nabble.com/Review-Request%3A-future-library-%28Gaskill-version%29-to16600402.html

I've come up with some interfaces which I find intuitive and useful. I've never implemented thread waiting, so please let me know if you think something like this is viable.

------------------------------ Proposal  ------------------------------
A future can be constructed and thereafter wait on four different "future conditions"
 * a promise
 * a future_switch
 * a future_barrier
 * another future

Each condition has a specified return type and the futures you create from it must match this type. They all have the same possible states - isn't ready, has value or an expression. I have left these and alot of other details out of the interface below for conciseness.


------------------------------  Interface drafts  ------------------------------

/// Future condition which is ready when any of the added conditions is ready. Has an arbitrary return-type
template<class ReturnType>
class future_switch
{
public:

  /// Direct return
  template<class FutureConditionWithMatchingReturnType>
  void add_case(FutureConditionWithMatchingReturnType f);

  /// Possibility to perform custom functor, bind state, perform some logic etc before fulfilling it's condition
  template<class FutureCondition, class FutureTypeToReturnTypeFunctor>
  void add_case(FutureCondition f, FutureTypeToReturnTypeFunctor case);

  /// Only valid in is_ready state. Removes last fulfilled futurecondition and enables new futures to be  
  /// constructed. Existing futures will have copied the value or exception already and will still work.
  void remove_ready_case();

  bool empty() const;
};


/// Future condition which is ready when all added futures are.
/// This interface is less promising and has some flaws. Maybe you can get inspired by it though.
template<class ReturnType>
class future_barrier
{
public:
  future_barrier(const function<ReturnType()>& onReady);

  template<class FutureCondition>
  void add_condition(const future<FutureType>& f);
};

------------------------------  Example usage  ------------------------------

template<class T>
future_switch<T> operator||(const future<T>& lhs, const future<T>& rhs)
{
  future_switch<T> sw;
  sw.add_case(lhs);
  sw.add_case(rhs);
  return sw;
}

template<class T>
T and_impl(const future<T>& lhs, const future<T>& rhs)
{
  return lhs.get() && rhs.get();
}

template<class T>
future_barrier<T> operator&&(const future<T>& lhs, const future<T>& rhs)
{
  // Rather large runtime insecurity, we might forget to add lhs and rhs as conditions to the barrier
  future_barrier b(bind(&and_impl, lhs, rhs));
  b.add_condition(lhs);
  b.add_condition(rhs);
  return b;
}


// This is an experimental use case for using switch as a dynamic set of future conditions
// It handles three future responses and calls the three functions on the next lines

bool handle_response_a(ResponseA r);
bool handle_response_b(ResponseB r);
bool handle_response_c(ResponseC r);

void handle_pending_requests(const future<ResponseA>& a,
                                         const future<ResponseB >& b,
                                         const future<ResponseC >& c)
{
  future_switch<void> sw;

  sw.add_case(a, &handle_repsonse_a);
  sw.add_case(b, &handle_repsonse_b);
  sw.add_case(c, &handle_repsonse_c);
 
  bool all_ok = true;
  while (!sw.empty())
  {
     // Will automatically remove ok:ed futures from sw
     future<bool> next_response_was_ok(sw);
     bool all_ok = all_ok && next_response_was_ok.get();
     sw.remove_ready_case();
  }
 
  return all_ok;
}
------------------------------  Some comments  ------------------------------

There is a lot to improve here. I'd appreciate it if we could focus on determining whether this is a viable solution and the way we want to go before diving into details.

These abstractions has the following property:
A Users can compose conditions and poll them without the need of waiting.
B We do not need to spawn extra threads for waiting - we only wait on the "outmost" future
C Threads needn't awake and poll readiness - assuming the and/or logic is handled by waitingcode.
D No "user logic" from the future-blocking threads propagate to the promise-fulfilling thread.
E It's possible to implement efficient laziness by keeping readiness/waiting logic internal in the future library

"wait for all" can be implemented without adding future_barrier, by simply calling wait() on all futures. We lose property A and E for "wait for all" if we do it this way. The blocking thread will awake unnecessarily many times.

Property C means promise-fullfilling threads need to do some basic and/or logic. I think this can be implemented in a safe and efficient manner, O(log(N)) for barrier/all and O(1) for switch/any. I also think it is much preferable to waking and switching to future-blocking threads which re-check readiness conditions. Note property D.


Whatever mechanism we choose I think it's desirable to be able to easily "lift" existing functions to futures;
  R foo(A1 a1, A2 a2, A3 a3)
should be easily rewritable as;
  future<R> foo(future<A1> a1, A2 a2, future<A3> a3) // Some parameters need not be lifted


Johan

 « Return to Thread: [future] Early draft of wait for multiple futures interface