Skip to content

Commit 1fe6659

Browse files
junwhanahntensorflower-gardener
authored andcommitted
Gracefully handle invalid futures in JoinFutures
This CL makes `JoinFutures` return an invalid future when it receives at least one invalid future. This allows `JoinFutures` to handle invalid futures gracefully rather than crashing inside it. PiperOrigin-RevId: 936819255
1 parent 20ef460 commit 1fe6659

3 files changed

Lines changed: 48 additions & 0 deletions

File tree

third_party/xla/xla/tsl/concurrency/future.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ Future<> JoinFutures(absl::Span<const Future<>> futures) {
6868
std::make_shared<JoinStateless>(futures.size(), std::move(promise));
6969

7070
for (const Future<>& future : futures) {
71+
if (!future.IsValid()) [[unlikely]] {
72+
return {};
73+
}
7174
future.OnReady(
7275
[join](const absl::Status& status) { join->OnReady(status); });
7376
}

third_party/xla/xla/tsl/concurrency/future.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,15 @@ struct IsFuture<Future<T>> : std::true_type {};
8383

8484
// Returns a `Future` that will be successful if all `futures` complete
8585
// successfully, or return a first encountered error.
86+
//
87+
// If any of the futures is invalid, the returned future will be invalid.
8688
Future<> JoinFutures(absl::Span<const Future<>> futures);
8789

8890
// Returns a `Future` that will be successful if all `futures` complete
8991
// successfully, or return a first encountered error. Copies values from
9092
// completed futures into the result vector.
93+
//
94+
// If any of the futures is invalid, the returned future will be invalid.
9195
template <typename T, std::enable_if_t<!std::is_void_v<T>>* = nullptr>
9296
Future<std::vector<T>> JoinFutures(absl::Span<const Future<T>> futures);
9397

@@ -96,6 +100,8 @@ Future<std::vector<T>> JoinFutures(absl::Span<const Future<T>> futures);
96100
// completed futures into the result vector and leaves `futures` in move-from
97101
// state (for copyable `T` it still incurs a copy overhead, see `OnReady`
98102
// documentation for details).
103+
//
104+
// If any of the futures is invalid, the returned future will be invalid.
99105
template <typename T, std::enable_if_t<!std::is_void_v<T>>* = nullptr>
100106
Future<std::vector<T>> JoinFutures(absl::Span<Future<T>> futures);
101107

@@ -124,6 +130,8 @@ Future<std::vector<T>> JoinFutures(absl::Span<Future<T>> futures);
124130
// If custom result type for `JoinFutures` is not defined (is void by default),
125131
// then the result type will be inferred as `std::tuple`. Otherwise the result
126132
// value of type `R` will be constructed from expanded tuple values.
133+
//
134+
// If any of the futures is invalid, the returned future will be invalid.
127135
template <typename R = void, typename... Futures,
128136
std::enable_if_t<std::conjunction_v<
129137
internal::IsFuture<std::decay_t<Futures>>...>>* = nullptr>
@@ -1581,6 +1589,9 @@ Future<std::vector<T>> JoinFutures(absl::Span<const Future<T>> futures) {
15811589
std::move(promise));
15821590

15831591
for (size_t index = 0; index < futures.size(); ++index) {
1592+
if (!futures[index].IsValid()) [[unlikely]] {
1593+
return {};
1594+
}
15841595
futures[index].OnReady([index, join](absl::StatusOr<T> value) {
15851596
join->OnReady(index, std::move(value));
15861597
});
@@ -1602,6 +1613,9 @@ Future<std::vector<T>> JoinFutures(absl::Span<Future<T>> futures) {
16021613
std::move(promise));
16031614

16041615
for (size_t index = 0; index < futures.size(); ++index) {
1616+
if (!futures[index].IsValid()) [[unlikely]] {
1617+
return {};
1618+
}
16051619
std::move(futures[index]).OnReady([index, join](absl::StatusOr<T> value) {
16061620
join->OnReady(index, std::move(value));
16071621
});
@@ -1729,6 +1743,10 @@ auto JoinFutures(Futures&&... futures) {
17291743
auto join = std::make_shared<internal::JoinStatic<PromiseResult, State>>(
17301744
sizeof...(futures), std::move(promise));
17311745

1746+
if (((!futures.IsValid()) || ...)) [[unlikely]] {
1747+
return tsl::Future<PromiseResult>();
1748+
}
1749+
17321750
using Is = std::make_index_sequence<sizeof...(Futures)>;
17331751
join->OnReady(std::move(join), Is{}, std::forward<Futures>(futures)...);
17341752

third_party/xla/xla/tsl/concurrency/future_test.cc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,23 @@ TEST(FutureTest, JoinErrors) {
10051005
EXPECT_EQ(join_two.Await(), absl::InternalError("error #0"));
10061006
}
10071007

1008+
TEST(FutureTest, JoinInvalid) {
1009+
auto [promise0, future0] = MakePromise();
1010+
auto [promise1, future1] = MakePromise();
1011+
1012+
std::vector<Future<>> futures0 = {future0, {}};
1013+
std::vector<Future<>> futures1 = {future0, {}, future1};
1014+
1015+
auto join_one = JoinFutures(futures0);
1016+
EXPECT_FALSE(join_one.IsValid());
1017+
1018+
auto join_two = JoinFutures(futures1);
1019+
EXPECT_FALSE(join_two.IsValid());
1020+
1021+
promise0.Set();
1022+
promise1.Set();
1023+
}
1024+
10081025
TEST(FutureTest, JoinCopyableFutures) {
10091026
auto [promise0, future0] = MakePromise<int32_t>();
10101027
auto [promise1, future1] = MakePromise<int32_t>();
@@ -1156,6 +1173,16 @@ TEST(FutureTest, JoinStaticallyError) {
11561173
EXPECT_EQ(joined.Await().status(), absl::InternalError("error0"));
11571174
}
11581175

1176+
TEST(FutureTest, JoinStaticallyInvalid) {
1177+
auto [promise, future] = MakePromise<int32_t>();
1178+
1179+
Future<std::tuple<int32_t, int32_t>> joined =
1180+
JoinFutures(future, tsl::Future<int32_t>());
1181+
EXPECT_FALSE(joined.IsValid());
1182+
1183+
promise.Set(absl::InternalError("error0"));
1184+
}
1185+
11591186
TEST(FutureTest, JoinStaticallyToCustomType) {
11601187
struct TwoInts {
11611188
TwoInts(int32_t a, int32_t b) : a(a), b(b) {}

0 commit comments

Comments
 (0)