Skip to content

Commit 6de6d73

Browse files
smilesa-maurice
authored andcommitted
Workaround racy behavior of [FIRAnalytics resetAnalyticsData].
When calling [FIRAnalytics resetAnalyticsData] too quickly after initializing the iOS library the method fails to do anything. In addition, it is not possible to retrieve the new app instance ID after analytics data has been reset without polling on iOS which is unfortunate since the C++ SDK exposes an asynchronous interface to retrieve the ID. This implements a workaround using AnalyticsDataResetter which tracks the requested reset state of analytics data which is then used by a thread spawned by GetAnalyticsInstanceId() to determine when the data is reset by polling the app instance ID. PiperOrigin-RevId: 277738391
1 parent 1f5c481 commit 6de6d73

File tree

1 file changed

+109
-4
lines changed

1 file changed

+109
-4
lines changed

analytics/src/analytics_ios.mm

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,103 @@
2424
#include "app/src/include/firebase/version.h"
2525
#include "app/src/assert.h"
2626
#include "app/src/log.h"
27+
#include "app/src/mutex.h"
28+
#include "app/src/time.h"
29+
#include "app/src/thread.h"
30+
#include "app/src/util.h"
2731
#include "app/src/util_ios.h"
2832

2933
namespace firebase {
3034
namespace analytics {
3135

36+
// Used to workaround b/143656277 and b/110166640
37+
class AnalyticsDataResetter {
38+
private:
39+
enum ResetState {
40+
kResetStateNone = 0,
41+
kResetStateRequested,
42+
kResetStateRetry,
43+
};
44+
public:
45+
// Initialize the class.
46+
AnalyticsDataResetter() : reset_state_(kResetStateNone), reset_timestamp_(0) {}
47+
48+
// Reset analytics data.
49+
void Reset() {
50+
MutexLock lock(mutex_);
51+
reset_timestamp_ = firebase::internal::GetTimestampEpoch();
52+
reset_state_ = kResetStateRequested;
53+
instance_id_ = util::StringFromNSString([FIRAnalytics appInstanceID]);
54+
[FIRAnalytics resetAnalyticsData];
55+
}
56+
57+
// Get the instance ID, returning a non-empty string if it's valid or an empty string if it's
58+
// still being reset.
59+
std::string GetInstanceId() {
60+
MutexLock lock(mutex_);
61+
std::string current_instance_id = util::StringFromNSString([FIRAnalytics appInstanceID]);
62+
uint64_t reset_time_elapsed_milliseconds = GetResetTimeElapsedMilliseconds();
63+
switch (reset_state_) {
64+
case kResetStateNone:
65+
break;
66+
case kResetStateRequested:
67+
if (reset_time_elapsed_milliseconds >= kResetRetryIntervalMilliseconds) {
68+
// Firebase Analytics on iOS can take a while to initialize, in this case we try to reset
69+
// again if the instance ID hasn't changed for a while.
70+
reset_state_ = kResetStateRetry;
71+
reset_timestamp_ = firebase::internal::GetTimestampEpoch();
72+
[FIRAnalytics resetAnalyticsData];
73+
return std::string();
74+
}
75+
FIREBASE_CASE_FALLTHROUGH;
76+
77+
case kResetStateRetry:
78+
if ((current_instance_id.empty() || current_instance_id == instance_id_) &&
79+
reset_time_elapsed_milliseconds < kResetTimeoutMilliseconds) {
80+
return std::string();
81+
}
82+
break;
83+
}
84+
instance_id_ = current_instance_id;
85+
return current_instance_id;
86+
}
87+
88+
private:
89+
// Get the time elapsed in milliseconds since reset was requested.
90+
uint64_t GetResetTimeElapsedMilliseconds() const {
91+
return firebase::internal::GetTimestampEpoch() - reset_timestamp_;
92+
}
93+
94+
private:
95+
Mutex mutex_;
96+
// Reset attempt.
97+
ResetState reset_state_;
98+
// When a reset was last requested.
99+
uint64_t reset_timestamp_;
100+
// Instance ID before it was reset.
101+
std::string instance_id_;
102+
103+
// Time to wait before trying to reset again.
104+
static const uint64_t kResetRetryIntervalMilliseconds;
105+
// Time to wait before giving up on resetting the ID.
106+
static const uint64_t kResetTimeoutMilliseconds;
107+
};
108+
32109
DEFINE_FIREBASE_VERSION_STRING(FirebaseAnalytics);
33110

111+
const uint64_t AnalyticsDataResetter::kResetRetryIntervalMilliseconds = 1000;
112+
const uint64_t AnalyticsDataResetter::kResetTimeoutMilliseconds = 5000;
113+
34114
static const double kMillisecondsPerSecond = 1000.0;
115+
static Mutex g_mutex; // NOLINT
35116
static bool g_initialized = false;
117+
static AnalyticsDataResetter *g_resetter = nullptr;
36118

37119
// Initialize the API.
38120
void Initialize(const ::firebase::App& app) {
121+
MutexLock lock(g_mutex);
39122
g_initialized = true;
123+
g_resetter = new AnalyticsDataResetter();
40124
internal::RegisterTerminateOnDefaultAppDestroy();
41125
internal::FutureData::Create();
42126
}
@@ -50,8 +134,11 @@ void Initialize(const ::firebase::App& app) {
50134

51135
// Terminate the API.
52136
void Terminate() {
137+
MutexLock lock(g_mutex);
53138
internal::FutureData::Destroy();
54139
internal::UnregisterTerminateOnDefaultAppDestroy();
140+
delete g_resetter;
141+
g_resetter = nullptr;
55142
g_initialized = false;
56143
}
57144

@@ -169,22 +256,40 @@ void SetCurrentScreen(const char* screen_name, const char* screen_class) {
169256
}
170257

171258
void ResetAnalyticsData() {
259+
MutexLock lock(g_mutex);
172260
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
173-
[FIRAnalytics resetAnalyticsData];
261+
g_resetter->Reset();
174262
}
175263

176264
Future<std::string> GetAnalyticsInstanceId() {
265+
MutexLock lock(g_mutex);
177266
FIREBASE_ASSERT_RETURN(Future<std::string>(), internal::IsInitialized());
178267
auto* api = internal::FutureData::Get()->api();
179268
const auto future_handle = api->SafeAlloc<std::string>(
180269
internal::kAnalyticsFnGetAnalyticsInstanceId);
181-
api->CompleteWithResult(
182-
future_handle, 0, "",
183-
util::StringFromNSString([FIRAnalytics appInstanceID]));
270+
static int kPollTimeMs = 100;
271+
Thread get_id_thread([](SafeFutureHandle<std::string>* handle) {
272+
for ( ; ; ) {
273+
{
274+
MutexLock lock(g_mutex);
275+
if (!internal::IsInitialized()) break;
276+
std::string instance_id = g_resetter->GetInstanceId();
277+
if (!instance_id.empty()) {
278+
internal::FutureData::Get()->api()->CompleteWithResult(
279+
*handle, 0, "", instance_id);
280+
break;
281+
}
282+
}
283+
firebase::internal::Sleep(kPollTimeMs);
284+
}
285+
delete handle;
286+
}, new SafeFutureHandle<std::string>(future_handle));
287+
get_id_thread.Detach();
184288
return Future<std::string>(api, future_handle.get());
185289
}
186290

187291
Future<std::string> GetAnalyticsInstanceIdLastResult() {
292+
MutexLock lock(g_mutex);
188293
FIREBASE_ASSERT_RETURN(Future<std::string>(), internal::IsInitialized());
189294
return static_cast<const Future<std::string>&>(
190295
internal::FutureData::Get()->api()->LastResult(

0 commit comments

Comments
 (0)