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

[Android Crash] Touch data should have been recorded on start #5246

Closed
Richard-Cao opened this issue Jan 11, 2016 · 26 comments
Closed

[Android Crash] Touch data should have been recorded on start #5246

Richard-Cao opened this issue Jan 11, 2016 · 26 comments
Assignees
Labels
Platform: iOS iOS applications. Resolution: Locked This issue was locked by the bot.

Comments

@Richard-Cao
Copy link

Paltform: Android
Version: 5.0

If you open Touch gestures feature on your device, when three fingers on the screen at the same time, crash.

There is crash stack:

device-2016-01-11-150743

I test this on react-native showcase Movie Trailers by MovieLaLa Android, crash still exist.

@Richard-Cao
Copy link
Author

@satya164

@facebook-github-bot
Copy link
Contributor

Hey Richard-Cao, thanks for reporting this issue!

React Native, as you've probably heard, is getting really popular and truth is we're getting a bit overwhelmed by the activity surrounding it. There are just too many issues for us to manage properly.

  • If you don't know how to do something or something is not working as you expect but not sure it's a bug, please ask on StackOverflow with the tag react-native or for more real time interactions, ask on Discord in the #react-native channel.
  • If this is a feature request or a bug that you would like to be fixed, please report it on Product Pains. It has a ranking feature that lets us focus on the most important issues the community is experiencing.
  • We welcome clear issues and PRs that are ready for in-depth discussion. Please provide screenshots where appropriate and always mention the version of React Native you're using. Thank you for your contributions!

@satya164
Copy link
Contributor

Sorry. I've no context on this. What do you mean by the touch gestures feature? How to enable it?

@Richard-Cao
Copy link
Author

@satya164 Some Android devices like XiaoMi, Huawei, Nubia and so on, have touch gestures feature.
If you open it, you can restore this crash.

@marcshilling
Copy link

I just got this on iOS as well. It seems to happen when I try to scroll my ListView quickly after it mounts...

@brianreavis
Copy link

Also got this on iOS when randomly tapping around on an app. Can't reproduce reliably ... so, sorry for there not being any more context. I don't think it was in the context of a ListView.

@marcshilling
Copy link

Found where this is happening: react/lib/ResponderTouchHistoryStore.js in the method recordMoveTouchData.

On a cold boot of my app, if the first touch on the screen is a scroll on a ListView/ScrollView, there appears to be a race condition and recordMoveTouchData is called before recordStartTouchData, therefore touchHistory.touchBank is not properly initialized.

Upon the first call,
var touchTrack = touchBank[touch.identifier];
touchTrack is undefined, causing the crash. Inserting:

  if (touchTrack) {
    reinitializeTouchTrack(touchTrack, touch);
  } else {
    touchBank[touch.identifier] = initializeTouchData(touch);
  }

(like it exists in recordStartTouchData) resolves the problem. However, I doubt this is the correct solution. Again, I believe it's a race condition with the recordStartTouchData.

@marcshilling
Copy link

Opened an issue on react core here facebook/react#6277

@marcshilling
Copy link

Ok, they bounced it back over here. Anyone familiar enough with PanResponder to look further into this?

@skevy
Copy link
Contributor

skevy commented Mar 17, 2016

@majak I know you recently refactored how touches work (by passing them through the event dispatcher)...would you have any clue on this issue?

TLDR; we're getting touchesMoved before a touchesStart is ever sent.

@skevy
Copy link
Contributor

skevy commented Mar 17, 2016

(cc @nicklockwood)

@marcshilling
Copy link

Yep, you can see here iOS gets touchesBegan, then a series of touchesMoved, yet the first event to land in JS is a topTouchMove

This is on React Native 0.20

