Skip to content

Commit 8ea3e10

Browse files
Googlera-maurice
authored andcommitted
Fix a case where a ReferenceCountedFutureImpl could be deallocated while running a user callback.
This could happen with an orphaned `RCFI` managed by a `FutureManager`. `FutureManager` relies on `RCFI::IsSafeToDelete()` to know whether it can destroy an orphaned instance; however, `IsSafeToDelete()` only considers whether there are any pending futures in flight. Thus, if no future is pending, it would return true if invoked while the `RCFI` is running the user callback associated with a recently-completed future. Destroying the `RCFI` instance would clean up the future that is passed to the user callback, making it invalid and likely leading the user callback to crash when `Future::result()` suddenly becomes null. To fix this, track whether a user callback is currently running using a dedicated flag protected by the common mutex. PiperOrigin-RevId: 263039541
1 parent 1233fd9 commit 8ea3e10

File tree

2 files changed

+31
-12
lines changed

2 files changed

+31
-12
lines changed

app/src/reference_counted_future_impl.cc

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ namespace {
6161
// This class manages the link between the two.
6262
class FutureProxyManager {
6363
public:
64-
FutureProxyManager(ReferenceCountedFutureImpl* api, FutureHandle subject) :
65-
api_(api), subject_(subject) {}
64+
FutureProxyManager(ReferenceCountedFutureImpl* api, FutureHandle subject)
65+
: api_(api), subject_(subject) {}
6666

6767
void RegisterClient(FutureHandle handle) {
6868
// We create one reference per client to the Future.
@@ -73,8 +73,8 @@ class FutureProxyManager {
7373
}
7474

7575
struct UnregisterData {
76-
UnregisterData(FutureProxyManager* proxy, FutureHandle handle) :
77-
proxy(proxy), handle(handle) {}
76+
UnregisterData(FutureProxyManager* proxy, FutureHandle handle)
77+
: proxy(proxy), handle(handle) {}
7878
FutureProxyManager* proxy;
7979
FutureHandle handle;
8080
};
@@ -203,7 +203,7 @@ FutureApiInterface::~FutureApiInterface() {}
203203
} // namespace detail
204204

205205
const char ReferenceCountedFutureImpl::kErrorMessageFutureIsNoLongerValid[] =
206-
"Invalid Future";
206+
"Invalid Future";
207207

208208
ReferenceCountedFutureImpl::~ReferenceCountedFutureImpl() {
209209
// All futures should be released before we destroy ourselves.
@@ -270,8 +270,7 @@ void ReferenceCountedFutureImpl::CompleteProxy(FutureBackingData* backing) {
270270
// This function is in the cpp instead of the header because
271271
// FutureBackingData is only declared in the cpp.
272272
if (backing->proxy) {
273-
backing->proxy->CompleteClients(backing->error,
274-
backing->error_msg.c_str());
273+
backing->proxy->CompleteClients(backing->error, backing->error_msg.c_str());
275274
}
276275
}
277276

@@ -303,17 +302,26 @@ void ReferenceCountedFutureImpl::ReleaseMutexAndRunCallback(
303302
backing->completion_callback = nullptr;
304303
backing->callback_user_data_delete_fn = nullptr;
305304
backing->callback_user_data = nullptr;
305+
306+
// Make sure we're not deallocated while running the callback, because it
307+
// would make `future_base` invalid.
308+
is_running_callback_ = true;
309+
306310
// Release the lock, which is assumed to be obtained by the caller, before
307311
// calling the callback.
308312
mutex_.Release();
309313

310314
callback(future_base, user_data);
311315

312-
if (delete_fn) {
313-
// Lock again, as we can't assume that the callback is thread-safe from
314-
// the user's perspective.
316+
{
315317
MutexLock lock(mutex_);
316-
delete_fn(user_data);
318+
is_running_callback_ = false;
319+
320+
// Call this while holding lock, as we can't assume that the callback is
321+
// thread-safe from the user's perspective.
322+
if (delete_fn) {
323+
delete_fn(user_data);
324+
}
317325
}
318326
} else {
319327
mutex_.Release();
@@ -494,6 +502,11 @@ bool ReferenceCountedFutureImpl::IsSafeToDelete() const {
494502
// If any Future is still pending, not safe to delete.
495503
if (i->second->status == kFutureStatusPending) return false;
496504
}
505+
506+
if (is_running_callback_) {
507+
return false;
508+
}
509+
497510
return true;
498511
}
499512

@@ -544,7 +557,7 @@ FutureBase ReferenceCountedFutureImpl::LastResultProxy(int fn_idx) {
544557

545558
return FutureBase(this, client_handle);
546559
}
547-
#endif // defined(INTERNAL_EXPERIMENTAL)
560+
#endif // defined(INTERNAL_EXPERIMENTAL)
548561

549562
// NOLINTNEXTLINE - allow namespace overridden
550563
} // namespace FIREBASE_NAMESPACE

app/src/reference_counted_future_impl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,12 @@ class ReferenceCountedFutureImpl : public detail::FutureApiInterface {
519519

520520
/// Clean up any stale Future instances.
521521
TypedCleanupNotifier<FutureBase> cleanup_;
522+
523+
/// True while running the user-supplied callback upon a future's completion.
524+
/// This flag prevents this instance from being considered safe to delete
525+
/// before the callback is finished, which would be unsafe because it would
526+
/// clean up the future that is passed to the callback.
527+
bool is_running_callback_ = false;
522528
};
523529

524530
/// Specialize the case where the data is void since we don't need to

0 commit comments

Comments
 (0)