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

Rare crashes in buffer list's RecyclerView #512

Open
oakkitten opened this issue Apr 16, 2021 · 3 comments
Open

Rare crashes in buffer list's RecyclerView #512

oakkitten opened this issue Apr 16, 2021 · 3 comments
Labels

Comments

@oakkitten
Copy link
Collaborator

there are a few very rare bugs related to buffer list's RecyclerView and its item animator. these bugs has been present in the app for a long time. it is possible to reproduce them, but it takes a bit of effort, even then it's not reliable and can take tens of minutes to reproduce. i could not reproduce these bugs in isolated environment.

at this time my best guess is some obscure race condition in the library code. for the first bug to manifest, apparently you have to trigger a change animation, which is then ended by subsequent animation:

private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) {
    ...
    } else if (changeInfo.oldHolder == item) {
        changeInfo.oldHolder = null;
    ...

DefaultItemAnimator.runPendingAnimations() then schedules animation using the deleted view holder, which leads to a crash:

if (changesPending) {
    if (removalsPending) {
        RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
        ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());

the holder in question comes from mPendingChanges. i gather it shouldn't be there as it must've been removed by something around the call to endChangeAnimationIfNecessary. so the RecyclerView is in some bad state. i think this underlying issue causes the other crashes as well.

in order of the frequency of appearance, these are:

java.lang.NullPointerException: Attempt to read from field 'android.view.View androidx.recyclerview.widget.RecyclerView$ViewHolder.itemView' on a null object reference
    at androidx.recyclerview.widget.DefaultItemAnimator.runPendingAnimations(DefaultItemAnimator.java:157)
    at androidx.recyclerview.widget.RecyclerView$2.run(RecyclerView.java:589)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:967)
    at android.view.Choreographer.doCallbacks(Choreographer.java:791)
    at android.view.Choreographer.doFrame(Choreographer.java:722)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)
java.lang.IllegalArgumentException: view is not a child, cannot hide android.widget.RelativeLayout{b13601 VFE...C.. ......ID 0,981-539,1066 #7f08004e app:id/bufferlist_item_container}
    at androidx.recyclerview.widget.ChildHelper.unhide(ChildHelper.java:352)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getScrapOrHiddenOrCachedHolderForPosition(RecyclerView.java:6393)
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5896)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
    at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
    at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
    at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
    at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
    at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3336)
    at android.view.View.measure(View.java:25086)
    at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:735)
    at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:481)
    at android.view.View.measure(View.java:25086)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6872)
    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
    at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1204)
    at android.widget.LinearLayout.onMeasure(LinearLayout.java:723)
    at android.view.View.measure(View.java:25086)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6872)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:146)
    at android.view.View.measure(View.java:25086)
java.lang.RuntimeException: trying to unhide a view that was not hiddenandroid.widget.TextView{74540c2 VFED..C.. ........ 0,807-196,859}
    at androidx.recyclerview.widget.ChildHelper.unhide(ChildHelper.java:355)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getScrapOrHiddenOrCachedHolderForPosition(RecyclerView.java:6393)
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5896)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
    at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
    at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
    at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
    at androidx.recyclerview.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1331)
    at androidx.recyclerview.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:1075)
    at androidx.recyclerview.widget.RecyclerView.scrollStep(RecyclerView.java:1832)
    at androidx.recyclerview.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:5067)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:967)
    at android.view.Choreographer.doCallbacks(Choreographer.java:791)
    at android.view.Choreographer.doFrame(Choreographer.java:722)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)

(ignore the fact that it's a TextView above, not bufferlist_item_container )

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 14(offset:-1).state:15 androidx.recyclerview.widget.RecyclerView{9dd3ee8 VFED.V... ......ID 0,0-539,957 #7f0800e7 app:id/recycler}, adapter:com.ubergeek42.WeechatAndroid.adapters.BufferListAdapter@c99d850, layout:com.ubergeek42.WeechatAndroid.views.FullScreenDrawerLinearLayoutManager@b49aa49, context:com.ubergeek42.WeechatAndroid.WeechatActivity@89ce4b9
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5923)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
    at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
    at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
    at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
    at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:3875)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3639)
    at androidx.recyclerview.widget.RecyclerView.consumePendingUpdateOperations(RecyclerView.java:1888)
    at androidx.recyclerview.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:5044)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:967)
    at android.view.Choreographer.doCallbacks(Choreographer.java:791)
    at android.view.Choreographer.doFrame(Choreographer.java:722)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)

These can be triggered on current search & master branches by simulating rapid buffer list changes by using this and perhaps by having the following in WeechatActivity.onHotlistSelected():

@MainThread @Cat("Menu") private fun onHotlistSelected() {
    val buffer = BufferList.buffers.shuffled().firstOrNull { it.hotCount > 0 }

    if (buffer != null) {
        openBuffer(buffer.pointer)
    } else {
        Toaster.ShortToast.show(R.string.error__etc__no_hot_buffers)
    }

    val closeDelay = if (Random.nextInt(0, 2) != 0) Random.nextLong(0, 3000) else 0
    if (closeDelay != 0L) {
        Weechat.runOnMainThread({
            val pointer = pagerAdapter.currentBufferPointer
            if (pointer != 0L) closeBuffer(pointer)
        }, closeDelay)
    }
    val switchDelay = closeDelay + Random.nextLong(0, 3000)
    Weechat.runOnMainThread({ onHotlistSelected() }, switchDelay)
}
@oakkitten oakkitten added the bug label Apr 16, 2021
@oakkitten
Copy link
Collaborator Author

oakkitten commented Apr 16, 2021

some thoughts & ideas:

  • buffer list diff is run without move detection. still crashes with it
  • not using the custom layout manager required for full screen drawer doesn't help
  • having a bare text view for rows doesn't help
  • not doing or forcing predictive animations doesn't help
  • discarding old adapter updates and only running the latest ones on the main thread doesn't help (we should do this anyway)

what seems to help is; moving RecyclerView from outside the enclosing RelativeLayout..¯\_(ツ)_/¯

also, another related issue might be some stuck rows. they are usually semi transparent so must've been abandoned in the process of animation. the animation itself got canceled, but the view wasn't removed or recycled or anything and it just stays there. probably another manifestation of the underlying issue

also, the last crash was mentioned in #459

oakkitten added a commit that referenced this issue Apr 22, 2021
fixes #512

hopefully this will get rid of the following bugs, although i have
no idea why RelativeLayout was a problem:

java.lang.NullPointerException: Attempt to read from field 'android.view.View androidx.recyclerview.widget.RecyclerView$ViewHolder.itemView' on a null object reference
    at androidx.recyclerview.widget.DefaultItemAnimator.runPendingAnimations(DefaultItemAnimator.java:157)
    at androidx.recyclerview.widget.RecyclerView$2.run(RecyclerView.java:589)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:967)
    at android.view.Choreographer.doCallbacks(Choreographer.java:791)
    at android.view.Choreographer.doFrame(Choreographer.java:722)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)

java.lang.IllegalArgumentException: view is not a child, cannot hide android.widget.RelativeLayout{b13601 VFE...C.. ......ID 0,981-539,1066 #7f08004e app:id/bufferlist_item_container}
    at androidx.recyclerview.widget.ChildHelper.unhide(ChildHelper.java:352)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getScrapOrHiddenOrCachedHolderForPosition(RecyclerView.java:6393)
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5896)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
    at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
    at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
    at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
    at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
    at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3336)
    at android.view.View.measure(View.java:25086)
    at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:735)
    at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:481)
    at android.view.View.measure(View.java:25086)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6872)
    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
    at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1204)
    at android.widget.LinearLayout.onMeasure(LinearLayout.java:723)
    at android.view.View.measure(View.java:25086)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6872)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:146)
    at android.view.View.measure(View.java:25086)

java.lang.RuntimeException: trying to unhide a view that was not hiddenandroid.widget.TextView{74540c2 VFED..C.. ........ 0,807-196,859}
    at androidx.recyclerview.widget.ChildHelper.unhide(ChildHelper.java:355)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getScrapOrHiddenOrCachedHolderForPosition(RecyclerView.java:6393)
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5896)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
    at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
    at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
    at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
    at androidx.recyclerview.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1331)
    at androidx.recyclerview.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:1075)
    at androidx.recyclerview.widget.RecyclerView.scrollStep(RecyclerView.java:1832)
    at androidx.recyclerview.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:5067)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:967)
    at android.view.Choreographer.doCallbacks(Choreographer.java:791)
    at android.view.Choreographer.doFrame(Choreographer.java:722)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 14(offset:-1).state:15 androidx.recyclerview.widget.RecyclerView{9dd3ee8 VFED.V... ......ID 0,0-539,957 #7f0800e7 app:id/recycler}, adapter:com.ubergeek42.WeechatAndroid.adapters.BufferListAdapter@c99d850, layout:com.ubergeek42.WeechatAndroid.views.FullScreenDrawerLinearLayoutManager@b49aa49, context:com.ubergeek42.WeechatAndroid.WeechatActivity@89ce4b9
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5923)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
    at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
    at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
    at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
    at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:3875)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3639)
    at androidx.recyclerview.widget.RecyclerView.consumePendingUpdateOperations(RecyclerView.java:1888)
    at androidx.recyclerview.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:5044)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:967)
    at android.view.Choreographer.doCallbacks(Choreographer.java:791)
    at android.view.Choreographer.doFrame(Choreographer.java:722)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)
@oakkitten
Copy link
Collaborator Author

the issue might happen a tad more rarely now but still exists

@oakkitten
Copy link
Collaborator Author

apparently this issue affects buffer fragment's RecyclerView, as reported in #525:

Process: com.ubergeek42.WeechatAndroid, PID: 12400
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 5(offset:-1).state:17 com.ubergeek42.WeechatAndroid.views.AnimatedRecyclerView{e41407a V.ED.V... ......ID 0,0-1080,1722 #7f090054 app:id/chat_lines}, adapter:com.ubergeek42.WeechatAndroid.adapters.ChatLinesAdapter@bc9d02b, layout:androidx.recyclerview.widget.LinearLayoutManager@e9cec88, context:com.ubergeek42.WeechatAndroid.WeechatActivity@4f9e700
   at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:133)
   at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:9)
   at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1)
   at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:12)
   at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:102)
   at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:59)
   at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:7)
   at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:3)
   at android.view.View.layout(View.java:22466)
   ...

@oakkitten oakkitten reopened this Jul 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant