Skip to content

Fix a multiple pointers bug#68587

Merged
dkwingsmt merged 3 commits intoflutter:masterfrom
xu-baolin:1020bug
Oct 22, 2020
Merged

Fix a multiple pointers bug#68587
dkwingsmt merged 3 commits intoflutter:masterfrom
xu-baolin:1020bug

Conversation

@xu-baolin
Copy link
Member

@xu-baolin xu-baolin commented Oct 20, 2020

Description

This bug can be reproduced with a high probability by #68373

The scenario where the bug occurs is:

  1. Pointer 1 down and add two Recognize(TapGestureRecognizer & VerticalDragGestureRecognizer#5efda)
  2. Pointer 2 down and add two Recognize(TapGestureRecognizer & VerticalDragGestureRecognizer#5efda)
  3. Pointer 3 down and add two Recognize(TapGestureRecognizer & VerticalDragGestureRecognizer#5efda)
  4. Pointer 4 down and add only one Recognize(VerticalDragGestureRecognizer#5efda)

Because there is only one gesture in Arena 4, the VerticalDragGestureRecognizer#5efda will win by default, and acceptGesture. Therefore, this gesture can no longer be accepted by another arena, otherwise, there will be the assertion information mentioned in the #68373.

  1. Then release one of the fingers slightly before release all at the other fingers.

Why the issue happen?

From the log we can catch that:

  1. When process the last finger event _TransformedPointerCancelEvent,What we expect is stopTrackingPointer and then reject this gesture, as the following code show:
  void _giveUpPointer(int pointer, {bool reject = true}) {
    stopTrackingPointer(pointer);
    if (reject) {
      if (_velocityTrackers.containsKey(pointer)) {
        _velocityTrackers.remove(pointer);
        resolvePointer(pointer, GestureDisposition.rejected);
      }
    }
  }
  1. But when stop tracking the last pointer will call didStopTrackingLastPointer and clear the _velocityTrackers, This leads to the resolvePointer method cannot be called. This gesture becomes the last member and wins via sweeping incorrectly.

Related PR

This change is an improvement of #39017, @dkwingsmt Please review, thanks. :)

Log

Launching lib\main.dart on PDCM00 in debug mode...
Running Gradle task 'assembleDebug'...
Parameter format not correct -Built build\app\outputs\flutter-apk\app-debug.apk.
Installing build\app\outputs\flutter-apk\app.apk...
Waiting for PDCM00 to report its views...
Debug service listening on ws://127.0.0.1:59993/0X88-HsbOEM=/ws
Syncing files to device PDCM00...
I/flutter ( 5750): Gesture arena 1    ❙ ★ Opening new gesture arena.
I/flutter ( 5750): Gesture arena 1Adding: TapGestureRecognizer#93267(debugOwner: GestureDetector, state: ready, button: 1)
I/flutter ( 5750): addAllowedPointer this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.ready]
I/flutter ( 5750): Gesture arena 1Adding: VerticalDragGestureRecognizer#aac43(start behavior: start)
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.possible]event[_TransformedPointerDownEvent#da02f(position: Offset(37.7, 134.0))][1]
I/flutter ( 5750): Gesture arena 1Closing with 2 members.
I/flutter ( 5750): Gesture arena 2    ❙ ★ Opening new gesture arena.
I/flutter ( 5750): Gesture arena 2Adding: TapGestureRecognizer#d4edf(debugOwner: GestureDetector, state: ready, button: 1)
I/flutter ( 5750): addAllowedPointer this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.possible]
I/flutter ( 5750): Gesture arena 2Adding: VerticalDragGestureRecognizer#aac43(start behavior: start)
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.possible]event[_TransformedPointerDownEvent#ed1ae(position: Offset(48.7, 335.3))][2]
I/flutter ( 5750): Gesture arena 2Closing with 2 members.
I/flutter ( 5750): Gesture arena 3    ❙ ★ Opening new gesture arena.
I/flutter ( 5750): Gesture arena 3Adding: TapGestureRecognizer#3379a(debugOwner: GestureDetector, state: ready, button: 1)
I/flutter ( 5750): addAllowedPointer this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.possible]
I/flutter ( 5750): Gesture arena 3Adding: VerticalDragGestureRecognizer#aac43(start behavior: start)
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.possible]event[_TransformedPointerDownEvent#68437(position: Offset(65.0, 192.3))][3]
I/flutter ( 5750): Gesture arena 3Closing with 2 members.
I/flutter ( 5750): addAllowedPointer this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.possible]
I/flutter ( 5750): Gesture arena 4    ❙ ★ Opening new gesture arena.
I/flutter ( 5750): Gesture arena 4Adding: VerticalDragGestureRecognizer#aac43(start behavior: start)
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.possible]event[_TransformedPointerDownEvent#1675a(position: Offset(63.7, 263.7))][4]
I/flutter ( 5750): Gesture arena 4Closing with 1 member.
I/flutter ( 5750): Gesture arena 4Default winner: VerticalDragGestureRecognizer#aac43(start behavior: start)
I/flutter ( 5750): acceptGesture pointer[4][VerticalDragGestureRecognizer#aac43(start behavior: start)]state[_DragState.possible]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#f3c66(position: Offset(37.7, 134.0))][1]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:53.504000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#778c6(position: Offset(48.7, 335.3))][2]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:53.504000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#6c668(position: Offset(65.0, 192.3))][3]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:53.504000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#2ca47(position: Offset(63.7, 263.7))][4]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:53.504000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#a0143(position: Offset(37.7, 134.0))][1]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:53.538000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#355eb(position: Offset(48.7, 335.3))][2]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:53.538000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#660c6(position: Offset(65.0, 192.3))][3]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:53.538000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#09744(position: Offset(63.7, 263.7))][4]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:53.538000]
...
start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#aeb42(position: Offset(36.0, 131.0))][1]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:54.442000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#e482e(position: Offset(48.7, 335.3))][2]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:54.442000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#059bd(position: Offset(64.0, 193.3))][3]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:54.442000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#91b4a(position: Offset(62.0, 267.0))][4]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:54.442000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#64629(position: Offset(35.7, 131.0))][1]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:54.449000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#cc7c2(position: Offset(64.0, 193.0))][3]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:54.449000]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerMoveEvent#4e474(position: Offset(62.0, 267.0))][4]
I/flutter ( 5750): handleEvent 2 this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]ts[745:29:54.449000]
I/flutter ( 5750): stopTrackingPointer pointer[2]_trackedPointers[{2}]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerUpEvent#42107(position: Offset(48.7, 335.3))][2]
I/flutter ( 5750): _giveUpPointer [0] [2]reject[false]_velocityTrackers[{1: Instance of 'VelocityTracker', 2: Instance of 'VelocityTracker', 3: Instance of 'VelocityTracker', 4: Instance of 'VelocityTracker'}]
I/flutter ( 5750): stopTrackingPointer pointer[2]_trackedPointers[{1, 2, 3, 4}]
I/flutter ( 5750): _giveUpPointer [1] [2]reject[false]_velocityTrackers[{1: Instance of 'VelocityTracker', 2: Instance of 'VelocityTracker', 3: Instance of 'VelocityTracker', 4: Instance of 'VelocityTracker'}]
I/flutter ( 5750): Gesture arena 2Sweeping with 2 members.
I/flutter ( 5750): Gesture arena 2Winner: TapGestureRecognizer#d4edf(debugOwner: GestureDetector, state: ready, finalPosition: Offset(48.7, 335.3), finalLocalPosition: Offset(48.7, 15.3), button: 1, sent tap down)
I/flutter ( 5750): _giveUpPointer [0] [2]reject[true]_velocityTrackers[{1: Instance of 'VelocityTracker', 2: Instance of 'VelocityTracker', 3: Instance of 'VelocityTracker', 4: Instance of 'VelocityTracker'}]
I/flutter ( 5750): stopTrackingPointer pointer[2]_trackedPointers[{1, 3, 4}]
I/flutter ( 5750): _giveUpPointer [1] [2]reject[true]_velocityTrackers[{1: Instance of 'VelocityTracker', 2: Instance of 'VelocityTracker', 3: Instance of 'VelocityTracker', 4: Instance of 'VelocityTracker'}]
I/flutter ( 5750): resolvePointer [2]disposition[GestureDisposition.rejected]_entries[{1: Instance of 'GestureArenaEntry', 2: Instance of 'GestureArenaEntry', 3: Instance of 'GestureArenaEntry', 4: Instance of 'GestureArenaEntry'}]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerCancelEvent#8b921(position: Offset(0.0, 0.0))][4]
I/flutter ( 5750): _giveUpPointer [0] [4]reject[true]_velocityTrackers[{1: Instance of 'VelocityTracker', 3: Instance of 'VelocityTracker', 4: Instance of 'VelocityTracker'}]
I/flutter ( 5750): stopTrackingPointer pointer[4]_trackedPointers[{1, 3, 4}]
I/flutter ( 5750): _giveUpPointer [1] [4]reject[true]_velocityTrackers[{1: Instance of 'VelocityTracker', 3: Instance of 'VelocityTracker', 4: Instance of 'VelocityTracker'}]
I/flutter ( 5750): resolvePointer [4]disposition[GestureDisposition.rejected]_entries[{1: Instance of 'GestureArenaEntry', 3: Instance of 'GestureArenaEntry', 4: Instance of 'GestureArenaEntry'}]
I/flutter ( 5750): Gesture arena 3Rejecting: TapGestureRecognizer#3379a(debugOwner: GestureDetector, state: possible, button: 1, sent tap down)
I/flutter ( 5750): stopTrackingPointer pointer[3]_trackedPointers[{3}]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerCancelEvent#cb079(position: Offset(0.0, 0.0))][3]
I/flutter ( 5750): _giveUpPointer [0] [3]reject[true]_velocityTrackers[{1: Instance of 'VelocityTracker', 3: Instance of 'VelocityTracker'}]
I/flutter ( 5750): stopTrackingPointer pointer[3]_trackedPointers[{1, 3}]
I/flutter ( 5750): _giveUpPointer [1] [3]reject[true]_velocityTrackers[{1: Instance of 'VelocityTracker', 3: Instance of 'VelocityTracker'}]
I/flutter ( 5750): resolvePointer [3]disposition[GestureDisposition.rejected]_entries[{1: Instance of 'GestureArenaEntry', 3: Instance of 'GestureArenaEntry'}]
I/flutter ( 5750): Gesture arena 3Rejecting: VerticalDragGestureRecognizer#aac43(start behavior: start)
I/flutter ( 5750): _giveUpPointer [0] [3]reject[true]_velocityTrackers[{1: Instance of 'VelocityTracker'}]
I/flutter ( 5750): stopTrackingPointer pointer[3]_trackedPointers[{1}]
I/flutter ( 5750): _giveUpPointer [1] [3]reject[true]_velocityTrackers[{1: Instance of 'VelocityTracker'}]
I/flutter ( 5750): Gesture arena 3Arena empty.
I/flutter ( 5750): Gesture arena 1Rejecting: TapGestureRecognizer#93267(debugOwner: GestureDetector, state: possible, button: 1, sent tap down)
I/flutter ( 5750): stopTrackingPointer pointer[1]_trackedPointers[{1}]
I/flutter ( 5750): handleEvent this[VerticalDragGestureRecognizer#aac43(start behavior: start)]_state[_DragState.accepted]event[_TransformedPointerCancelEvent#7e63e(position: Offset(0.0, 0.0))][1]
I/flutter ( 5750): _giveUpPointer [0] [1]reject[true]_velocityTrackers[{1: Instance of 'VelocityTracker'}]
I/flutter ( 5750): stopTrackingPointer pointer[1]_trackedPointers[{1}]
I/flutter ( 5750): _giveUpPointer [1] [1]reject[true]_velocityTrackers[{}]
I/flutter ( 5750): Gesture arena 1Default winner: VerticalDragGestureRecognizer#aac43(start behavior: start)
I/flutter ( 5750): acceptGesture pointer[1][VerticalDragGestureRecognizer#aac43(start behavior: start)]state[_DragState.ready]
E/flutter ( 5750): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Null check operator used on a null value
E/flutter ( 5750): #0      DragGestureRecognizer.acceptGesture (package:flutter/src/gestures/monodrag.dart:328:60)
E/flutter ( 5750): #1      GestureArenaManager._resolveByDefault (package:flutter/src/gestures/arena.dart:251:25)
E/flutter ( 5750): #2      GestureArenaManager._tryToResolveArena.<anonymous closure> (package:flutter/src/gestures/arena.dart:232:31)
E/flutter ( 5750): #3      _rootRun (dart:async/zone.dart:1182:47)
E/flutter ( 5750): #4      _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 5750): #5      _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter ( 5750): #6      _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter ( 5750): #7      _rootRun (dart:async/zone.dart:1190:13)
E/flutter ( 5750): #8      _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 5750): #9      _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter ( 5750): #10     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter ( 5750): #11     _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
E/flutter ( 5750): #12     _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
E/flutter ( 5750): 

Related Issues

Fixes #68373

Tests

See files.

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Oct 20, 2020
@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@google-cla google-cla bot added the cla: yes label Oct 20, 2020
@dkwingsmt
Copy link
Contributor

Thank you so much for the investigation and the writeup! Can you come up with a unit test so that we don't accidentally regress it?

@xu-baolin
Copy link
Member Author

Thank you so much for the investigation and the writeup! Can you come up with a unit test so that we don't accidentally regress it?

Coming soon.

@xu-baolin
Copy link
Member Author

Thank you so much for the investigation and the writeup! Can you come up with a unit test so that we don't accidentally regress it?

Done.

@mustCallSuper
void resolvePointer(int pointer, GestureDisposition disposition) {
final GestureArenaEntry? entry = _entries[pointer];
final Map<int, GestureArenaEntry> localEntries = Map<int, GestureArenaEntry>.from(_entries);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this clone needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entry. resolve() will trigger nested
calls to this function, so we should remove the entry first as following do.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entry in the map is a reference type here, I think we can use it after removing it from the map without clone, I will double check:)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed the unnecessary map clone.

Copy link
Contributor

@dkwingsmt dkwingsmt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM except for one comment. Thank you for the PR!

@xu-baolin
Copy link
Member Author

@dkwingsmt I think it is ready for landing now. : )

@dkwingsmt dkwingsmt merged commit bde85ea into flutter:master Oct 22, 2020
jonahwilliams pushed a commit that referenced this pull request Oct 23, 2020
jonahwilliams pushed a commit that referenced this pull request Oct 23, 2020
@yitong12580
Copy link

你好 这边已经是最新版本 master 分支
但是还是会出现这个问题。
重现方式:
tabbarview 左右切换的时候 双指 同时多次点击屏幕

@xu-baolin
Copy link
Member Author

你好 这边已经是最新版本 master 分支
但是还是会出现这个问题。
重现方式:
tabbarview 左右切换的时候 双指 同时多次点击屏幕

抱歉这个修正昨天被回退了#68841 ,请确认你验证的版本,如果包含了这个修正还是有问题,请帮忙提供版本信息,和一个最小范围内可以直接运行的复现问题的用例,谢谢。 我会尽快调查影响然后重新合入这个修正。

Sorry, this fix was revert yesterday #68841 . Please confirm the version you verified. If there is still a problem with the change, please provide the version information and a minimal sample code that can be run directly. Thank you. I'll look into the impact as soon as possible and then reland this PR.

@xu-baolin
Copy link
Member Author

@dkwingsmt
I am so sorry for breaking something.
From this error log #68841 , it seems that it has nothing to do with our change, also waiting for your opinions. I have no right see all the logs. Can you help me downlload all the failed logs?

dkwingsmt added a commit to dkwingsmt/flutter that referenced this pull request Oct 29, 2020
dkwingsmt added a commit that referenced this pull request Nov 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gestures stop working sometimes in lists with multiple touch points

3 participants