Skip to content

Commit d70520d

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.
1 parent f527153 commit d70520d

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)