Skip to content

Sporadic EXC_BAD_ACCESS during BinaryDisposable release after upgrading to RxSwift 6.6.0 on iOS 16–26 #2706

Description

@R-Kengai

Overview

After updating RxSwift from 5.1.1 to 6.6.0 and the iOS SDK from 16.1 to 17.0, we started encountering sporadic, unexplained crashes (EXC_BAD_ACCESS) related to asynchronous processing.

This issue has been observed exclusively on devices running iOS 16, 17, 18, and 26.

Based on the crash logs, we suspect there is a high probability that the root cause lies in memory management during the deallocation of RxSwift resources—specifically around BinaryDisposable.

Environment

RxSwift: 6.6.0
Build SDK: iOS 17.0 (21A325)
Xcode: 16.x
Affected OS Versions: iOS 16, 17, 18, 26
Firebase SDK: 10.22.0

Problem Details

Since there are no clear steps to reproduce this crash, it has been extremely difficult to replicate in our local development environment.

According to the stack traces obtained from Firebase Crashlytics, EXC_BAD_ACCESS (KERN_INVALID_ADDRESS) occurs during operations like objc_release and ValueWitnesses.destroy.

Crash Logs (Stack Traces)

Stack Trace 1:

Crashed: rxswift.queue.DispatchQoS(qosClass: Dispatch.DispatchQoS.QoSClass.default, relativePriority: 0)
0  libobjc.A.dylib                0x2370 objc_release + 16
1  libobjc.A.dylib                0x2370 objc_release_x0 + 16
2  RxSwift                        0x12784 BinaryDisposable.__deallocating_deinit + 10 (BinaryDisposable.swift:10)
3  libswiftCore.dylib             0x3d783c _swift_release_dealloc + 56
4  libswiftCore.dylib             0x3d8950 bool swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 160
5  libswiftCore.dylib             0x3a51dc assignWithCopy for ClosedRange<>.Index + 208
6  MyApp                          0x24dac outlined assign with copy of Disposable? + 4378430892 (<compiler-generated>:4378430892)
7  MyApp                          0xada78 closure #1 in closure #1 in MyAppConnect.foo() + 4378991224
8  MyApp                          0xbdde4 partial apply for closure #1 in closure #1 in MyAppConnect.foo() + 4379057636 (<compiler-generated>:4379057636)
9  RxSwift                        0x38f90 closure #1 in Do.run<A>(_:cancel:) + 109 (Do.swift:109)
10 RxSwift                        0x9e18 AnonymousDisposable.dispose() + 44 (AnonymousDisposable.swift:44)
11 RxSwift                        0x9edc protocol witness for Disposable.dispose() in conformance AnonymousDisposable + 20 (<compiler-generated>:20)
12 RxSwift                        0x60e88 SinkDisposer.dispose() + 88 (Producer.swift:88)
13 RxSwift                        0x610a8 protocol witness for Disposable.dispose() in conformance SinkDisposer + 20 (<compiler-generated>:20)
14 RxSwift                        0x77370 Sink.dispose() + 48 (Sink.swift:48)
15 RxSwift                        0x38ce4 protocol witness for ObserverType.on(_:) in conformance DoSink<A> + 20 (<compiler-generated>:20)
16 RxSwift                        0x77294 Sink.forwardOn(_:) + 35 (Sink.swift:35)
17 RxSwift                        0x4fdcc closure #2 in MergeSink.on(_:) + 501 (Merge.swift:501)
18 RxSwift                        0x4ef80 MergeSink.on(_:) + 20 (Lock.swift:20)
19 RxSwift                        0x4fe84 protocol witness for ObserverType.on(_:) in conformance MergeSink<A, B, C> + 20 (<compiler-generated>:20)
20 RxSwift                        0x5750c ObserveOnSink.run(_:_:) + 134 (ObserveOn.swift:134)
21 RxSwift                        0x58660 partial apply for thunk for @escaping @callee_guaranteed (@guaranteed @callee_guaranteed () -> ()) -> () + 40 (<compiler-generated>:40)
22 RxSwift                        0x665c4 partial apply for thunk for @escaping @callee_guaranteed (@in_guaranteed A, @guaranteed @callee_guaranteed @substituted <A> (@in_guaranteed A) -> () for <A>) -> () + 52 (<compiler-generated>:52)
23 RxSwift                        0x65f84 closure #1 in RecursiveImmediateScheduler.schedule(_:) + 175 (RecursiveScheduler.swift:175)
24 RxSwift                        0x36720 closure #1 in DispatchQueueConfiguration.schedule<A>(_:action:) + 27 (DispatchQueueConfiguration.swift:27)
25 RxSwift                        0x2cb10 thunk for @escaping @callee_guaranteed @Sendable () -> () + 28 (<compiler-generated>:28)
26 libdispatch.dylib              0x2370 _dispatch_call_block_and_release + 32
27 libdispatch.dylib              0x40d0 _dispatch_client_callout + 20
28 libdispatch.dylib              0x7580 _dispatch_continuation_pop + 596
29 libdispatch.dylib              0x6bb0 _dispatch_async_redirect_invoke + 580
30 libdispatch.dylib              0x15d8c _dispatch_root_queue_drain + 392
31 libdispatch.dylib              0x16590 _dispatch_worker_thread2 + 156
32 libsystem_pthread.dylib        0x4c40 _pthread_wqthread + 228
33 libsystem_pthread.dylib        0x1488 start_wqthread + 8

Stack Trace 2:

Crashed: rxswift.queue.DispatchQoS(qosClass: Dispatch.DispatchQoS.QoSClass.default, relativePriority: 0)
0  libswiftCore.dylib             0x406e24 swift::metadataimpl::ValueWitnesses<swift::metadataimpl::OpaqueExistentialBox<1u>>::destroy(swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*) + 8
1  RxSwift                        0x1035c outlined destroy of Disposable? + 48 (<compiler-generated>:48)
2  RxSwift                        0x1278c BinaryDisposable.__deallocating_deinit + 10 (BinaryDisposable.swift:10)
3  libswiftCore.dylib             0x3d783c _swift_release_dealloc + 56
4  libswiftCore.dylib             0x3d8950 bool swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 160
5  libswiftCore.dylib             0x3a51dc assignWithCopy for ClosedRange<>.Index + 208
6  MyApp                          0x24dac outlined assign with copy of Disposable? + 4295396780 (<compiler-generated>:4295396780)
7  MyApp                          0xada78 closure #1 in closure #1 in MyAppConnect.foo() + 4295957112
8  MyApp                          0xbce8c partial apply for closure #4 in MyAppConnect.bar() + 4296019596 (<compiler-generated>:4296019596)
9  RxSwift                        0x38f90 closure #1 in Do.run<A>(_:cancel:) + 109 (Do.swift:109)
10 RxSwift                        0x9e18 AnonymousDisposable.dispose() + 44 (AnonymousDisposable.swift:44)
11 RxSwift                        0x9edc protocol witness for Disposable.dispose() in conformance AnonymousDisposable + 20 (<compiler-generated>:20)
12 RxSwift                        0x60e88 SinkDisposer.dispose() + 88 (Producer.swift:88)
13 RxSwift                        0x610a8 protocol witness for Disposable.dispose() in conformance SinkDisposer + 20 (<compiler-generated>:20)
14 RxSwift                        0x77370 Sink.dispose() + 48 (Sink.swift:48)
15 RxSwift                        0x38ce4 protocol witness for ObserverType.on(_:) in conformance DoSink<A> + 20 (<compiler-generated>:20)
16 RxSwift                        0x77294 Sink.forwardOn(_:) + 35 (Sink.swift:35)
17 RxSwift                        0x4fdcc closure #2 in MergeSink.on(_:) + 501 (Merge.swift:501)
18 RxSwift                        0x4ef80 MergeSink.on(_:) + 20 (Lock.swift:20)
19 RxSwift                        0x4fe84 protocol witness for ObserverType.on(_:) in conformance MergeSink<A, B, C> + 20 (<compiler-generated>:20)
20 RxSwift                        0x5750c ObserveOnSink.run(_:_:) + 134 (ObserveOn.swift:134)
21 RxSwift                        0x58660 partial apply for thunk for @escaping @callee_guaranteed (@guaranteed @callee_guaranteed () -> ()) -> () + 40 (<compiler-generated>:40)
22 RxSwift                        0x665c4 partial apply for thunk for @escaping @callee_guaranteed (@in_guaranteed A, @guaranteed @callee_guaranteed @substituted <A> (@in_guaranteed A) -> () for <A>) -> () + 52 (<compiler-generated>:52)
23 RxSwift                        0x65f84 closure #1 in RecursiveImmediateScheduler.schedule(_:) + 175 (RecursiveScheduler.swift:175)
24 RxSwift                        0x36720 closure #1 in DispatchQueueConfiguration.schedule<A>(_:action:) + 27 (DispatchQueueConfiguration.swift:27)
25 RxSwift                        0x2cb10 thunk for @escaping @callee_guaranteed @Sendable () -> () + 28 (<compiler-generated>:28)
26 libdispatch.dylib              0x2370 _dispatch_call_block_and_release + 32
27 libdispatch.dylib              0x40d0 _dispatch_client_callout + 20
28 libdispatch.dylib              0x7580 _dispatch_continuation_pop + 596
29 libdispatch.dylib              0x6bb0 _dispatch_async_redirect_invoke + 580
30 libdispatch.dylib              0x15d8c _dispatch_root_queue_drain + 392
31 libdispatch.dylib              0x16590 _dispatch_worker_thread2 + 156
32 libsystem_pthread.dylib        0x4c40 _pthread_wqthread + 228
33 libsystem_pthread.dylib        0x1488 start_wqthread + 8

Stack Trace 3:

Crashed: rxswift.queue.DispatchQoS(qosClass: Dispatch.DispatchQoS.QoSClass.default, relativePriority: 0)
0  libobjc.A.dylib                0x4f1c objc_destructInstance + 32
1  libswiftCore.dylib             0x3d8230 swift_deallocClassInstance + 120
2  libswiftCore.dylib             0x3d817c _swift_release_dealloc + 56
3  libswiftCore.dylib             0x3d9290 bool swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 160
4  libswiftCore.dylib             0x3a5ad8 assignWithCopy for ClosedRange<>.Index + 208
5  MyApp                          0x20dac outlined assign with copy of Disposable? + 4341616044 (<compiler-generated>:4341616044)
6  MyApp                          0xa8c0c closure #1 in closure #1 in MyAppConnect.foo() + 4342172684
7  MyApp                          0xb80b4 partial apply for closure #1 in MyAppConnect.bar() + 4342235316 (<compiler-generated>:4342235316)
8  RxSwift                        0x38f90 closure #1 in Do.run<A>(_:cancel:) + 109 (Do.swift:109)
9  RxSwift                        0x9e18 AnonymousDisposable.dispose() + 44 (AnonymousDisposable.swift:44)
10 RxSwift                        0x9edc protocol witness for Disposable.dispose() in conformance AnonymousDisposable + 20 (<compiler-generated>:20)
11 RxSwift                        0x60e88 SinkDisposer.dispose() + 88 (Producer.swif
12 RxSwift                        0x610a8 protocol witness for Disposable.dispose() in conformance SinkDisposer + 20 (<compiler-generated>:20)
13 RxSwift                        0x77370 Sink.dispose() + 48 (Sink.swift:48)
14 RxSwift                        0x38ce4 protocol witness for ObserverType.on(_:) in conformance DoSink<A> + 20 (<compiler-generated>:20)
15 RxSwift                        0x77294 Sink.forwardOn(_:) + 35 (Sink.swift:35)
16 RxSwift                        0x4fdcc closure #2 in MergeSink.on(_:) + 501 (Merge.swift:501)
17 RxSwift                        0x4ef80 MergeSink.on(_:) + 20 (Lock.swift:20)
18 RxSwift                        0x4fe84 protocol witness for ObserverType.on(_:) in conformance MergeSink<A, B, C> + 20 (<compiler-generated>:20)
19 RxSwift                        0x5750c ObserveOnSink.run(_:_:) + 134 (ObserveOn.swift:134)
20 RxSwift                        0x58660 partial apply for thunk for @escaping @callee_guaranteed (@guaranteed @callee_guaranteed () -> ()) -> () + 40 (<compiler-generated>:40)
21 RxSwift                        0x665c4 partial apply for thunk for @escaping @callee_guaranteed (@in_guaranteed A, @guaranteed @callee_guaranteed @substituted <A> (@in_guaranteed A) -> () for <A>) -> () + 52 (<compiler-generated>:52)
22 RxSwift                        0x65f84 closure #1 in RecursiveImmediateScheduler.schedule(_:) + 175 (RecursiveScheduler.swift:175)
23 RxSwift                        0x36720 closure #1 in DispatchQueueConfiguration.schedule<A>(_:action:) + 27 (DispatchQueueConfiguration.swift:27)
24 RxSwift                        0x2cb10 thunk for @escaping @callee_guaranteed @Sendable () -> () + 28 (<compiler-generated>:28)
25 libdispatch.dylib              0x2248 _dispatch_call_block_and_release + 32
26 libdispatch.dylib              0x3fa8 _dispatch_client_callout + 20
27 libdispatch.dylib              0x745c _dispatch_continuation_pop + 596
28 libdispatch.dylib              0x6a8c _dispatch_async_redirect_invoke + 580
29 libdispatch.dylib              0x15ec0 _dispatch_root_queue_drain + 392
30 libdispatch.dylib              0x166c4 _dispatch_worker_thread2 + 156
31 libsystem_pthread.dylib        0x3644 _pthread_wqthread + 228
32 libsystem_pthread.dylib        0x1474 start_wqthread + 8

Stack Trace 4:

Crashed: rxswift.queue.DispatchQoS(qosClass: Dispatch.DispatchQoS.QoSClass.default, relativePriority: 0)
0  libswiftCore.dylib             0x3d7824 _swift_release_dealloc + 32
1  libswiftCore.dylib             0x3d8950 bool swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 160
2  libswiftCore.dylib             0x3a51dc assignWithCopy for ClosedRange<>.Index + 208
3  MyApp                          0x20dac outlined assign with copy of Disposable? + 4333276588 (<compiler-generated>:4333276588)
4  MyApp                          0xa8c0c closure #1 in closure #1 in MyAppConnect.foo() + 4333833228
5  MyApp                          0xb80b4 partial apply for closure #1 in MyAppConnect.bar() + 4333895860 (<compiler-generated>:4333895860)
6  RxSwift                        0x38f90 closure #1 in Do.run<A>(_:cancel:) + 109 (Do.swift:109)
7  RxSwift                        0x9e18 AnonymousDisposable.dispose() + 44 (AnonymousDisposable.swift:44)
8  RxSwift                        0x9edc protocol witness for Disposable.dispose() in conformance AnonymousDisposable + 20 (<compiler-generated>:20)
9  RxSwift                        0x60e88 SinkDisposer.dispose() + 88 (Producer.swift:88)
10 RxSwift                        0x610a8 protocol witness for Disposable.dispose() in conformance SinkDisposer + 20 (<compiler-generated>:20)
11 RxSwift                        0x77370 Sink.dispose() + 48 (Sink.swift:48)
12 RxSwift                        0x38ce4 protocol witness for ObserverType.on(_:) in conformance DoSink<A> + 20 (<compiler-generated>:20)
13 RxSwift                        0x77294 Sink.forwardOn(_:) + 35 (Sink.swift:35)
14 RxSwift                        0x4fdcc closure #2 in MergeSink.on(_:) + 501 (Merge.swift:501)
15 RxSwift                        0x4ef80 MergeSink.on(_:) + 20 (Lock.swift:20)
16 RxSwift                        0x4fe84 protocol witness for ObserverType.on(_:) in conformance MergeSink<A, B, C> + 20 (<compiler-generated>:20)
17 RxSwift                        0x5750c ObserveOnSink.run(_:_:) + 134 (ObserveOn.swift:134)
18 RxSwift                        0x58660 partial apply for thunk for @escaping @callee_guaranteed (@guaranteed @callee_guaranteed () -> ()) -> () + 40 (<compiler-generated>:40)
19 RxSwift                        0x665c4 partial apply for thunk for @escaping @callee_guaranteed (@in_guaranteed A, @guaranteed @callee_guaranteed @substituted <A> (@in_guaranteed A) -> () for <A>) -> () + 52 (<compiler-generated>:52)
20 RxSwift                        0x65f84 closure #1 in RecursiveImmediateScheduler.schedule(_:) + 175 (RecursiveScheduler.swift:175)
21 RxSwift                        0x36720 closure #1 in DispatchQueueConfiguration.schedule<A>(_:action:) + 27 (DispatchQueueConfiguration.swift:27)
22 RxSwift                        0x2cb10 thunk for @escaping @callee_guaranteed @Sendable () -> () + 28 (<compiler-generated>:28)
23 libdispatch.dylib              0x2370 _dispatch_call_block_and_release + 32
24 libdispatch.dylib              0x40d0 _dispatch_client_callout + 20
25 libdispatch.dylib              0x7580 _dispatch_continuation_pop + 596
26 libdispatch.dylib              0x6bb0 _dispatch_async_redirect_invoke + 580
27 libdispatch.dylib              0x15d8c _dispatch_root_queue_drain + 392
28 libdispatch.dylib              0x16590 _dispatch_worker_thread2 + 156
29 libsystem_pthread.dylib        0x4c40 _pthread_wqthread + 228
30 libsystem_pthread.dylib        0x1488 start_wqthread + 8

Investigation Details

We attempted to compile the project in Xcode 16 with Strict Concurrency Checking set to Complete.
While we expected a large volume of warnings, no concurrency-related warnings were flagged around the MyAppConnect class, which appears to be the starting point of the crash.

Questions

Are there any known compatibility issues regarding lifecycle management of BinaryDisposable between RxSwift 6.6.0 and the Swift runtime environment in iOS 16–26?

Our current hypothesis is that a data race/race condition is occurring between the resource disposal process on a background thread and the deallocation of the parent object on the Main Actor (Main Thread).
If anyone has encountered a similar issue or could provide guidance on effective debugging techniques for this scenario, your insights would be highly appreciated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions