Skip to content

Commit 375ade9

Browse files
committed
fix(android): catch RejectedExecutionException on executor-backed Tasks
Wrap Tasks.call paths that run after Firebase callbacks or during bridge teardown: Realtime Database once listeners and streaming handleDatabaseEvent, Firestore query get, and Firestore transaction get document. Matches the Firestore snapshot listener handling when TaskExecutorService is shut down. (cherry picked from commit d70520d)
1 parent 25ed605 commit 375ade9

3 files changed

Lines changed: 135 additions & 102 deletions

File tree

packages/database/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseQueryModule.java

Lines changed: 104 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,19 @@ private void addOnceValueEventListener(
101101
new ValueEventListener() {
102102
@Override
103103
public void onDataChange(@Nonnull DataSnapshot dataSnapshot) {
104-
Tasks.call(getExecutor(), () -> snapshotToMap(dataSnapshot))
105-
.addOnCompleteListener(
106-
task -> {
107-
if (task.isSuccessful()) {
108-
promise.resolve(task.getResult());
109-
} else {
110-
rejectPromiseWithExceptionMap(promise, task.getException());
111-
}
112-
});
104+
try {
105+
Tasks.call(getExecutor(), () -> snapshotToMap(dataSnapshot))
106+
.addOnCompleteListener(
107+
task -> {
108+
if (task.isSuccessful()) {
109+
promise.resolve(task.getResult());
110+
} else {
111+
rejectPromiseWithExceptionMap(promise, task.getException());
112+
}
113+
});
114+
} catch (java.util.concurrent.RejectedExecutionException e) {
115+
rejectPromiseWithExceptionMap(promise, e);
116+
}
113117
}
114118

115119
@Override
@@ -139,69 +143,85 @@ private void addChildOnceEventListener(
139143
public void onChildAdded(@Nonnull DataSnapshot dataSnapshot, String previousChildName) {
140144
if ("child_added".equals(eventType)) {
141145
databaseQuery.removeEventListener(this);
142-
Tasks.call(
143-
getExecutor(),
144-
() -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName))
145-
.addOnCompleteListener(
146-
task -> {
147-
if (task.isSuccessful()) {
148-
promise.resolve(task.getResult());
149-
} else {
150-
rejectPromiseWithExceptionMap(promise, task.getException());
151-
}
152-
});
146+
try {
147+
Tasks.call(
148+
getExecutor(),
149+
() -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName))
150+
.addOnCompleteListener(
151+
task -> {
152+
if (task.isSuccessful()) {
153+
promise.resolve(task.getResult());
154+
} else {
155+
rejectPromiseWithExceptionMap(promise, task.getException());
156+
}
157+
});
158+
} catch (java.util.concurrent.RejectedExecutionException e) {
159+
rejectPromiseWithExceptionMap(promise, e);
160+
}
153161
}
154162
}
155163

156164
@Override
157165
public void onChildChanged(@Nonnull DataSnapshot dataSnapshot, String previousChildName) {
158166
if ("child_changed".equals(eventType)) {
159167
databaseQuery.removeEventListener(this);
160-
Tasks.call(
161-
getExecutor(),
162-
() -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName))
163-
.addOnCompleteListener(
164-
task -> {
165-
if (task.isSuccessful()) {
166-
promise.resolve(task.getResult());
167-
} else {
168-
rejectPromiseWithExceptionMap(promise, task.getException());
169-
}
170-
});
168+
try {
169+
Tasks.call(
170+
getExecutor(),
171+
() -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName))
172+
.addOnCompleteListener(
173+
task -> {
174+
if (task.isSuccessful()) {
175+
promise.resolve(task.getResult());
176+
} else {
177+
rejectPromiseWithExceptionMap(promise, task.getException());
178+
}
179+
});
180+
} catch (java.util.concurrent.RejectedExecutionException e) {
181+
rejectPromiseWithExceptionMap(promise, e);
182+
}
171183
}
172184
}
173185

174186
@Override
175187
public void onChildRemoved(@Nonnull DataSnapshot dataSnapshot) {
176188
if ("child_removed".equals(eventType)) {
177189
databaseQuery.removeEventListener(this);
178-
Tasks.call(getExecutor(), () -> snapshotWithPreviousChildToMap(dataSnapshot, null))
179-
.addOnCompleteListener(
180-
task -> {
181-
if (task.isSuccessful()) {
182-
promise.resolve(task.getResult());
183-
} else {
184-
rejectPromiseWithExceptionMap(promise, task.getException());
185-
}
186-
});
190+
try {
191+
Tasks.call(getExecutor(), () -> snapshotWithPreviousChildToMap(dataSnapshot, null))
192+
.addOnCompleteListener(
193+
task -> {
194+
if (task.isSuccessful()) {
195+
promise.resolve(task.getResult());
196+
} else {
197+
rejectPromiseWithExceptionMap(promise, task.getException());
198+
}
199+
});
200+
} catch (java.util.concurrent.RejectedExecutionException e) {
201+
rejectPromiseWithExceptionMap(promise, e);
202+
}
187203
}
188204
}
189205

190206
@Override
191207
public void onChildMoved(@Nonnull DataSnapshot dataSnapshot, String previousChildName) {
192208
if ("child_moved".equals(eventType)) {
193209
databaseQuery.removeEventListener(this);
194-
Tasks.call(
195-
getExecutor(),
196-
() -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName))
197-
.addOnCompleteListener(
198-
task -> {
199-
if (task.isSuccessful()) {
200-
promise.resolve(task.getResult());
201-
} else {
202-
rejectPromiseWithExceptionMap(promise, task.getException());
203-
}
204-
});
210+
try {
211+
Tasks.call(
212+
getExecutor(),
213+
() -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName))
214+
.addOnCompleteListener(
215+
task -> {
216+
if (task.isSuccessful()) {
217+
promise.resolve(task.getResult());
218+
} else {
219+
rejectPromiseWithExceptionMap(promise, task.getException());
220+
}
221+
});
222+
} catch (java.util.concurrent.RejectedExecutionException e) {
223+
rejectPromiseWithExceptionMap(promise, e);
224+
}
205225
}
206226
}
207227

@@ -315,34 +335,39 @@ private void handleDatabaseEvent(
315335
DataSnapshot dataSnapshot,
316336
@Nullable String previousChildName) {
317337
final String eventRegistrationKey = registration.getString("eventRegistrationKey");
318-
Tasks.call(
319-
getTransactionalExecutor(eventRegistrationKey),
320-
() -> {
321-
if (eventType.equals("value")) {
322-
return snapshotToMap(dataSnapshot);
323-
} else {
324-
return snapshotWithPreviousChildToMap(dataSnapshot, previousChildName);
325-
}
326-
})
327-
.addOnCompleteListener(
328-
getExecutor(),
329-
task -> {
330-
if (task.isSuccessful()) {
331-
WritableMap data = task.getResult();
332-
WritableMap event = Arguments.createMap();
333-
event.putMap("data", data);
334-
event.putString("key", key);
335-
event.putString("eventType", eventType);
336-
event.putMap("registration", readableMapToWritableMap(registration));
337-
338-
ReactNativeFirebaseEventEmitter emitter =
339-
ReactNativeFirebaseEventEmitter.getSharedInstance();
340-
341-
emitter.sendEvent(
342-
new ReactNativeFirebaseDatabaseEvent(
343-
ReactNativeFirebaseDatabaseEvent.EVENT_SYNC, event));
344-
}
345-
});
338+
try {
339+
Tasks.call(
340+
getTransactionalExecutor(eventRegistrationKey),
341+
() -> {
342+
if (eventType.equals("value")) {
343+
return snapshotToMap(dataSnapshot);
344+
} else {
345+
return snapshotWithPreviousChildToMap(dataSnapshot, previousChildName);
346+
}
347+
})
348+
.addOnCompleteListener(
349+
getExecutor(),
350+
task -> {
351+
if (task.isSuccessful()) {
352+
WritableMap data = task.getResult();
353+
WritableMap event = Arguments.createMap();
354+
event.putMap("data", data);
355+
event.putString("key", key);
356+
event.putString("eventType", eventType);
357+
event.putMap("registration", readableMapToWritableMap(registration));
358+
359+
ReactNativeFirebaseEventEmitter emitter =
360+
ReactNativeFirebaseEventEmitter.getSharedInstance();
361+
362+
emitter.sendEvent(
363+
new ReactNativeFirebaseDatabaseEvent(
364+
ReactNativeFirebaseDatabaseEvent.EVENT_SYNC, event));
365+
}
366+
});
367+
} catch (java.util.concurrent.RejectedExecutionException e) {
368+
// Event arrived after module invalidation shut down an executor.
369+
// Safe to drop when tearing down; no JS listener will consume the event.
370+
}
346371
}
347372

348373
/**

packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -389,16 +389,20 @@ private void handleQueryOnSnapshot(
389389

390390
private void handleQueryGet(
391391
ReactNativeFirebaseFirestoreQuery firestoreQuery, Source source, Promise promise) {
392-
firestoreQuery
393-
.get(getExecutor(), source)
394-
.addOnCompleteListener(
395-
task -> {
396-
if (task.isSuccessful()) {
397-
promise.resolve(task.getResult());
398-
} else {
399-
rejectPromiseFirestoreException(promise, task.getException());
400-
}
401-
});
392+
try {
393+
firestoreQuery
394+
.get(getExecutor(), source)
395+
.addOnCompleteListener(
396+
task -> {
397+
if (task.isSuccessful()) {
398+
promise.resolve(task.getResult());
399+
} else {
400+
rejectPromiseFirestoreException(promise, task.getException());
401+
}
402+
});
403+
} catch (java.util.concurrent.RejectedExecutionException e) {
404+
rejectPromiseFirestoreException(promise, e);
405+
}
402406
}
403407

404408
private void sendOnSnapshotEvent(

packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreTransactionModule.java

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,23 @@ public void transactionGetDocument(
7777
FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId);
7878
DocumentReference documentReference = getDocumentForFirestore(firebaseFirestore, path);
7979

80-
Tasks.call(
81-
getTransactionalExecutor(),
82-
() ->
83-
snapshotToWritableMap(
84-
appName, databaseId, transactionHandler.getDocument(documentReference)))
85-
.addOnCompleteListener(
86-
task -> {
87-
if (task.isSuccessful()) {
88-
promise.resolve(task.getResult());
89-
} else {
90-
rejectPromiseWithExceptionMap(promise, task.getException());
91-
}
92-
});
80+
try {
81+
Tasks.call(
82+
getTransactionalExecutor(),
83+
() ->
84+
snapshotToWritableMap(
85+
appName, databaseId, transactionHandler.getDocument(documentReference)))
86+
.addOnCompleteListener(
87+
task -> {
88+
if (task.isSuccessful()) {
89+
promise.resolve(task.getResult());
90+
} else {
91+
rejectPromiseWithExceptionMap(promise, task.getException());
92+
}
93+
});
94+
} catch (java.util.concurrent.RejectedExecutionException e) {
95+
rejectPromiseWithExceptionMap(promise, e);
96+
}
9397
}
9498

9599
@ReactMethod

0 commit comments

Comments
 (0)