Skip to content

Commit b79ecc7

Browse files
authored
Implement future<void> and promise<void>. (#1367)
This implements futures and promises for `void`. It does not yet implement the `.then()` member function, to keep the PR size small. Part of the work for #1345.
1 parent 0e14724 commit b79ecc7

8 files changed

+922
-4
lines changed

google/cloud/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ add_library(google_cloud_cpp_common
103103
${CMAKE_CURRENT_BINARY_DIR}/internal/build_info.cc
104104
internal/filesystem.h
105105
internal/filesystem.cc
106+
internal/future_fwd.h
106107
internal/future_impl.h
108+
internal/future_void.h
107109
internal/getenv.h
108110
internal/getenv.cc
109111
internal/make_unique.h
@@ -164,6 +166,7 @@ set(google_cloud_cpp_common_unit_tests
164166
internal/big_endian_test.cc
165167
internal/filesystem_test.cc
166168
internal/future_impl_test.cc
169+
internal/future_void_test.cc
167170
internal/invoke_result_test.cc
168171
internal/random_test.cc
169172
internal/retry_policy_test.cc

google/cloud/google_cloud_cpp_common.bzl

+2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ google_cloud_cpp_common_HDRS = [
77
"internal/big_endian.h",
88
"internal/build_info.h",
99
"internal/filesystem.h",
10+
"internal/future_fwd.h",
1011
"internal/future_impl.h",
12+
"internal/future_void.h",
1113
"internal/getenv.h",
1214
"internal/make_unique.h",
1315
"internal/port_platform.h",

google/cloud/google_cloud_cpp_common_unit_tests.bzl

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ google_cloud_cpp_common_unit_tests = [
55
"internal/big_endian_test.cc",
66
"internal/filesystem_test.cc",
77
"internal/future_impl_test.cc",
8+
"internal/future_void_test.cc",
89
"internal/invoke_result_test.cc",
910
"internal/random_test.cc",
1011
"internal/retry_policy_test.cc",

google/cloud/internal/future_fwd.h

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_FUTURE_FWD_H_
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_FUTURE_FWD_H_
17+
18+
#include "google/cloud/internal/port_platform.h"
19+
20+
// C++ futures only make sense when exceptions are enabled.
21+
#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
22+
#include "google/cloud/version.h"
23+
24+
namespace google {
25+
namespace cloud {
26+
inline namespace GOOGLE_CLOUD_CPP_NS {
27+
// Forward declare the promise type so we can write some helpers.
28+
template <typename R>
29+
class promise;
30+
31+
// Forward declare the future type so we can write some helpers.
32+
template <typename R>
33+
class future;
34+
35+
// Forward declare the specializations for references.
36+
template <typename R>
37+
class promise<R&>;
38+
template <typename R>
39+
class future<R&>;
40+
41+
// Forward declare the specialization for `void`.
42+
template <>
43+
class promise<void>;
44+
template <>
45+
class future<void>;
46+
} // namespace GOOGLE_CLOUD_CPP_NS
47+
} // namespace cloud
48+
} // namespace google
49+
50+
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
51+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_FUTURE_FWD_H_

google/cloud/internal/future_impl.h

+37-1
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ class future_shared_state_base {
164164
current_state_ = state::has_exception;
165165
}
166166

167+
// My (@coryan) reading of the spec is that calling get_future() on a promise
168+
// should succeed exactly once, even when used from multiple threads. This
169+
// requires some kind of flag and synchronization primitive. The obvious
170+
// question is whether this flag should be in `promise<T>` or part of the
171+
// shared state. If it is a member of the shared state then it can be a
172+
// `std::atomic_flag`, which is guaranteed to be lock free and, well, atomic.
173+
// But an object of type `std::atomic_flag` (or `std::atomic<bool>`) cannot
174+
// be a member of `promise<T>` because such objects are not MoveConstructible,
175+
// and `promise<T>` must be. Once could implement this with an `std::mutex` +
176+
// a bool, but that is more overhead than just a flag here.
177+
/// Keep track of whether `get_future()` has been called.
178+
std::atomic_flag retrieved_ = ATOMIC_FLAG_INIT;
179+
167180
mutable std::mutex mu_;
168181
std::condition_variable cv_;
169182
enum class state {
@@ -220,6 +233,30 @@ class future_shared_state<void> final : private future_shared_state_base {
220233
cv_.notify_all();
221234
}
222235

236+
/**
237+
* The implementation details for `promise<void>::get_future()`.
238+
*
239+
* `promise<void>::get_future()` can be called exactly once, this function
240+
* must raise `std::future_error` if (quoting the C++ spec):
241+
*
242+
* `get_future` has already been called on a `promise` with the same shared
243+
* state as `*this`
244+
*
245+
* While it is not clear how one could create multiple promises pointing to
246+
* the same shared state, it is easier to keep all the locking and atomic
247+
* checks in one class.
248+
*
249+
* @throws std::future_error if the operation fails.
250+
*/
251+
static void mark_retrieved(std::shared_ptr<future_shared_state> const& sh) {
252+
if (not sh) {
253+
throw std::future_error(std::future_errc::no_state);
254+
}
255+
if (sh->retrieved_.test_and_set()) {
256+
throw std::future_error(std::future_errc::future_already_retrieved);
257+
}
258+
}
259+
223260
private:
224261
void set_value(std::unique_lock<std::mutex>& lk) {
225262
if (is_ready_unlocked()) {
@@ -235,5 +272,4 @@ class future_shared_state<void> final : private future_shared_state_base {
235272
} // namespace google
236273

237274
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
238-
239275
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_FUTURE_IMPL_H_

google/cloud/internal/future_impl_test.cc

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include "google/cloud/testing_util/chrono_literals.h"
1818
#include <gmock/gmock.h>
1919

20+
// C++ futures only make sense when exceptions are enabled.
21+
#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
2022
namespace google {
2123
namespace cloud {
2224
inline namespace GOOGLE_CLOUD_CPP_NS {
@@ -27,8 +29,6 @@ using ::testing::HasSubstr;
2729

2830
using namespace google::cloud::testing_util::chrono_literals;
2931

30-
// C++ futures only make sense when exceptions are enabled.
31-
#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
3232
TEST(FutureImplBaseTest, Basic) {
3333
future_shared_state_base shared_state;
3434
EXPECT_FALSE(shared_state.is_ready());
@@ -140,10 +140,10 @@ TEST(FutureImplVoid, Abandon) {
140140
},
141141
std::future_error);
142142
}
143-
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
144143

145144
} // namespace
146145
} // namespace internal
147146
} // namespace GOOGLE_CLOUD_CPP_NS
148147
} // namespace cloud
149148
} // namespace google
149+
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS

google/cloud/internal/future_void.h

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_FUTURE_VOID_H_
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_FUTURE_VOID_H_
17+
/**
18+
* @file
19+
*
20+
* Fully specialize `future<void>` and `promise<R>` for void.
21+
*/
22+
23+
#include "google/cloud/internal/port_platform.h"
24+
25+
// C++ futures only make sense when exceptions are enabled.
26+
#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
27+
#include "google/cloud/internal/future_fwd.h"
28+
#include "google/cloud/internal/future_impl.h"
29+
30+
namespace google {
31+
namespace cloud {
32+
inline namespace GOOGLE_CLOUD_CPP_NS {
33+
/**
34+
* Implement ISO/IEC TS 19571:2016 future for void.
35+
*/
36+
template <>
37+
class future<void> {
38+
public:
39+
future() noexcept : shared_state_() {}
40+
future(future<void>&& f) noexcept
41+
: shared_state_(std::move(f.shared_state_)) {}
42+
43+
// TODO(#1345) - implement the unwrapping constructor.
44+
// future(future<future<void>>&& f) noexcept;
45+
// future& operator=(future<future<void>>&& f) noexcept;
46+
future(future<void> const&) = delete;
47+
48+
future& operator=(future<void>&& f) noexcept {
49+
future tmp(std::move(f));
50+
std::swap(tmp.shared_state_, shared_state_);
51+
return *this;
52+
}
53+
future& operator=(future const&) = delete;
54+
55+
void get() {
56+
check_valid();
57+
std::shared_ptr<shared_state_type> tmp;
58+
tmp.swap(shared_state_);
59+
return tmp->get();
60+
}
61+
62+
bool valid() const noexcept { return static_cast<bool>(shared_state_); }
63+
void wait() const {
64+
check_valid();
65+
shared_state_->wait();
66+
}
67+
68+
template <typename Rep, typename Period>
69+
std::future_status wait_for(
70+
std::chrono::duration<Rep, Period> const& rel_time) const {
71+
check_valid();
72+
return shared_state_->wait_for(rel_time);
73+
}
74+
75+
template <typename Rep, typename Period>
76+
std::future_status wait_until(
77+
std::chrono::time_point<Rep, Period> const& abs_time) const {
78+
check_valid();
79+
return shared_state_->wait_until(abs_time);
80+
}
81+
82+
private:
83+
/// Shorthand to refer to the shared state type.
84+
using shared_state_type = internal::future_shared_state<void>;
85+
86+
friend class promise<void>;
87+
explicit future(std::shared_ptr<shared_state_type> state)
88+
: shared_state_(std::move(state)) {}
89+
90+
void check_valid() const {
91+
if (not shared_state_) {
92+
throw std::future_error(std::future_errc::no_state);
93+
}
94+
}
95+
96+
std::shared_ptr<shared_state_type> shared_state_;
97+
};
98+
99+
/**
100+
* Specialize promise as defined in ISO/IEC TS 19571:2016 for void.
101+
*/
102+
template <>
103+
class promise<void> {
104+
public:
105+
/// Create a promise
106+
promise() : shared_state_(new shared_state) {}
107+
108+
promise(promise&& rhs) noexcept
109+
: shared_state_(std::move(rhs.shared_state_)) {
110+
rhs.shared_state_.reset();
111+
}
112+
113+
promise(promise const&) = delete;
114+
115+
~promise() {
116+
if (shared_state_) {
117+
shared_state_->abandon();
118+
}
119+
}
120+
121+
promise& operator=(promise&& rhs) noexcept {
122+
promise tmp(std::move(rhs));
123+
this->swap(tmp);
124+
return *this;
125+
}
126+
127+
promise& operator=(promise const&) = delete;
128+
129+
void swap(promise& other) noexcept {
130+
std::swap(shared_state_, other.shared_state_);
131+
}
132+
133+
future<void> get_future() {
134+
shared_state::mark_retrieved(shared_state_);
135+
return future<void>(shared_state_);
136+
}
137+
138+
void set_value() {
139+
if (not shared_state_) {
140+
throw std::future_error(std::future_errc::no_state);
141+
}
142+
shared_state_->set_value();
143+
}
144+
145+
void set_exception(std::exception_ptr p) {
146+
if (not shared_state_) {
147+
throw std::future_error(std::future_errc::no_state);
148+
}
149+
shared_state_->set_exception(std::move(p));
150+
}
151+
152+
private:
153+
/// Shorthand to refer to the shared state type.
154+
using shared_state = internal::future_shared_state<void>;
155+
std::shared_ptr<shared_state> shared_state_;
156+
};
157+
158+
} // namespace GOOGLE_CLOUD_CPP_NS
159+
} // namespace cloud
160+
} // namespace google
161+
162+
#endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
163+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_FUTURE_VOID_H_

0 commit comments

Comments
 (0)