Skip to content

Commit 60bd867

Browse files
jonsimantova-maurice
authored andcommitted
A couple of fixes in Future, which modify the way that each Future's reference count is kept. This:
- Fixes a race condition between creating a FutureHandle and creating a Future from that handle. - Fixes Future OnCompletion handlers so that they run even if there is no Future instance left. This CL also includes new and modified tests to exercise these bugs, including a test that allocates a couple million futures and makes sure that memory isn't leaking. This is accomplished by refactoring FutureHandle so that it is included in the reference count in each FutureBackingData. Previously only fully-realized Future instances would count towards the reference count. PiperOrigin-RevId: 307743575
1 parent 43cf623 commit 60bd867

File tree

5 files changed

+421
-223
lines changed

5 files changed

+421
-223
lines changed

app/src/include/firebase/future.h

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include <stddef.h>
2121
#include <stdint.h>
22+
2223
#include <utility>
2324

2425
#include "firebase/internal/common.h"
@@ -56,7 +57,55 @@ enum FutureStatus {
5657

5758
/// Handle that the API uses to identify an asynchronous call.
5859
/// The exact interpretation of the handle is up to the API.
59-
typedef uintptr_t FutureHandle;
60+
typedef uintptr_t FutureHandleId;
61+
62+
/// Class that provides more context to FutureHandleId, which allows the
63+
/// underlying API to track handles, perform reference counting, etc.
64+
class FutureHandle {
65+
public:
66+
/// @cond FIREBASE_APP_INTERNAL
67+
FutureHandle();
68+
explicit FutureHandle(FutureHandleId id) : FutureHandle(id, nullptr) {}
69+
FutureHandle(FutureHandleId id, detail::FutureApiInterface* api);
70+
~FutureHandle();
71+
72+
// Copy constructor and assignment operator.
73+
FutureHandle(const FutureHandle& rhs);
74+
FutureHandle& operator=(const FutureHandle& rhs);
75+
76+
#if defined(FIREBASE_USE_MOVE_OPERATORS)
77+
// Move constructor and assignment operator.
78+
FutureHandle(FutureHandle&& rhs) noexcept;
79+
FutureHandle& operator=(FutureHandle&& rhs) noexcept;
80+
#endif // defined(FIREBASE_USE_MOVE_OPERATORS)
81+
82+
// Comparison operators.
83+
bool operator!=(const FutureHandle& rhs) const { return !(*this == rhs); }
84+
bool operator==(const FutureHandle& rhs) const {
85+
// Only compare IDs, since the API is irrelevant (comparison will only occur
86+
// within the context of a single API anyway).
87+
return id() == rhs.id();
88+
}
89+
90+
FutureHandleId id() const { return id_; }
91+
detail::FutureApiInterface* api() const { return api_; }
92+
93+
// Detach from the FutureApi. This handle will no longer increment the
94+
// Future's reference count. This is mainly used for testing, so that you can
95+
// still look up the Future based on its handle's ID without affecting the
96+
// reference count yourself.
97+
void Detach();
98+
99+
// Called by CleanupNotifier when the API is being deleted. At this point we
100+
// can ignore all of the reference counts since all Future data is about to be
101+
// deleted anyway.
102+
void Cleanup() { api_ = nullptr; }
103+
104+
private:
105+
FutureHandleId id_;
106+
detail::FutureApiInterface* api_;
107+
/// @endcond
108+
};
60109

61110
/// @brief Type-independent return type of asynchronous calls.
62111
///
@@ -100,7 +149,7 @@ class FutureBase {
100149
///
101150
/// @param api API class used to provide the future implementation.
102151
/// @param handle Handle to the future.
103-
FutureBase(detail::FutureApiInterface* api, FutureHandle handle);
152+
FutureBase(detail::FutureApiInterface* api, const FutureHandle& handle);
104153

105154
/// @endcond
106155

@@ -229,8 +278,8 @@ class FutureBase {
229278
/// @param[in] user_data Optional user data. We will pass this back to your
230279
/// callback.
231280
/// @return A handle that can be passed to RemoveOnCompletion.
232-
CompletionCallbackHandle
233-
AddOnCompletion(CompletionCallback callback, void* user_data) const;
281+
CompletionCallbackHandle AddOnCompletion(CompletionCallback callback,
282+
void* user_data) const;
234283

235284
#if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
236285
/// Like OnCompletion, but allows adding multiple callbacks.
@@ -272,7 +321,7 @@ class FutureBase {
272321

273322
#if defined(INTERNAL_EXPERIMENTAL)
274323
/// Returns the API-specific handle. Should only be called by the API.
275-
FutureHandle GetHandle() const { return handle_; }
324+
const FutureHandle& GetHandle() const { return handle_; }
276325
#endif // defined(INTERNAL_EXPERIMENTAL)
277326

278327
protected:
@@ -365,7 +414,7 @@ class Future : public FutureBase {
365414
///
366415
/// @param api API class used to provide the future implementation.
367416
/// @param handle Handle to the future.
368-
Future(detail::FutureApiInterface* api, FutureHandle handle)
417+
Future(detail::FutureApiInterface* api, const FutureHandle& handle)
369418
: FutureBase(api, handle) {}
370419

371420
/// @endcond
@@ -409,8 +458,8 @@ class Future : public FutureBase {
409458
/// @note This is the same callback as FutureBase::OnCompletion(), so you
410459
/// can't expect to set both and have both run; again, only the most recently
411460
/// registered one will run.
412-
inline void
413-
OnCompletion(TypedCompletionCallback callback, void* user_data) const;
461+
inline void OnCompletion(TypedCompletionCallback callback,
462+
void* user_data) const;
414463

415464
#if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
416465
/// Register a single callback that will be called at most once, when the
@@ -427,8 +476,8 @@ class Future : public FutureBase {
427476
/// @note This is the same callback as FutureBase::OnCompletion(), so you
428477
/// can't expect to set both and have both run; again, only the most recently
429478
/// registered one will run.
430-
inline void
431-
OnCompletion(std::function<void(const Future<ResultType>&)> callback) const;
479+
inline void OnCompletion(
480+
std::function<void(const Future<ResultType>&)> callback) const;
432481
#endif // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
433482

434483
#if defined(INTERNAL_EXPERIMENTAL)
@@ -447,8 +496,8 @@ class Future : public FutureBase {
447496
/// @param[in] user_data Optional user data. We will pass this back to your
448497
/// callback.
449498
/// @return A handle that can be passed to RemoveOnCompletion.
450-
inline CompletionCallbackHandle
451-
AddOnCompletion(TypedCompletionCallback callback, void* user_data) const;
499+
inline CompletionCallbackHandle AddOnCompletion(
500+
TypedCompletionCallback callback, void* user_data) const;
452501

453502
#if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
454503
/// Like OnCompletion, but allows adding multiple callbacks.
@@ -467,9 +516,8 @@ class Future : public FutureBase {
467516
///
468517
/// @note This method is not available when using STLPort on Android, as
469518
/// `std::function` is not supported on STLPort.
470-
inline CompletionCallbackHandle
471-
AddOnCompletion(std::function<void(const Future<ResultType>&)> callback)
472-
const;
519+
inline CompletionCallbackHandle AddOnCompletion(
520+
std::function<void(const Future<ResultType>&)> callback) const;
473521
#endif // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
474522
#endif // defined(INTERNAL_EXPERIMENTAL)
475523
};

app/src/include/firebase/internal/future_impl.h

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -52,34 +52,35 @@ class FutureApiInterface {
5252

5353
/// Increment the reference count on handle's asynchronous call.
5454
/// Called when the Future is copied.
55-
virtual void ReferenceFuture(FutureHandle handle) = 0;
55+
virtual void ReferenceFuture(const FutureHandle& handle) = 0;
5656

5757
/// Decrement the reference count on handle's asynchronous call.
5858
/// Called when the Future is destroyed or moved.
5959
/// If the reference count drops to zero, the asynchronous call can be
6060
/// forgotten.
61-
virtual void ReleaseFuture(FutureHandle handle) = 0;
61+
virtual void ReleaseFuture(const FutureHandle& handle) = 0;
6262

6363
/// Return the status of the asynchronous call.
64-
virtual FutureStatus GetFutureStatus(FutureHandle handle) const = 0;
64+
virtual FutureStatus GetFutureStatus(const FutureHandle& handle) const = 0;
6565

6666
/// Return the API-specific error.
6767
/// Valid when GetFutureStatus() is kFutureStatusComplete, and undefined
6868
/// otherwise.
69-
virtual int GetFutureError(FutureHandle handle) const = 0;
69+
virtual int GetFutureError(const FutureHandle& handle) const = 0;
7070

7171
/// Return the API-specific error, in human-readable form, or "" if no message
7272
/// has been provided.
7373
/// Valid when GetFutureStatus() is kFutureStatusComplete, and undefined
7474
/// otherwise.
75-
virtual const char* GetFutureErrorMessage(FutureHandle handle) const = 0;
75+
virtual const char* GetFutureErrorMessage(
76+
const FutureHandle& handle) const = 0;
7677

7778
/// Return a pointer to the completed asynchronous result, or NULL if
7879
/// result is still pending.
7980
/// After an asynchronous call is marked complete, the API should not
8081
/// modify the result (especially on a callback thread), since the threads
8182
/// owning the Future can reference the result memory via this function.
82-
virtual const void* GetFutureResult(FutureHandle handle) const = 0;
83+
virtual const void* GetFutureResult(const FutureHandle& handle) const = 0;
8384

8485
/// Register a callback that will be called when this future's status is set
8586
/// to Complete. If clear_existing_callbacks is true, then the new callback
@@ -92,16 +93,14 @@ class FutureApiInterface {
9293
/// After the callback has been called, if `user_data_delete_fn_ptr` is
9394
/// non-null, then `(*user_data_delete_fn_ptr)(user_data)` will be called.
9495
virtual CompletionCallbackHandle AddCompletionCallback(
95-
FutureHandle handle, FutureBase::CompletionCallback callback,
96-
void* user_data,
97-
void (* user_data_delete_fn)(void *),
96+
const FutureHandle& handle, FutureBase::CompletionCallback callback,
97+
void* user_data, void (*user_data_delete_fn)(void*),
9898
bool clear_existing_callbacks) = 0;
9999

100100
/// Unregister a callback that was previously registered with
101101
/// `AddCompletionCallback`.
102102
virtual void RemoveCompletionCallback(
103-
FutureHandle handle,
104-
CompletionCallbackHandle callback_handle) = 0;
103+
const FutureHandle& handle, CompletionCallbackHandle callback_handle) = 0;
105104

106105
#if defined(FIREBASE_USE_STD_FUNCTION)
107106
/// Register a callback that will be called when this future's status is set
@@ -116,7 +115,8 @@ class FutureApiInterface {
116115
///
117116
/// @return A handle that can be passed to `FutureBase::RemoveCompletion`.
118117
virtual CompletionCallbackHandle AddCompletionCallbackLambda(
119-
FutureHandle handle, std::function<void(const FutureBase&)> callback,
118+
const FutureHandle& handle,
119+
std::function<void(const FutureBase&)> callback,
120120
bool clear_existing_callbacks) = 0;
121121
#endif // defined(FIREBASE_USE_STD_FUNCTION)
122122

@@ -143,37 +143,36 @@ class CompletionCallbackHandle {
143143
public:
144144
// Construct a null CompletionCallbackHandle.
145145
CompletionCallbackHandle()
146-
: callback_(nullptr), user_data_(nullptr),
147-
user_data_delete_fn_(nullptr) {}
146+
: callback_(nullptr),
147+
user_data_(nullptr),
148+
user_data_delete_fn_(nullptr) {}
149+
148150
private:
149151
friend class ::FIREBASE_NAMESPACE::FutureBase;
150152
friend class ::FIREBASE_NAMESPACE::ReferenceCountedFutureImpl;
151-
CompletionCallbackHandle(
152-
FutureBase::CompletionCallback callback,
153-
void* user_data,
154-
void (* user_data_delete_fn)(void *))
155-
: callback_(callback), user_data_(user_data),
156-
user_data_delete_fn_(user_data_delete_fn) {}
153+
CompletionCallbackHandle(FutureBase::CompletionCallback callback,
154+
void* user_data, void (*user_data_delete_fn)(void*))
155+
: callback_(callback),
156+
user_data_(user_data),
157+
user_data_delete_fn_(user_data_delete_fn) {}
157158

158159
FutureBase::CompletionCallback callback_;
159160
void* user_data_;
160-
void (* user_data_delete_fn_)(void *);
161+
void (*user_data_delete_fn_)(void*);
161162
};
162163

163164
} // namespace detail
164165

165166
template <class T>
166-
void
167-
Future<T>::OnCompletion(
168-
TypedCompletionCallback callback, void* user_data) const {
169-
FutureBase::OnCompletion(
170-
reinterpret_cast<CompletionCallback>(callback), user_data);
167+
void Future<T>::OnCompletion(TypedCompletionCallback callback,
168+
void* user_data) const {
169+
FutureBase::OnCompletion(reinterpret_cast<CompletionCallback>(callback),
170+
user_data);
171171
}
172172

173173
#if defined(FIREBASE_USE_STD_FUNCTION)
174174
template <class ResultType>
175-
inline void
176-
Future<ResultType>::OnCompletion(
175+
inline void Future<ResultType>::OnCompletion(
177176
std::function<void(const Future<ResultType>&)> callback) const {
178177
FutureBase::OnCompletion(
179178
*reinterpret_cast<std::function<void(const FutureBase&)>*>(&callback));
@@ -182,17 +181,15 @@ Future<ResultType>::OnCompletion(
182181

183182
#if defined(INTERNAL_EXPERIMENTAL)
184183
template <class T>
185-
FutureBase::CompletionCallbackHandle
186-
Future<T>::AddOnCompletion(
184+
FutureBase::CompletionCallbackHandle Future<T>::AddOnCompletion(
187185
TypedCompletionCallback callback, void* user_data) const {
188186
return FutureBase::AddOnCompletion(
189187
reinterpret_cast<CompletionCallback>(callback), user_data);
190188
}
191189

192190
#if defined(FIREBASE_USE_STD_FUNCTION)
193191
template <class ResultType>
194-
inline FutureBase::CompletionCallbackHandle
195-
Future<ResultType>::AddOnCompletion(
192+
inline FutureBase::CompletionCallbackHandle Future<ResultType>::AddOnCompletion(
196193
std::function<void(const Future<ResultType>&)> callback) const {
197194
return FutureBase::AddOnCompletion(
198195
*reinterpret_cast<std::function<void(const FutureBase&)>*>(&callback));
@@ -204,7 +201,7 @@ Future<ResultType>::AddOnCompletion(
204201
inline FutureBase::FutureBase() : api_(NULL), handle_(0) {} // NOLINT
205202

206203
inline FutureBase::FutureBase(detail::FutureApiInterface* api,
207-
FutureHandle handle)
204+
const FutureHandle& handle)
208205
: api_(api), handle_(handle) {
209206
api_->ReferenceFuture(handle_);
210207
detail::RegisterForCleanup(api_, this);
@@ -280,17 +277,16 @@ inline void FutureBase::OnCompletion(CompletionCallback callback,
280277
void* user_data) const {
281278
if (api_ != NULL) { // NOLINT
282279
api_->AddCompletionCallback(handle_, callback, user_data, nullptr,
283-
/*clear_existing_callbacks=*/ true);
280+
/*clear_existing_callbacks=*/true);
284281
}
285282
}
286283

287284
#if defined(INTERNAL_EXPERIMENTAL)
288-
inline FutureBase::CompletionCallbackHandle
289-
FutureBase::AddOnCompletion(CompletionCallback callback,
290-
void* user_data) const {
285+
inline FutureBase::CompletionCallbackHandle FutureBase::AddOnCompletion(
286+
CompletionCallback callback, void* user_data) const {
291287
if (api_ != NULL) { // NOLINT
292288
return api_->AddCompletionCallback(handle_, callback, user_data, nullptr,
293-
/*clear_existing_callbacks=*/ false);
289+
/*clear_existing_callbacks=*/false);
294290
}
295291
return CompletionCallbackHandle();
296292
}
@@ -308,16 +304,17 @@ inline void FutureBase::OnCompletion(
308304
std::function<void(const FutureBase&)> callback) const {
309305
if (api_ != NULL) { // NOLINT
310306
api_->AddCompletionCallbackLambda(handle_, callback,
311-
/*clear_existing_callbacks=*/ true);
307+
/*clear_existing_callbacks=*/true);
312308
}
313309
}
314310

315311
#if defined(INTERNAL_EXPERIMENTAL)
316312
inline FutureBase::CompletionCallbackHandle FutureBase::AddOnCompletion(
317313
std::function<void(const FutureBase&)> callback) const {
318314
if (api_ != NULL) { // NOLINT
319-
return api_->AddCompletionCallbackLambda(handle_, callback,
320-
/*clear_existing_callbacks=*/ false);
315+
return api_->AddCompletionCallbackLambda(
316+
handle_, callback,
317+
/*clear_existing_callbacks=*/false);
321318
}
322319
return CompletionCallbackHandle();
323320
}

0 commit comments

Comments
 (0)