Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.x: SwitchMapInnerObserver.queue.offer NullPointerException #7596

Closed
xvaldetaro opened this issue Aug 29, 2023 · 2 comments · Fixed by #7597
Closed

3.x: SwitchMapInnerObserver.queue.offer NullPointerException #7596

xvaldetaro opened this issue Aug 29, 2023 · 2 comments · Fixed by #7597

Comments

@xvaldetaro
Copy link

RxJava 3.1.0

I'm getting this NPE:

java.lang.NullPointerException: Attempt to invoke interface method 'boolean SimpleQueue.offer(java.lang.Object)' on a null object reference
	at io.reactivex.rxjava3.internal.operators.observable.ObservableSwitchMap$SwitchMapInnerObserver.onNext(ObservableSwitchMap.java)
	at io.reactivex.rxjava3.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java)
	at io.reactivex.rxjava3.internal.operators.observable.ObservableScanSeed$ScanSeedObserver.onSubscribe(ObservableScanSeed.java)
	at io.reactivex.rxjava3.subjects.PublishSubject.v1(PublishSubject.java:6)
	at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java)
	at io.reactivex.rxjava3.internal.operators.observable.ObservableScanSeed.subscribeActual(ObservableScanSeed.java)
	at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java)
	at io.reactivex.rxjava3.internal.operators.observable.ObservableDefer.subscribeActual(ObservableDefer.java)
	at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java)
	at io.reactivex.rxjava3.internal.operators.observable.ObservableMap.subscribeActual(ObservableMap.java)
	at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java)
	at io.reactivex.rxjava3.internal.operators.observable.ObservableSwitchMap$SwitchMapObserver.onNext(ObservableSwitchMap.java)
	at io.reactivex.rxjava3.observers.SerializedObserver.onNext(SerializedObserver.java)
	at io.reactivex.rxjava3.internal.operators.observable.ObservableThrottleFirstTimed$DebounceTimedObserver.onNext(ObservableThrottleFirstTimed.java)
	at io.reactivex.rxjava3.internal.util.NotificationLite.accept(NotificationLite.java)
	at io.reactivex.rxjava3.subjects.BehaviorSubject$BehaviorDisposable.test(BehaviorSubject.java)
	at io.reactivex.rxjava3.subjects.BehaviorSubject$BehaviorDisposable.emitNext(BehaviorSubject.java)
	at io.reactivex.rxjava3.subjects.BehaviorSubject.onNext(BehaviorSubject.java)
        at onAndroidWidgetUpdateByOs(Main.java)

This is the abstracted code that causes it:

private val refreshModelsSubject = BehaviorSubject.createDefault(Unit)
private val refreshObservable = refreshModelsSubject.throttleFirst(THROTTLE_REFRESH_S, TimeUnit.MILLISECONDS)
private val dimensionsChangedForWidgetIdSubject = PublishSubject.create<Pair<Int, Bundle>>()

fun onAndroidWidgetUpdateByOs() {
  refreshModelsSubject.onNext(Unit)
}

// ... in the initialization code:
fun init() {
  refreshObservable.switchMap { 
    Observable.defer {
      dimensionsChangedForWidgetIdSubject
        .scan(initialDimensionsByWidgetId()) { previousDimensionsByWidgetId, pair ->
          // ... scan code
        }
        .map {
          // ... map code
        }
    }
  }
}

This issue happens very rarely and I can't repro it locally.
I have been reading the Observables involved in the crash for a few hours and I think what happens is that:

  1. refreshModelsSubject emits. This causes a chain of events that will lead to ObservableScanSeed.onSubscribe code being called with the SwitchMapInnerObserver being passed to it (indirectly inside ObservableMap).
  2. While inside ObservableScanSeed.onSubscribe, something will dispose of the SwitchMapInnerObserver and then the call to SwitchMapInnerObserver.onSubscribe that happens from ObservableScanSeed.onSubscribe will not create the queue object in SwitchMapInnerObserver.
  3. ObservableScanSeed.onSubscribe will call SwitchMapInnerObserver.onNext and crash happens

Is my assessment correct? If so, what kind of mistakes I'm making here and how do I mitigate them?

I also saw this issue, which is similar, but seems like it was caused by a non-standard operator (which I'm not using).

@akarnokd
Copy link
Member

akarnokd commented Aug 29, 2023

Indeed it looks like the whole switchMap is disposed right at the moment the inner's onSubscribe would setup the inner and that leaves the inner without a queue.

I'll post fixes in a day. Not sure how you could fix this on your end other than somehow delaying the dispose itself.

@akarnokd
Copy link
Member

Sorry about the delay. The fix will land in v3.1.8 just released (may take a few hours to show up).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants