Overly attached promise

Document number: N4445
Date: 2015-04-09
Project: Programming Language C++, Concurrency and Parallelism Study Group
Reply-to: Agustín Bergé agustinberge@gmail.com, Hartmut Kaiser hartmut.kaiser@gmail.com

Overly attached promise

1. Introduction

This paper proposes changes and additions to std::promise to allow an efficient use of its shared state.

2. Motivation

A std::promise explicitly specifies when its shared state is released, and under certain circumstances it clings to it and keeps it around long after it is needed. Since releasing the shared state may cause a user-defined destructor to run, and that would be an observable effect, there is little wiggle room for implementations to be more efficient under the as-if rule. Implementors should be allowed to release the shared state early as an optimization, reclaiming the shared state resources (which might be limited resources on some systems) as well as the dynamic memory used by it (if any).

std::promise<int> p;
p.get_future();
p.set_value(42);
// p's shared state no longer needed, sticks around until ~promise()
...
 
std::promise<int>{}.set_value_at_thread_exit(42);
// shared state no longer needed, sticks around until thread exit (maybe)
...

The intention of this paper is to allow (but not require) an implementation to release a promise's shared state as early as possible, once both the future has been acquired and the shared state has been made ready, or once the last future releases the shared state of a promise satisfied via one of the *_at_thread_exit member functions.

Some existing implementations choose to bundle the state of the promise itself into its shared state. Such implementations would not be able to retain their state after releasing the shared state, hence the need for explicit ways to accomplish the same effects. This can be already accomplished today with the following constructs:

p = std::promise<int>{};         // okayish
std::promise<int>(std::move(p)); // better

The first line effectively resets the promise, it first constructs a new shared state and then replaces the current state, abandoning the old one. The second line effectively releases the promise, abandoning any shared state. This paper additionally proposes two new promise member functions: reset and release, with the aforementioned effects. It should be noted that a reset member function could potentially recycle the current shared state, provided it is its sole owner (either the future was never obtained or it has already released this shared state).

Finally, this paper includes a drive-by fix to future::get's wording, which does not specify that the shared state is released.

3. Implementability

This implementation builds on top of any current implementation. It is sufficient to meet this paper's requirements, but is not as efficient as intended by it.

class promise {
  // everything else as today
 
  void reset() { *this = promise{}; }
 
  template <class Allocator>
  void reset(const Allocator& a) { *this = promise{allocator_arg, a}; }
 
  void release() noexcept { promise{std::move(*this)}; }
};

4. Proposed Wording

This wording is relative to [N4296].

4.1 Shared state early release

Change 30.6.4 [futures.state], shared state, as indicated.

Add the following as a new paragraph after paragraph 11:

Implementations are allowed to execute user-defined destructors for shared state results without releasing the shared state when this cannot be otherwise observed.

4.2 promise::reset

Change 30.6.5 [futures.promise], class template promise, as indicated.

Add the following at the end of the synopsis:

// modifiers
void reset();
template <class Allocator>
  void reset(const Allocator& a);

Add the following as a new paragraph after paragraph 26:

void promise::reset();
template <class Allocator>
  void promise::reset(const Allocator& a);
  • Effects: Abandons any shared state (30.6.4) and then as if promise().swap(*this) for the first version and as if promise(allocator_arg, a).swap(*this) for the second version.

4.3 promise::release

Change 30.6.5 [futures.promise], class template promise, as indicated.

Add the following at the end of the synopsis:

// modifiers
void release() noexcept;

Add the following as a new paragraph after paragraph 26:

void promise::release() noexcept;
  • Effects: Abandons any shared state (30.6.4).

4.4 future::get

Change 30.6.6 [futures.unique_future], class template future, as indicated.

Change paragraph 15 as indicated:

  • Effects:

    • wait()s until the shared state is ready, then retrieves the value stored in the shared state.

    • releases the shared state (30.6.4).

5. References