Skip to content

Commit e86aa62

Browse files
authored
Merge pull request #300 from firebase/bugfix/race_crash
Fixed race condition when removing listeners.
2 parents 0cf2f97 + 26fedfc commit e86aa62

File tree

9 files changed

+234
-107
lines changed

9 files changed

+234
-107
lines changed

database/integration_test/src/integration_test.cc

Lines changed: 83 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ TEST(TimestampIsNear, Matcher) {
112112
EXPECT_THAT(firebase::Variant::EmptyString(), Not(TimestampIsNear(0)));
113113
}
114114

115-
116115
class FirebaseDatabaseTest : public FirebaseTest {
117116
public:
118117
FirebaseDatabaseTest();
@@ -151,7 +150,7 @@ class FirebaseDatabaseTest : public FirebaseTest {
151150

152151
static firebase::App* shared_app_;
153152
static firebase::auth::Auth* shared_auth_;
154-
153+
155154
static bool first_time_;
156155

157156
bool initialized_;
@@ -169,15 +168,13 @@ class FirebaseDatabaseTest : public FirebaseTest {
169168
// - TearDown: Shut down Database.
170169
// - Once, after all tests are finished:
171170
// - TearDownTestSuite: Sign out. Shut down Auth and App.
172-
171+
173172
firebase::App* FirebaseDatabaseTest::shared_app_;
174173
firebase::auth::Auth* FirebaseDatabaseTest::shared_auth_;
175174

176175
bool FirebaseDatabaseTest::first_time_ = true;
177176

178-
void FirebaseDatabaseTest::SetUpTestSuite() {
179-
InitializeAppAndAuth();
180-
}
177+
void FirebaseDatabaseTest::SetUpTestSuite() { InitializeAppAndAuth(); }
181178

182179
void FirebaseDatabaseTest::InitializeAppAndAuth() {
183180
LogDebug("Initialize Firebase App.");
@@ -197,14 +194,14 @@ void FirebaseDatabaseTest::InitializeAppAndAuth() {
197194

198195
// Initialize Firebase Auth.
199196
::firebase::ModuleInitializer initializer;
200-
initializer.Initialize(
201-
shared_app_, &shared_auth_, [](::firebase::App* app, void* target) {
202-
LogDebug("Attempting to initialize Firebase Auth.");
203-
::firebase::InitResult result;
204-
*reinterpret_cast<firebase::auth::Auth**>(target) =
205-
::firebase::auth::Auth::GetAuth(app, &result);
206-
return result;
207-
});
197+
initializer.Initialize(shared_app_, &shared_auth_,
198+
[](::firebase::App* app, void* target) {
199+
LogDebug("Attempting to initialize Firebase Auth.");
200+
::firebase::InitResult result;
201+
*reinterpret_cast<firebase::auth::Auth**>(target) =
202+
::firebase::auth::Auth::GetAuth(app, &result);
203+
return result;
204+
});
208205

209206
WaitForCompletion(initializer.InitializeLastResult(), "InitializeAuth");
210207

@@ -219,9 +216,7 @@ void FirebaseDatabaseTest::InitializeAppAndAuth() {
219216
SignIn();
220217
}
221218

222-
void FirebaseDatabaseTest::TearDownTestSuite() {
223-
TerminateAppAndAuth();
224-
}
219+
void FirebaseDatabaseTest::TearDownTestSuite() { TerminateAppAndAuth(); }
225220

226221
void FirebaseDatabaseTest::TerminateAppAndAuth() {
227222
if (shared_auth_) {
@@ -283,7 +278,7 @@ void FirebaseDatabaseTest::InitializeDatabase() {
283278
LogDebug("Attempting to initialize Firebase Database.");
284279
::firebase::InitResult result;
285280
*reinterpret_cast<firebase::database::Database**>(target) =
286-
firebase::database::Database::GetInstance(app, &result);
281+
firebase::database::Database::GetInstance(app, &result);
287282
return result;
288283
});
289284

@@ -345,9 +340,9 @@ void FirebaseDatabaseTest::SignOut() {
345340
}
346341
if (shared_auth_->current_user()->is_anonymous()) {
347342
// If signed in anonymously, delete the anonymous user.
348-
WaitForCompletion(shared_auth_->current_user()->Delete(), "DeleteAnonymousUser");
349-
}
350-
else {
343+
WaitForCompletion(shared_auth_->current_user()->Delete(),
344+
"DeleteAnonymousUser");
345+
} else {
351346
// If not signed in anonymously (e.g. if the tests were modified to sign in
352347
// as an actual user), just sign out normally.
353348
shared_auth_->SignOut();
@@ -771,7 +766,7 @@ TEST_F(FirebaseDatabaseTest, TestQueryFiltering) {
771766
UnorderedElementsAre(Key("fig")));
772767
}
773768
}
774-
#endif // !defined(ANDROID)
769+
#endif // !defined(ANDROID)
775770

776771
// A simple ValueListener that logs the values it sees.
777772
class LoggingValueListener : public firebase::database::ValueListener {
@@ -804,6 +799,72 @@ class LoggingValueListener : public firebase::database::ValueListener {
804799
bool got_error_;
805800
};
806801

802+
TEST_F(FirebaseDatabaseTest, TestAddAndRemoveListenerRace) {
803+
const char* test_name = test_info_->name();
804+
805+
SignIn();
806+
807+
firebase::database::DatabaseReference ref = CreateWorkingPath();
808+
WaitForCompletion(ref.Child(test_name).SetValue(0), "SetValue");
809+
810+
const int kTestIterations = 100;
811+
812+
// Ensure adding, removing and deleting a listener in rapid succession is safe
813+
// from race conditions.
814+
for (int i = 0; i < kTestIterations; i++) {
815+
LoggingValueListener* listener = new LoggingValueListener();
816+
ref.Child(test_name).AddValueListener(listener);
817+
ref.Child(test_name).RemoveValueListener(listener);
818+
delete listener;
819+
}
820+
821+
// Ensure adding, removing and deleting the same listener twice in rapid
822+
// succession is safe from race conditions.
823+
for (int i = 0; i < kTestIterations; i++) {
824+
LoggingValueListener* listener = new LoggingValueListener();
825+
ref.Child(test_name).AddValueListener(listener);
826+
ref.Child(test_name).RemoveValueListener(listener);
827+
ref.Child(test_name).AddValueListener(listener);
828+
ref.Child(test_name).RemoveValueListener(listener);
829+
delete listener;
830+
}
831+
832+
// Ensure adding, removing and deleting the same listener twice in rapid
833+
// succession is safe from race conditions.
834+
for (int i = 0; i < kTestIterations; i++) {
835+
LoggingValueListener* listener = new LoggingValueListener();
836+
ref.Child(test_name).AddValueListener(listener);
837+
ref.Child(test_name).AddValueListener(listener);
838+
ref.Child(test_name).RemoveValueListener(listener);
839+
ref.Child(test_name).RemoveValueListener(listener);
840+
delete listener;
841+
}
842+
843+
// Ensure removing a listener too many times is benign.
844+
for (int i = 0; i < kTestIterations; i++) {
845+
LoggingValueListener* listener = new LoggingValueListener();
846+
ref.Child(test_name).AddValueListener(listener);
847+
ref.Child(test_name).AddValueListener(listener);
848+
ref.Child(test_name).RemoveValueListener(listener);
849+
ref.Child(test_name).RemoveValueListener(listener);
850+
ref.Child(test_name).RemoveValueListener(listener);
851+
delete listener;
852+
}
853+
854+
// Ensure adding, removing and deleting difference listeners in rapid
855+
// succession is safe from race conditions.
856+
for (int i = 0; i < kTestIterations; i++) {
857+
LoggingValueListener* listener1 = new LoggingValueListener();
858+
LoggingValueListener* listener2 = new LoggingValueListener();
859+
ref.Child(test_name).AddValueListener(listener1);
860+
ref.Child(test_name).AddValueListener(listener2);
861+
ref.Child(test_name).RemoveValueListener(listener1);
862+
ref.Child(test_name).RemoveValueListener(listener2);
863+
delete listener1;
864+
delete listener2;
865+
}
866+
}
867+
807868
TEST_F(FirebaseDatabaseTest, TestValueListener) {
808869
const char* test_name = test_info_->name();
809870

database/src/desktop/core/event_registration.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,34 @@ namespace internal {
2020

2121
EventRegistration::~EventRegistration() {}
2222

23+
void EventRegistration::SafelyFireEvent(const Event& event) {
24+
// Ensure that the listener has not already been removed.
25+
//
26+
// The main thread may remove listeners at any time, so we should not call any
27+
// callbacks on a listener that has been removed. This does not protect
28+
// callbacks that are currently running when the listener is removed: those
29+
// must handled by a mutex from within the callback.
30+
if (status_ == kRemoved) {
31+
return;
32+
}
33+
34+
FireEvent(event);
35+
}
36+
37+
void EventRegistration::SafelyFireCancelEvent(Error error) {
38+
// Ensure that the listener has not already been removed.
39+
//
40+
// The main thread may remove listeners at any time, so we should not call any
41+
// callbacks on a listener that has been removed. This does not protect
42+
// callbacks that are currently running when the listener is removed: those
43+
// must handled by a mutex from within the callback.
44+
if (status_ == kRemoved) {
45+
return;
46+
}
47+
48+
FireCancelEvent(error);
49+
}
50+
2351
} // namespace internal
2452
} // namespace database
2553
} // namespace firebase

database/src/desktop/core/event_registration.h

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct Event;
3535
class EventRegistration {
3636
public:
3737
explicit EventRegistration(const QuerySpec& query_spec)
38-
: query_spec_(query_spec) {}
38+
: status_(kActive), query_spec_(query_spec) {}
3939

4040
virtual ~EventRegistration();
4141

@@ -49,10 +49,10 @@ class EventRegistration {
4949

5050
// Execute the event. Generally this means consuming the Event data and using
5151
// it to trigger a Listener.
52-
virtual void FireEvent(const Event& event) = 0;
52+
void SafelyFireEvent(const Event& event);
5353

5454
// Cancel the event, passing along the given error code.
55-
virtual void FireCancelEvent(Error error) = 0;
55+
void SafelyFireCancelEvent(Error error);
5656

5757
// Returns true if this EventRegistration contains the given listener.
5858
// Notes: This takes a void* because ValueListener and ChildListener do not
@@ -67,7 +67,29 @@ class EventRegistration {
6767
is_user_initiated_ = is_user_initiated;
6868
}
6969

70+
// Indicates whether this EventRegistration has been removed from the
71+
// database. A removed EventRegistration will not fire any incoming Events.
72+
enum Status {
73+
kRemoved,
74+
kActive,
75+
};
76+
77+
// Returns the current status of this EventRegistration, indicating whether
78+
// this registration has been removed from the database or not.
79+
Status status() { return status_; }
80+
81+
// Set the status of this EventRegistration. When an EventRegistration is
82+
// marked as kRemoved, it will no longer fire events.
83+
void set_status(Status status) { status_ = status; }
84+
85+
protected:
86+
virtual void FireEvent(const Event& event) = 0;
87+
88+
virtual void FireCancelEvent(Error error) = 0;
89+
7090
private:
91+
Status status_;
92+
7193
QuerySpec query_spec_;
7294

7395
bool is_user_initiated_;

database/src/desktop/core/repo.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,9 +527,9 @@ void Repo::DeferredInitialization() {
527527
void Repo::PostEvents(const std::vector<Event>& events) {
528528
for (const Event& event : events) {
529529
if (event.type != kEventTypeError) {
530-
event.event_registration->FireEvent(event);
530+
event.event_registration->SafelyFireEvent(event);
531531
} else {
532-
event.event_registration->FireCancelEvent(event.error);
532+
event.event_registration->SafelyFireCancelEvent(event.error);
533533
}
534534
}
535535
}

database/src/desktop/database_desktop.cc

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,15 @@ SingleValueListener::SingleValueListener(DatabaseInternal* database,
5252
future_(future),
5353
handle_(handle) {}
5454

55-
SingleValueListener::~SingleValueListener() {
56-
database_->RemoveSingleValueListener(this);
57-
}
55+
SingleValueListener::~SingleValueListener() {}
5856

5957
void SingleValueListener::OnValueChanged(const DataSnapshot& snapshot) {
6058
future_->CompleteWithResult<DataSnapshot>(handle_, kErrorNone, "", snapshot);
61-
delete this;
6259
}
6360

6461
void SingleValueListener::OnCancelled(const Error& error_code,
6562
const char* error_message) {
6663
future_->Complete(handle_, error_code, error_message);
67-
delete this;
6864
}
6965

7066
DatabaseInternal::DatabaseInternal(App* app)
@@ -94,16 +90,6 @@ DatabaseInternal::~DatabaseInternal() {
9490
// If initialization failed, there is nothing to clean up.
9591
if (app_ == nullptr) return;
9692

97-
// If there are any pending listeners, delete their pointers.
98-
{
99-
MutexLock lock(listener_mutex_);
100-
for (SingleValueListener** listener_holder : single_value_listeners_) {
101-
delete *listener_holder;
102-
*listener_holder = nullptr;
103-
}
104-
single_value_listeners_.clear();
105-
}
106-
10793
app_->function_registry()->CallFunction(
10894
::firebase::internal::FnAuthStopTokenListener, app_, nullptr, nullptr);
10995
}
@@ -224,6 +210,12 @@ bool DatabaseInternal::RegisterValueListener(
224210

225211
bool DatabaseInternal::UnregisterValueListener(const internal::QuerySpec& spec,
226212
ValueListener* listener) {
213+
EventRegistration* registration =
214+
ActiveEventRegistration(spec, (void*)listener);
215+
if (registration) {
216+
registration->set_status(EventRegistration::kRemoved);
217+
}
218+
227219
MutexLock lock(listener_mutex_);
228220
if (value_listeners_by_query_.Unregister(spec, listener)) {
229221
auto found = cleanup_value_listener_lookup_.find(listener);
@@ -284,6 +276,32 @@ void DatabaseInternal::UnregisterAllChildListeners(
284276
}
285277
}
286278

279+
void DatabaseInternal::AddEventRegistration(
280+
const QuerySpec& query_spec, void* listener_ptr,
281+
EventRegistration* event_registration) {
282+
event_registration_lookup_[query_spec][listener_ptr].push_back(
283+
event_registration);
284+
}
285+
286+
EventRegistration* DatabaseInternal::ActiveEventRegistration(
287+
const QuerySpec& query_spec, void* listener_ptr) {
288+
auto* listener_registration_map =
289+
MapGet(&event_registration_lookup_, query_spec);
290+
if (!listener_registration_map) {
291+
return nullptr;
292+
}
293+
auto* registration_vector = MapGet(listener_registration_map, listener_ptr);
294+
if (!registration_vector) {
295+
return nullptr;
296+
}
297+
for (auto* registration : *registration_vector) {
298+
if (registration->status() == EventRegistration::kActive) {
299+
return registration;
300+
}
301+
}
302+
return nullptr;
303+
}
304+
287305
void DatabaseInternal::EnsureRepo() {
288306
MutexLock lock(repo_mutex_);
289307
if (!repo_) {

0 commit comments

Comments
 (0)