Is there an existing issue for this?
Which plugins are affected?
Other
Which platforms are affected?
iOS
Description
Running many FirebaseFirestore.runTransaction calls concurrently on iOS crashes the app with SIGABRT (malloc heap-corruption abort, ___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED).
Root cause (from the symbolicated crash + plugin source):
Every native transaction attempt invokes the started: / ended: listeners from Firestore's transaction worker queue. Those listeners mutate the plugin's shared _transactions NSMutableDictionary with no synchronization:
NSMutableDictionary is not thread-safe. When enough transactions run in parallel (contention retries re-enter started:), two threads mutate the dictionary at the same time; a rehash then trips malloc's heap-consistency check and the process aborts.
In our crash report the crashed thread is inside the started: insert (frames: RunUpdateBlock → FLTTransactionStreamHandler.m:55 → -[__NSDictionaryM setObject:forKeyedSubscript:] → mdict_rehashd → abort) while the main thread is simultaneously servicing another transaction's event sink (FLTTransactionStreamHandler.m:74, the DEADLINE_EXCEEDED path) — i.e. at least two transaction attempts were interacting with the shared state at the same moment.
Same crash signature as #16899 (closed as stale for lack of a repro) and likely related to #9527. The unsynchronized code is still present on current main (links above) — we hit it on cloud_firestore 5.6.12.
Suggested fix: guard _transactions with a serial dispatch queue / os_unfair_lock / @synchronized, or confine all mutations to a single queue.
Reproducing the issue
Fire ~100+ transactions concurrently (don't await them individually). Targeting the same document makes it reproduce faster because contention retries amplify the parallelism:
final db = FirebaseFirestore.instance;
final ref = db.collection('stress').doc('same-doc');
for (var i = 0; i < 150; i++) {
// Intentionally not awaited — all transactions run in parallel.
db.runTransaction((tx) async {
final snap = await tx.get(ref);
tx.set(ref, {'n': ((snap.data()?['n'] as int?) ?? 0) + 1});
});
}
On a physical device this crashes within seconds (we hit it repeatedly on TestFlight builds). Our production trigger: a quiz app recording one transaction per answered question in a fire-and-forget loop at quiz completion (~120 questions → ~240 in-flight transactions including retries).
Firebase Core version
3.15.2
Flutter Version
3.44.1 (stable)
Relevant Log Output
Incident Identifier: D39359FB-877D-4C5F-9807-F25DD0BDD88C
Distributor ID: com.apple.TestFlight
Hardware Model: iPhone16,1
Process: Runner [39185]
Path: /private/var/containers/Bundle/Application/BFB49DAE-C3B4-4A27-A9EF-F50BD3F72BC9/Runner.app/Runner
Identifier: com.atacmd.atacMdMobile
Version: 1.1.3 (14)
AppStoreTools: 17F106
AppVariant: 1:iPhone16,1:26
Beta: YES
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.atacmd.atacMdMobile [9289]
Date/Time: 2026-07-02 14:25:53.4758 +0300
Launch Time: 2026-07-02 14:15:10.5106 +0300
OS Version: iPhone OS 26.5 (23F77)
Release Type: User
Baseband Version: 3.50.08
Report Version: 104
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Termination Reason: SIGNAL 6 Abort trap: 6
Terminating Process: Runner [39185]
Triggered by Thread: 35
Thread 35 name:
Thread 35 Crashed:
0 libsystem_kernel.dylib 0x000000024b0d21d0 __pthread_kill + 8 (:-1)
1 libsystem_pthread.dylib 0x00000001fb14b7dc pthread_kill + 268 (pthread.c:1721)
2 libsystem_c.dylib 0x00000001a7b05c98 abort + 148 (abort.c:122)
3 libsystem_malloc.dylib 0x00000001a76b4758 malloc_vreport + 892 (malloc_printf.c:251)
4 libsystem_malloc.dylib 0x00000001a76b43d0 malloc_report + 64 (malloc_printf.c:290)
5 libsystem_malloc.dylib 0x00000001a76a80bc ___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED + 76 (malloc_common.c:179)
6 CoreFoundation 0x000000019bf7559c mdict_rehashd + 288 (NSDictionaryM_Common.h:98)
7 CoreFoundation 0x000000019bf6f1f4 -[__NSDictionaryM setObject:forKeyedSubscript:] + 784 (NSDictionaryM.m:202)
8 Runner 0x0000000100fd203c __63-[FLTTransactionStreamHandler onListenWithArguments:eventSink:]_block_invoke + 112 (FLTTransactionStreamHandler.m:55)
9 FirebaseFirestoreInternal 0x000000010170f258 invocation function for block in -[FIRFirestore runTransactionWithOptions:block:dispatchQueue:completion:]::TransactionResult::RunUpdateBlock(std::__1::shared_ptr<firebase::firestore::core::Transac... + 180 (FIRFirestore.mm:329)
10 libdispatch.dylib 0x00000001d63739a8 _dispatch_call_block_and_release + 32 (init.c:1597)
11 libdispatch.dylib 0x00000001d638d1e4 _dispatch_client_callout + 16 (client_callout.mm:85)
12 libdispatch.dylib 0x00000001d6378148 _dispatch_continuation_pop + 596 (queue.c:345)
13 libdispatch.dylib 0x00000001d63777c4 _dispatch_async_redirect_invoke + 580 (queue.c:870)
14 libdispatch.dylib 0x00000001d6385900 _dispatch_root_queue_drain + 360 (queue.c:7473)
15 libdispatch.dylib 0x00000001d6386098 _dispatch_worker_thread2 + 184 (queue.c:7551)
16 libsystem_pthread.dylib 0x00000001fb145374 _pthread_wqthread + 232 (pthread.c:2709)
17 libsystem_pthread.dylib 0x00000001fb1448c0 start_wqthread + 8 (:-1)
Thread 36 name:
Thread 36:
0 libsystem_kernel.dylib 0x000000024b0c7c68 semaphore_timedwait_trap + 8 (:-1)
Thread 0 (main, servicing another transaction event at the same time):
Thread 0:
0 libobjc.A.dylib 0x0000000198b09260 objc_allocWithZone + 0 (NSObject.mm:2148)
1 Foundation 0x00000001991ebf1c +[NSMutableData(NSMutableData) dataWithCapacity:] + 24 (NSData.m:2060)
2 Flutter 0x00000001052a72ec -[FlutterStandardMethodCodec encodeSuccessEnvelope:] + 56 (FlutterStandardCodec.mm:90)
3 Flutter 0x00000001052a2c58 invocation function for block in SetStreamHandlerMessageHandlerOnChannel(NSObject<FlutterStreamHandler>*, NSString*, NSObject<FlutterBinaryMessenger>*, NSObject<FlutterMethodCodec>*, NSObject<Flutt... + 200 (FlutterChannels.mm:400)
4 Runner 0x0000000100fd2720 __63-[FLTTransactionStreamHandler onListenWithArguments:eventSink:]_block_invoke.5 + 220 (FLTTransactionStreamHandler.m:74)
5 libdispatch.dylib 0x00000001d63739a8 _dispatch_call_block_and_release + 32 (init.c:1597)
6 libdispatch.dylib 0x00000001d638d1e4 _dispatch_client_callout + 16 (client_callout.mm:85)
7 libdispatch.dylib 0x00000001d63aae10 _dispatch_main_queue_drain.cold.6 + 832 (queue.c:8252)
8 libdispatch.dylib 0x00000001d6382964 _dispatch_main_queue_drain + 176 (queue.c:8233)
9 libdispatch.dylib 0x00000001d63828a4 _dispatch_main_queue_callback_4CF + 44 (queue.c:8412)
10 CoreFoundation 0x000000019bff3030 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16 (CFRunLoop.c:1820)
11 CoreFoundation 0x000000019bf80604 __CFRunLoopRun + 1944 (CFRunLoop.c:3177)
12 CoreFoundation 0x000000019bf7f54c _CFRunLoopRunSpecificWithOptions + 532 (CFRunLoop.c:3462)
Flutter dependencies
Relevant packages
cloud_firestore: 5.6.12
firebase_core: 3.15.2
firebase_auth: (also in app, not involved in the crash)
Additional context and comments
- cloud_firestore: 5.6.12
- Device: iPhone16,1, iOS 26.5 (23F77), TestFlight (release) build
- Crash is not reproducible in debug as easily; release + real device + parallel transactions is the reliable combination.
- Workaround that eliminates the crash for us: serializing the transaction calls (await each one) so at most 1–2 are in flight.
Note: filed via CLI because issue forms don't allow pre-filling all fields; sections mirror the bug-report template.
Is there an existing issue for this?
Which plugins are affected?
Other
Which platforms are affected?
iOS
Description
Running many
FirebaseFirestore.runTransactioncalls concurrently on iOS crashes the app with SIGABRT (malloc heap-corruption abort,___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED).Root cause (from the symbolicated crash + plugin source):
Every native transaction attempt invokes the
started:/ended:listeners from Firestore's transaction worker queue. Those listeners mutate the plugin's shared_transactionsNSMutableDictionary with no synchronization:self->_transactions[transactionId] = transaction;instarted:— FLTFirebaseFirestorePlugin.m#L785self->_transactions[transactionId] = nil;inended:— FLTFirebaseFirestorePlugin.m#L788NSMutableDictionaryis not thread-safe. When enough transactions run in parallel (contention retries re-enterstarted:), two threads mutate the dictionary at the same time; a rehash then trips malloc's heap-consistency check and the process aborts.In our crash report the crashed thread is inside the
started:insert (frames:RunUpdateBlock→FLTTransactionStreamHandler.m:55→-[__NSDictionaryM setObject:forKeyedSubscript:]→mdict_rehashd→abort) while the main thread is simultaneously servicing another transaction's event sink (FLTTransactionStreamHandler.m:74, the DEADLINE_EXCEEDED path) — i.e. at least two transaction attempts were interacting with the shared state at the same moment.Same crash signature as #16899 (closed as stale for lack of a repro) and likely related to #9527. The unsynchronized code is still present on current
main(links above) — we hit it on cloud_firestore 5.6.12.Suggested fix: guard
_transactionswith a serial dispatch queue /os_unfair_lock/@synchronized, or confine all mutations to a single queue.Reproducing the issue
Fire ~100+ transactions concurrently (don't await them individually). Targeting the same document makes it reproduce faster because contention retries amplify the parallelism:
On a physical device this crashes within seconds (we hit it repeatedly on TestFlight builds). Our production trigger: a quiz app recording one transaction per answered question in a fire-and-forget loop at quiz completion (~120 questions → ~240 in-flight transactions including retries).
Firebase Core version
3.15.2
Flutter Version
3.44.1 (stable)
Relevant Log Output
Flutter dependencies
Relevant packages
Additional context and comments
Note: filed via CLI because issue forms don't allow pre-filling all fields; sections mirror the bug-report template.