2016-03-17 10:31:17.463 DiscoveryVR - Dev[3700:1038410] touchesBegan event = <UITouchesEvent: 0x147ef85c0> timestamp: 164352 touches: {(
    <UITouch: 0x147fdc900> phase: Began tap count: 1 window: <UIWindow: 0x149037540; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x149060430>; layer = <UIWindowLayer: 0x14903df70>> view: <RCTView: 0x14932baf0; reactTag: 189; frame = (0 0; 341 153); alpha = 0.4; layer = <CALayer: 0x149321ad0>> location in window: {219, 587.5} previous location in window: {219, 587.5} location in view: {203, 29.5} previous location in view: {203, 29.5}
)}
2016-03-17 10:31:17.476 DiscoveryVR - Dev[3700:1038410] touchesMoved event = <UITouchesEvent: 0x147ef85c0> timestamp: 164352 touches: {(
    <UITouch: 0x147fdc900> phase: Moved tap count: 1 window: <UIWindow: 0x149037540; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x149060430>; layer = <UIWindowLayer: 0x14903df70>> view: <RCTView: 0x14932baf0; reactTag: 189; frame = (0 0; 341 153); alpha = 0.4; layer = <CALayer: 0x149321ad0>> location in window: {219, 540.5} previous location in window: {219, 587.5} location in view: {203, -17.5} previous location in view: {203, 29.5}
)}
2016-03-17 10:31:17.492 DiscoveryVR - Dev[3700:1038410] touchesMoved event = <UITouchesEvent: 0x147ef85c0> timestamp: 164352 touches: {(
    <UITouch: 0x147fdc900> phase: Moved tap count: 1 window: <UIWindow: 0x149037540; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x149060430>; layer = <UIWindowLayer: 0x14903df70>> view: (null) location in window: {219, 471} previous location in window: {219, 540.5} location in view: {219, 471} previous location in view: {219, 540.5}
)}
2016-03-17 10:31:17.511 DiscoveryVR - Dev[3700:1038410] touchesMoved event = <UITouchesEvent: 0x147ef85c0> timestamp: 164352 touches: {(
    <UITouch: 0x147fdc900> phase: Moved tap count: 1 window: <UIWindow: 0x149037540; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x149060430>; layer = <UIWindowLayer: 0x14903df70>> view: (null) location in window: {219, 357.5} previous location in window: {219, 471} location in view: {219, 357.5} previous location in view: {219, 471}
)}
2016-03-17 10:31:17.526 DiscoveryVR - Dev[3700:1038410] touchesMoved event = <UITouchesEvent: 0x147ef85c0> timestamp: 164352 touches: {(
    <UITouch: 0x147fdc900> phase: Moved tap count: 1 window: <UIWindow: 0x149037540; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x149060430>; layer = <UIWindowLayer: 0x14903df70>> view: (null) location in window: {227.5, 230} previous location in window: {219, 357.5} location in view: {227.5, 230} previous location in view: {219, 357.5}
)}
2016-03-17 10:31:17.539 [info][tid:com.facebook.React.JavaScript] 'eventTopLevelType = ', 'topTouchMove'
2016-03-17 10:31:17.544 DiscoveryVR - Dev[3700:1038410] touchesMoved event = <UITouchesEvent: 0x147ef85c0> timestamp: 164352 touches: {(
    <UITouch: 0x147fdc900> phase: Moved tap count: 1 window: <UIWindow: 0x149037540; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x149060430>; layer = <UIWindowLayer: 0x14903df70>> view: (null) location in window: {253, 95.5} previous location in window: {227.5, 230} location in view: {253, 95.5} previous location in view: {227.5, 230}
)}
2016-03-17 10:31:17.546 [info][tid:com.facebook.React.JavaScript] 'touches = ', [ { target: 189,
    pageY: 540.5,
    locationX: 203,
    locationY: -17.5,
    identifier: 1,
    pageX: 219,
    timestamp: 164351752.6978334 } ]
2016-03-17 10:31:17.547 [info][tid:com.facebook.React.JavaScript] 'changedIndices = ', [ 0 ]
2016-03-17 10:31:17.548 [info][tid:com.facebook.React.JavaScript] ====================================
2016-03-17 10:31:17.550 [error][tid:com.facebook.React.JavaScript] Touch data should have been recorded on start

@majak
Copy link
Contributor

majak commented Mar 17, 2016

I've changed how touches are handled only on iOS side. If this issue is common for both iOS and Android I would expect something is wrong on js side.
@marcshilling can you repro this with a simple app containing just scrollview?
@Richard-Cao could you link to a page on this device specific touch gestures feature on Android?

