Re: [future] Early draft of wait for multiple futures interface
After thinking more carefully on the problem I've realized it is implementable using condition variables. Here is a revised proposal, which - for now - excludes barriers.
---------------------------- Proposal ----------------------------
Add class future_expression that basically is a function which depend on futures values.
- A typed evaluation function (some work) is associated with each future dependency
--- This evaluation can result in that the future_expressions becomes ready
- A future dependency is a parent child relationship. This forms a graph where "promise-futures" are leafs.
--- You can wait() on any future in the graph
- When a future is completed it tells it's parents they need to re-evaluate via a future-complete callback
--- The future_expression that needs re-evaluation will:
--- 1. Schedule the associated evaluation to be run on the client thread on next wait or is_ready is called
--- 2. Notifies it's condition in case the client thread was waiting on this particular node
--- 3. Signals it's parents they too need to re-evaluate
------ This way, the notifications propagate upwards to the root future
------ Some future_expression might be scheduled for re-evaluation even though
- When a future expression becomes ready it drops it's shared ownership to all depending childs
--- They are no longer needed and can die
--- This is done as a part of the pending evaluations on the client thread
- For now this is not a concurrent object, neither are the futures formed from it
--- All evaluation code can thus be single threaded
--- We can change this so that the work of evaluating a future is done by the first thread waiting on it.
Note:
- The callback is ONLY usable by future_exprs. - No user code can be executed by promise fulfilling code
- Other abstractions build on future_expressions rather than the dangerous complete-callback
---------------------------- Part of interface ----------------------------
template<class T>
struct future_result
{
T running_result_;
bool value_is_final_;
optional<exception> exception_;
...
};
/// A representation of a function which depends on futures.
///
template <class T>
class future_expression
{
/// Default value if no dependencies are added
future_expression(T starting_value);
/// Add a future alongside with an evaluation function which will be called lazily by waiting threads
template<class U>
void add_dependency<U> (shared_future<U> f,
function<void(U future_value, future_result<T>& result_setter)> on_ready);
/// Perform pending evaluations. If none sets an exception or result wait on the internal condition
void wait();
/// If not ready, perform pending evaluations. If none sets an exception or result, return false
bool is_ready();
};
---------------------------- Example code ----------------------------
void set_if_true(bool b, result& result_setter)
{
if (b)
{
result_setter.running_value_ = true;
result_setter.value_is_final_ = true;
}
}
template<class T>
future<bool> operator||(future<bool> lhs, future<bool> rhs)
{
future_expression<R> exp(false);
exp.add_dependency(a1, &set_if_true);
exp.add_dependency(a2, &set_if_true);
return future<R>(exp);
}
template<class T>
void running_add(T value, result& result_setter)
{
result_setter.running_value_ += value;
}
template<class T>
future<T> sum(vector<future<T>> futures)
{
future_expression<T> exp(value_initialized<T>());
for f in futures
exp.add_dependency(f, &running_add);
return future<T>(exp);
}
---------------------------- Conclusions ----------------------------
This mechanism would solve all the issues we've found. Most importantly it allows making composite futures while prohibiting execution of user code while fulfilling promises.
Unfortunately, I feel it might be a little cumbersome to use. Also, since we need wait_for_many support it needs to get accepted alongside with the first version of the future library. I was hoping this version could be very lightweight.
What do you think? Any quick thoughts?
Johan