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

Fix RefreshControl race condition #7317

Closed

Conversation

janicduplessis
Copy link
Contributor

@janicduplessis janicduplessis commented May 1, 2016

There was a race condition with SwipeRefreshLayout that cause the RefreshControl to keep refreshing when it shouldn't.

It was caused because we have to use post to set the refreshing state otherwise it doesn't work when setting refreshing=true on initial mount. What happened is that post doesn't guarantee the order the runnables will be called so calling post with refreshing=true followed by refreshing=false caused the resfreshing=false runnable to be called before the resfreshing=true one. This made it stay in refreshing state when it should not.

D/test    ( 6171): setRefreshing true
W/ReactNativeJS( 6171): false
D/test    ( 6171): setRefreshing false
D/test    ( 6171): setRefreshing post false
D/test    ( 6171): setRefreshing post true

This change adds an instance variable and uses it in the post runnable to make sure the last set value is always used.

Test plan (required)
Tested that it fixed the issue in the original issue app and did some additional tests in UIExplorer example to make sure everything still worked properly.

Fixes #7313

@ghost
Copy link

ghost commented May 1, 2016

By analyzing the blame information on this pull request, we identified @bestander and @andreicoman11 to be potential reviewers.

@ghost ghost added GH Review: review-needed CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. labels May 1, 2016
@brentvatne
Copy link
Collaborator

Seems reasonable to me, what do you think @bestander?

@andreicoman11
Copy link
Contributor

Lgtm

@andreicoman11
Copy link
Contributor

@facebook-github-bot shipit

@ghost ghost added GH Review: accepted Import Started This pull request has been imported. This does not imply the PR has been approved. and removed GH Review: review-needed labels May 11, 2016
@ghost
Copy link

ghost commented May 11, 2016

Thanks for importing. If you are an FB employee go to Phabricator to review.

@ghost ghost closed this in 8fbce30 May 12, 2016
@bestander
Copy link
Contributor

@janicduplessis looks like this change breaks ReactSwipeRefreshLayoutTestCase instrumentation test that got open sourced just a couple of days ago.

@bestander
Copy link
Contributor

I'll have to revert for now but please rebase and try again

bestander added a commit to bestander/react-native that referenced this pull request May 12, 2016
ghost pushed a commit that referenced this pull request May 12, 2016
Summary:
This reverts commit 8fbce30.
Reverts #7317 because it breaks instrumentation tests https://circleci.com/gh/facebook/react-native/6521
Closes #7529

Differential Revision: D3292461

fbshipit-source-id: 7dcde05adefe41e6b3c28697fccfa232a45f0742
@ghost
Copy link

ghost commented May 13, 2016

@janicduplessis updated the pull request.

@facebook-github-bot facebook-github-bot added Import Failed and removed Import Started This pull request has been imported. This does not imply the PR has been approved. labels May 13, 2016
@janicduplessis
Copy link
Contributor Author

Found out what the problem is, there is a dead lock with waitForBridgeAndUIIdle in the instrumentation test. I'll check if I can find out why it happens and fix it otherwise I got a workaround.

@bestander
Copy link
Contributor

thanks, Janic!

On Fri, May 13, 2016 at 6:55 PM, Janic Duplessis [email protected]
wrote:

Found out what the problem is, there is a dead lock with
waitForBridgeAndUIIdle in the instrumentation test. I'll check if I can
find out why it happens and fix it otherwise I got a workaround.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#7317 (comment)

@janicduplessis
Copy link
Contributor Author

I think I got to the bottom of this, the test block when calling waitForIdleSync here. Seems like an issue with animation and waitForIdleSync, I assume the animation is the SwipeRefreshLayout spinner. There is a reported bug here. I couldn't really find a workaround it.

What was weird is that it worked before so I checked what my PR changed and found out that I added a check to only call post if the refreshing state changed. Removing that check makes the test work again.

Doesn't work

  @Override
  public void setRefreshing(boolean refreshing) {
    if (mRefreshing != refreshing) {
      mRefreshing = refreshing;
      // Use `post` otherwise the control won't start refreshing if refreshing is true when
      // the component gets mounted.
      post(new Runnable() {
        @Override
        public void run() {
          ReactSwipeRefreshLayout.super.setRefreshing(mRefreshing);
        }
      });
    }
  }

Works

  @Override
  public void setRefreshing(boolean refreshing) {
    mRefreshing = refreshing;
    // Use `post` otherwise the control won't start refreshing if refreshing is true when
    // the component gets mounted.
    post(new Runnable() {
      @Override
      public void run() {
        ReactSwipeRefreshLayout.super.setRefreshing(mRefreshing);
      }
    });
  }

To be honest I'm not really sure why it makes it work but eh :)

@ghost
Copy link

ghost commented May 16, 2016

@janicduplessis updated the pull request.

@bestander
Copy link
Contributor

Thanks for giving it a try, @janicduplessis.
The component/test is considered flaky by our internal tests right now, there IS something wrong.
I wonder if there is a race condition happening and your check just highlighted it.

The code change in this PR does not fix a race condition though.

@janicduplessis
Copy link
Contributor Author

Yeah there is definetly something wrong, waitForIdleSync will block forever if it gets called while the SwipeRefreshLayout is animating. Changing the test to make it keep refreshing in the onRefresh callback will make the test fail 100% so I guess the race condition is between the SwipeRefreshLayout stopping refreshing and the first call to waitForIdleSync while it is animating.

One way of fixing it that I can see is using a simple sleep ~1sec before the assertion instead, it's a bit hacky but should work 100% since we can't rely on the normal wait technique that can block forever if a repeating animation is going on.

Do we want to merge this PR for now in address the test separatly? Right now it doesn't make it better or worse :)

@bestander
Copy link
Contributor

To be fair, I would leave it as is until we get an improvement.
A ~1 sec wait may be a problem for CI tests? The emulator there is so slow, it takes 20 times slower to run some tests then on my local PC.
I honestly don't know enough about android animations and concurrency to make a call here, feel free to invoke other people to the discussion

public ReactSwipeRefreshLayout(ReactContext reactContext) {
super(reactContext);
}

@Override
public void setRefreshing(boolean refreshing) {
mRefreshing = refreshing;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this field useful?
It is read only once in the new Runnable below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the problem is when setRefreshing gets called twice in the same bridge transaction the order the runnables get executed is bad, see the example in the PR desc, adding the field makes sure is uses the latest value.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see thanks, that makes sense.

@bestander
Copy link
Contributor

@janicduplessis, when you give it another look, could you create a new PR?
I am not sure what the shipit bot will do with the PR that was already improted.

@janicduplessis
Copy link
Contributor Author

Sure, let's close this one.

ghost pushed a commit that referenced this pull request May 23, 2016
Summary:
Improved version of #7317.

`setRefreshing` and `setProgressViewOffset` needs to be called after the view has been layed out. Instead of using `post` to do that we update the `refreshing` and `progressViewOffset` values in the first call to `onLayout`.

I also noticed that `progressViewOffset` default value wasn't exactly the same as when not calling `setProgressViewOffset` at all. Tweaked the values to match android defaults.

**Test plan (required)**
Make sure the integration test passes,
In UIExplorer: test RefreshControl with `refreshing = true` initially, test `progressViewOffset`.
Closes #7683

Differential Revision: D3334426

fbshipit-source-id: ddd63a5e9a6afe2b8b7fe6a25e875a40f4e888c6
zebulgar pushed a commit to nightingale/react-native that referenced this pull request Jun 18, 2016
Summary:
There was a race condition with `SwipeRefreshLayout` that cause the `RefreshControl` to keep refreshing when it shouldn't.

It was caused because we have to use `post` to set the refreshing state otherwise it doesn't work when setting `refreshing=true` on initial mount. What happened is that `post` doesn't guarantee the order the runnables will be called so calling post with `refreshing=true` followed by `refreshing=false` caused the `resfreshing=false` runnable to be called before the `resfreshing=true` one. This made it stay in refreshing state when it should not.

```
D/test    ( 6171): setRefreshing true
W/ReactNativeJS( 6171): false
D/test    ( 6171): setRefreshing false
D/test    ( 6171): setRefreshing post false
D/test    ( 6171): setRefreshing post true
```

This change adds an instance variable and uses it in the `post` runnable to make sure the last set value is always used.

**Test plan (required)**
Tested that it fixed the issue in the [original issue app](https://github.com/digisqu
Closes facebook#7317

Differential Revision: D3290464

Pulled By: andreicoman11

fbshipit-source-id: 15cabcfc6d2f191443be96e8845b924ce66c369f
zebulgar pushed a commit to nightingale/react-native that referenced this pull request Jun 18, 2016
Summary:
This reverts commit 8fbce30.
Reverts facebook#7317 because it breaks instrumentation tests https://circleci.com/gh/facebook/react-native/6521
Closes facebook#7529

Differential Revision: D3292461

fbshipit-source-id: 7dcde05adefe41e6b3c28697fccfa232a45f0742
bubblesunyum pushed a commit to iodine/react-native that referenced this pull request Aug 23, 2016
Summary:
There was a race condition with `SwipeRefreshLayout` that cause the `RefreshControl` to keep refreshing when it shouldn't.

It was caused because we have to use `post` to set the refreshing state otherwise it doesn't work when setting `refreshing=true` on initial mount. What happened is that `post` doesn't guarantee the order the runnables will be called so calling post with `refreshing=true` followed by `refreshing=false` caused the `resfreshing=false` runnable to be called before the `resfreshing=true` one. This made it stay in refreshing state when it should not.

```
D/test    ( 6171): setRefreshing true
W/ReactNativeJS( 6171): false
D/test    ( 6171): setRefreshing false
D/test    ( 6171): setRefreshing post false
D/test    ( 6171): setRefreshing post true
```

This change adds an instance variable and uses it in the `post` runnable to make sure the last set value is always used.

**Test plan (required)**
Tested that it fixed the issue in the [original issue app](https://github.com/digisqu
Closes facebook#7317

Differential Revision: D3290464

Pulled By: andreicoman11

fbshipit-source-id: 15cabcfc6d2f191443be96e8845b924ce66c369f
bubblesunyum pushed a commit to iodine/react-native that referenced this pull request Aug 23, 2016
Summary:
This reverts commit 8fbce30.
Reverts facebook#7317 because it breaks instrumentation tests https://circleci.com/gh/facebook/react-native/6521
Closes facebook#7529

Differential Revision: D3292461

fbshipit-source-id: 7dcde05adefe41e6b3c28697fccfa232a45f0742
bubblesunyum pushed a commit to iodine/react-native that referenced this pull request Aug 23, 2016
Summary:
Improved version of facebook#7317.

`setRefreshing` and `setProgressViewOffset` needs to be called after the view has been layed out. Instead of using `post` to do that we update the `refreshing` and `progressViewOffset` values in the first call to `onLayout`.

I also noticed that `progressViewOffset` default value wasn't exactly the same as when not calling `setProgressViewOffset` at all. Tweaked the values to match android defaults.

**Test plan (required)**
Make sure the integration test passes,
In UIExplorer: test RefreshControl with `refreshing = true` initially, test `progressViewOffset`.
Closes facebook#7683

Differential Revision: D3334426

fbshipit-source-id: ddd63a5e9a6afe2b8b7fe6a25e875a40f4e888c6
mpretty-cyro pushed a commit to HomePass/react-native that referenced this pull request Aug 25, 2016
Summary:
There was a race condition with `SwipeRefreshLayout` that cause the `RefreshControl` to keep refreshing when it shouldn't.

It was caused because we have to use `post` to set the refreshing state otherwise it doesn't work when setting `refreshing=true` on initial mount. What happened is that `post` doesn't guarantee the order the runnables will be called so calling post with `refreshing=true` followed by `refreshing=false` caused the `resfreshing=false` runnable to be called before the `resfreshing=true` one. This made it stay in refreshing state when it should not.

```
D/test    ( 6171): setRefreshing true
W/ReactNativeJS( 6171): false
D/test    ( 6171): setRefreshing false
D/test    ( 6171): setRefreshing post false
D/test    ( 6171): setRefreshing post true
```

This change adds an instance variable and uses it in the `post` runnable to make sure the last set value is always used.

**Test plan (required)**
Tested that it fixed the issue in the [original issue app](https://github.com/digisqu
Closes facebook#7317

Differential Revision: D3290464

Pulled By: andreicoman11

fbshipit-source-id: 15cabcfc6d2f191443be96e8845b924ce66c369f
mpretty-cyro pushed a commit to HomePass/react-native that referenced this pull request Aug 25, 2016
Summary:
This reverts commit 8fbce30.
Reverts facebook#7317 because it breaks instrumentation tests https://circleci.com/gh/facebook/react-native/6521
Closes facebook#7529

Differential Revision: D3292461

fbshipit-source-id: 7dcde05adefe41e6b3c28697fccfa232a45f0742
mpretty-cyro pushed a commit to HomePass/react-native that referenced this pull request Aug 25, 2016
Summary:
Improved version of facebook#7317.

`setRefreshing` and `setProgressViewOffset` needs to be called after the view has been layed out. Instead of using `post` to do that we update the `refreshing` and `progressViewOffset` values in the first call to `onLayout`.

I also noticed that `progressViewOffset` default value wasn't exactly the same as when not calling `setProgressViewOffset` at all. Tweaked the values to match android defaults.

**Test plan (required)**
Make sure the integration test passes,
In UIExplorer: test RefreshControl with `refreshing = true` initially, test `progressViewOffset`.
Closes facebook#7683

Differential Revision: D3334426

fbshipit-source-id: ddd63a5e9a6afe2b8b7fe6a25e875a40f4e888c6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Android] RefreshControl still spinning even after refreshing set to false
5 participants