@marcshilling
Copy link

@majak I'll try to throw something together. The tricky thing is it's not reproducible 100% of the time (definitely a race condition). But anyway, yeah let me see if I can get something up and running.

@skevy
Copy link
Contributor

skevy commented Mar 17, 2016

@majak Marc and I did a bit more research on this:

2016-03-17 12:22:28.850 DiscoveryVR - Dev[44643:4871099] OUTSIDE BLOCK: Module RCTEventEmitter, Method receiveTouches, Args (
    topTouchStart,
        (
                {
            identifier = 1;
            locationX = 226;
            locationY = "36.5";
            pageX = 242;
            pageY = "594.5";
            target = 189;
            timestamp = "65236062.87420401";
        }
    ),
        (
        0
    )
)
2016-03-17 12:22:28.873 DiscoveryVR - Dev[44643:4871447] OUTSIDE BLOCK: Module RCTEventEmitter, Method receiveTouches, Args (
    topTouchMove,
        (
                {
            identifier = 1;
            locationX = 226;
            locationY = "34.5";
            pageX = 242;
            pageY = "592.5";
            target = 189;
            timestamp = "65236079.616078";
        }
    ),
        (
        0
    )
)
2016-03-17 12:22:28.873 DiscoveryVR - Dev[44643:4871447] INSIDE BLOCK: Module RCTEventEmitter, Method receiveTouches, Args (
    topTouchMove,
        (
                {
            identifier = 1;
            locationX = 226;
            locationY = "34.5";
            pageX = 242;
            pageY = "592.5";
            target = 189;
            timestamp = "65236079.616078";
        }
    ),
        (
        0
    )
)
2016-03-17 12:22:28.874 [error][tid:com.facebook.React.JavaScript] Touch data should have been recorded on start
2016-03-17 12:22:28.879 [fatal][tid:com.facebook.React.RCTExceptionsManagerQueue] Unhandled JS Exception: Touch data should have been recorded on start
2016-03-17 12:22:28.879 DiscoveryVR - Dev[44643:4871447] INSIDE BLOCK: Module RCTEventEmitter, Method receiveTouches, Args (
    topTouchStart,
        (
                {
            identifier = 1;
            locationX = 226;
            locationY = "36.5";
            pageX = 242;
            pageY = "594.5";
            target = 189;
            timestamp = "65236062.87420401";
        }
    ),
        (
        0
    )
)

Here, we're logging calls to the bridge with the method "receiveTouches", referring to the method that receives touches in ReactNativeEventEmitter. In the log above, you notice we're logging "OUTSIDE BLOCK" and "INSIDE BLOCK" -- these logs are referring to:

OUTSIDE BLOCK: https://github.com/facebook/react-native/blob/master/React/Base/RCTBatchedBridge.m#L696
INSIDE BLOCK: https://github.com/facebook/react-native/blob/master/React/Base/RCTBatchedBridge.m#L705

As you can see in the log, the "OUTSIDE_BLOCK" stuff is executing in the proper order -- a topTouchStart followed by a topTouchMove.

INSIDE_BLOCK, however, is executing in the wrong order. You can see the topTouchStart event doesn't ACTUALLY get executed on the bridge until after the the topTouchMove.

This seems weird, but I think I may have a fundamental misunderstanding of how the JSC executor works. I was under the impression that while timing of a given JS call couldn't be guaranteed (due to the async nature of the bridge), the order could be, because as the method name implies, updates are "enqueued".

Obviously this could be fixed in JS by queuing up move calls until a touchStart is received, and we can enforce the order there. Perhaps the right fix, and presumably that would then be fixed on both platforms.

I'm curious though if anyone can explain the executor here though, and why order can't be guaranteed.

CCing @javache here as well.

@skevy skevy added the Platform: iOS iOS applications. label Mar 17, 2016
@majak majak self-assigned this Mar 18, 2016
@majak
Copy link
Contributor

majak commented Mar 18, 2016

Great data! I've chatted with @tadeuzagallo and we figured out there is indeed a race condition in how touches are processed.

For (perf) reasons, topTouchStart is immediately dispatched on js thread, while topTouchMove waits for js to collect it in at the beggining of a frame. This can lead into a situation in which they arrive to js in a wrong order, something like this:

fullsizerender

I'll fix it. Thanks for digging into this, @skevy & @marcshilling!

This also makes me think this github issue actually contains two different bugs that surface with the same redbox.

@skevy
Copy link
Contributor

skevy commented Mar 18, 2016

This is a great explanation, and makes a lot of sense conceptually. I'm not sure I'm clear where exactly in the code this is happening...but I'll wait for the diff!

Thanks so much @majak and @tadeuzagallo.

@Richard-Cao
Copy link
Author

@majak
screenshot_2016-03-21-10-21-47

@keeth
Copy link

keeth commented Mar 24, 2016

Seeing similar crash reports in the wild on iOS as well. Thanks for looking into it!

ghost pushed a commit that referenced this issue Apr 1, 2016
Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way:
* All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread)
* Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too.

This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order.
See #5246 (comment) for further explanation of this case.

The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block).
This creates a single way of processing events and makes it impossible to process them in different order in js.

The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with  non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now.
I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies).

On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run.

To illustrate this improvement let's look at these two systraces.
Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png
After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png

Reviewed By: javache

Differential Revision: D3092867

fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
@majak
Copy link
Contributor

majak commented Apr 1, 2016

Changes that should fix this issue were just pushed on master. Commits from 8f07b01 to 31bb85a. The important one with deeper explanation is 1d3db4c.
Since I didn't repro this issue locally I cannot guarantee it will fix it for real. Therefore it would be great if you could patch it locally and see whether it really helps.

But also turn off chrome debugging please. Looks like this stack breaks it 😅. I'm looking into that too.

@majak
Copy link
Contributor

majak commented Apr 6, 2016

Version 0.24.0-rc1 should already contain the fix and all necessary fixes for the fix.

@ide
Copy link
Contributor

ide commented Apr 6, 2016

To be clear, we believe the issue on iOS has been fixed. Those commits don't affect Android.

zebulgar pushed a commit to nightingale/react-native that referenced this issue Jun 18, 2016
Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way:
* All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread)
* Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too.

This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order.
See facebook#5246 (comment) for further explanation of this case.

The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block).
This creates a single way of processing events and makes it impossible to process them in different order in js.

The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with  non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now.
I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies).

On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run.

To illustrate this improvement let's look at these two systraces.
Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png
After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png

Reviewed By: javache

Differential Revision: D3092867

fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
@lacker
Copy link
Contributor

lacker commented Oct 20, 2016

Does anyone know whether this is still an issue on Android? It seems like we at least half fixed this type of problem, but since it has been a while it does not seem clear to me whether there is still a problem on Android here..

@johnwayner
Copy link

FWIW, I just saw this in Production on Android v6.0.1 with React Native v0.25.1 (which was tagged on May 4th).

@anuragdadheech
Copy link

anuragdadheech commented Mar 7, 2017

This is still reproducible on Android, RN v0.30
Fixed before v0.39.

@hramos hramos added the Icebox label Jul 20, 2017
@hramos
Copy link
Contributor

hramos commented Jul 20, 2017

Hi there! This issue is being closed because it has been inactive for a while. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. Either way, we're automatically closing issues after a period of inactivity. Please do not take it personally!

If you think this issue should definitely remain open, please let us know. The following information is helpful when it comes to determining if the issue should be re-opened:

  • Does the issue still reproduce on the latest release candidate? Post a comment with the version you tested.
  • If so, is there any information missing from the bug report? Post a comment with all the information required by the issue template.
  • Is there a pull request that addresses this issue? Post a comment with the PR number so we can follow up.

If you would like to work on a patch to fix the issue, contributions are very welcome! Read through the contribution guide, and feel free to hop into #react-native if you need help planning your contribution.

@hramos hramos closed this as completed Jul 20, 2017
@facebook facebook locked as resolved and limited conversation to collaborators Jul 20, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 20, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Platform: iOS iOS applications. Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests