Skip to content

Commit fece6ff

Browse files
authored
Fix a race condition in Promise (#7602)
Promise needs to update `m_shared_state` before fulfilling the promise, as `Future::get()` on another thread could return before the write occurs. If the Promise is destroyed immediately after `get()` returns, the destructor could then read the non-null value of `m_shared_state` before the write happens and attempt to report a broken promise error, which hits an assertion.
1 parent 589dbbe commit fece6ff

File tree

3 files changed

+24
-10
lines changed

3 files changed

+24
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
* The metadata disabled mode has been replaced with an in-memory metadata mode which performs similarly and doesn't work weirdly differently from the normal mode. The new mode is intended for testing purposes, but should be suitable for production usage if there is a scenario where metadata persistence is not needed. ([PR #7300](https://github.com/realm/realm-core/pull/7300).
4949
* The ownership relationship between App and User has changed. User now strongly retains App and App has a weak cache of Users. This means that creating a SyncConfig or opening a Realm will keep the parent App alive, rather than things being in a broken state if the App is deallocated. ([PR #7300](https://github.com/realm/realm-core/pull/7300).
5050
* A new CMake define `REALM_APP_SERVICES` can be used to compile out core's default implmentation of the application services. ([#7268](https://github.com/realm/realm-core/issues/7268))
51+
* Fix a race condition in Promise which could result in an assertion failure if it was destroyed immediately after a `get()` on the Future returned. The problematic scenario only occurred in test code and not in library code ([PR #7602](https://github.com/realm/realm-core/pull/7602)).
5152

5253
----------------------------------------------
5354

src/realm/util/future.hpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -492,24 +492,24 @@ class future_details::Promise {
492492

493493
void set_from_status_with(StatusWith<T> sw) noexcept
494494
{
495-
set_impl([&] {
496-
m_shared_state->set_from(std::move(sw));
495+
set_impl([&](auto& shared_state) {
496+
shared_state.set_from(std::move(sw));
497497
});
498498
}
499499

500500
template <typename... Args>
501501
void emplace_value(Args&&... args) noexcept
502502
{
503-
set_impl([&] {
504-
m_shared_state->emplace_value(std::forward<Args>(args)...);
503+
set_impl([&](auto& shared_state) {
504+
shared_state.emplace_value(std::forward<Args>(args)...);
505505
});
506506
}
507507

508508
void set_error(Status status) noexcept
509509
{
510510
REALM_ASSERT_DEBUG(!status.is_ok());
511-
set_impl([&] {
512-
m_shared_state->set_status(std::move(status));
511+
set_impl([&](auto& shared_state) {
512+
shared_state.set_status(std::move(status));
513513
});
514514
}
515515

@@ -547,8 +547,10 @@ class future_details::Promise {
547547
void set_impl(Func&& do_set) noexcept
548548
{
549549
REALM_ASSERT(m_shared_state);
550-
do_set();
551-
m_shared_state.reset();
550+
// Update m_shared_state before fulfilling the promise so that anything
551+
// waiting on the future will see `m_shared_state = nullptr`.
552+
auto shared_state = std::move(m_shared_state);
553+
do_set(*shared_state);
552554
}
553555

554556
util::bind_ptr<SharedState<T>> m_shared_state = make_intrusive<SharedState<T>>();
@@ -1270,8 +1272,8 @@ inline Future<T> Promise<T>::get_future() noexcept
12701272
template <typename T>
12711273
inline void Promise<T>::set_from(Future<T>&& future) noexcept
12721274
{
1273-
set_impl([&] {
1274-
std::move(future).propagate_result_to(m_shared_state.get());
1275+
set_impl([&](auto& shared_state) {
1276+
std::move(future).propagate_result_to(&shared_state);
12751277
});
12761278
}
12771279

test/test_util_future.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2235,6 +2235,17 @@ TEST(Future_Void_Fail_onCompletionFutureAsync)
22352235
});
22362236
}
22372237

2238+
TEST(Future_PromiseUpdatesSharedStateBeforeCompletingCallbacks)
2239+
{
2240+
auto pf = util::make_promise_future<void>();
2241+
std::move(pf.future).get_async([&](Status) {
2242+
// If pf.promise != nullptr here this will hit an assertion failure
2243+
// when `promise` is destroyed
2244+
auto promise = std::move(pf.promise);
2245+
});
2246+
pf.promise.emplace_value();
2247+
}
2248+
22382249
} // namespace
22392250
} // namespace realm::util
22402251

0 commit comments

Comments
 (0)