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

Save eventually issue #831

Merged
merged 12 commits into from
Jul 20, 2018
Merged

Conversation

cjosepha
Copy link
Contributor

Failing test for issue #827

cjosepha added 3 commits May 24, 2018 12:25
- The eventually queue should be cleared at each test execution but Parse.getEventuallyQueue().clear() crashes, that's why line 1585 is commented
* master:
  Correction to gcm artifact name (parse-community#830)
@flovilmart
Copy link
Contributor

Awesome ! Thanks! Any chance you had the opportunity to identify what’s deadlocking?

@cjosepha
Copy link
Contributor Author

@flovilmart unfortunately no. Also, to be honest, I'm not sure this test reproduces the exact same issue I have in my app because behavior is a little bit different. So maybe it's not a deadlock.
Can you review this test to make sure mocking is done properly?

@flovilmart
Copy link
Contributor

@dangtz I'm not an Android expert but likely @rogerhu or @Jawnnypoo may have a better grasp.

Seems that the assertion that is failing is this one Is it what you'd expect?

@cjosepha
Copy link
Contributor Author

cjosepha commented May 24, 2018

@flovilmart I expected that it fails on another assertion : this one.
In my app test, the first time saveEventually() is called, it returns the invalid session error properly, then all calls to save() should freeze.
Here the first call to saveEventually() freezes, that's why I'm wondering if test conditions are right.

@flovilmart
Copy link
Contributor

flovilmart commented May 24, 2018

Doesnt't look it's what's failing on travis, I'll have a look with your code.

@cjosepha
Copy link
Contributor Author

@flovilmart sorry, I misunderstood : yes this test is expected to fail here (ParseUserTest.java:1624).
Thanks!

@flovilmart
Copy link
Contributor

Ah good then! Thanks!

@cjosepha
Copy link
Contributor Author

@flovilmart now the test fails at the right line.

We can see that:

@flovilmart
Copy link
Contributor

flovilmart commented May 29, 2018

Awesome! That helps a lot!

@cjosepha
Copy link
Contributor Author

Yes, now I'm sure the test is right, I will be able to see why the 2nd save freezes. Will let you know.

@flovilmart
Copy link
Contributor

Awesome! If you have any questions, don’t hesitate to ask’ we really appreciate you take the time to dive in! That’s the way to go!

@cjosepha
Copy link
Contributor Author

Unfortunately, the reason why the save freezes here is due to the way the server response is mocked here : the same instance of ParseHttpClient should be kept instead of creating a new one. So a ParseException with "bad json response" message is thrown.

@coveralls
Copy link

coveralls commented May 29, 2018

Coverage Status

Coverage increased (+10.6%) to 69.579% when pulling 70516c3 on DanGTZ:save-eventually-issue into 2817393 on parse-community:master.

@codecov
Copy link

codecov bot commented Jun 1, 2018

Codecov Report

Merging #831 into master will increase coverage by 9.77%.
The diff coverage is 75%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #831      +/-   ##
============================================
+ Coverage     54.61%   64.39%   +9.77%     
- Complexity     1709     1906     +197     
============================================
  Files           123      123              
  Lines          9785     9796      +11     
  Branches       1372     1375       +3     
============================================
+ Hits           5344     6308     +964     
+ Misses         4013     2980    -1033     
- Partials        428      508      +80
Impacted Files Coverage Δ Complexity Δ
Parse/src/main/java/com/parse/ParseObject.java 59.52% <100%> (+8.36%) 234 <0> (+29) ⬆️
Parse/src/main/java/com/parse/Parse.java 57.07% <42.85%> (+35.71%) 28 <2> (+17) ⬆️
...rse/src/main/java/com/parse/OfflineQueryLogic.java 66% <0%> (+0.87%) 132% <0%> (+5%) ⬆️
...rse/src/main/java/com/parse/ParseSQLiteCursor.java 1.69% <0%> (+1.69%) 1% <0%> (+1%) ⬆️
...rse/src/main/java/com/parse/ParseCommandCache.java 2.18% <0%> (+2.18%) 3% <0%> (+3%) ⬆️
Parse/src/main/java/com/parse/ParseUser.java 79.59% <0%> (+2.44%) 117% <0%> (+10%) ⬆️
...in/java/com/parse/CachedCurrentUserController.java 93.96% <0%> (+2.58%) 15% <0%> (ø) ⬇️
...arse/src/main/java/com/parse/ParseRESTCommand.java 86.36% <0%> (+3.18%) 57% <0%> (+5%) ⬆️
Parse/src/main/java/com/parse/PLog.java 63.33% <0%> (+3.33%) 11% <0%> (+1%) ⬆️
...arse/src/main/java/com/parse/ParseCorePlugins.java 67.22% <0%> (+4.99%) 51% <0%> (+3%) ⬆️
... and 16 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2817393...70516c3. Read the comment docs.

@rogerhu
Copy link
Contributor

rogerhu commented Jun 3, 2018

It seems like the deadlock is happening inside ParseObject in the waitFor() call? (

return task.onSuccessTask(
TaskQueue.<Void>waitFor(toAwait)
)

If I had to guess, it would seem that the task queue never quite runs quite right after sessions are closed (because unhandled exception occurred)

@cjosepha
Copy link
Contributor Author

cjosepha commented Jun 3, 2018

@rogerhu Yes deadlock occurs there but I can't reproduce it with this test.

@natario1
Copy link
Contributor

natario1 commented Jun 3, 2018

I have not followed the conversation with attention but, as someone who has gone crazy on #646 , this smells like it could be it. Just 2 cents, maybe it might save you some time.

That one is an issue with the definition of the default pool thread of bolts. The pool size there depends on the machine processor, so I can see how your tests that run on the PC go well, while the app hangs because your smartphone has less CPU cores.

AFAIK Tasks should not hang if there are unhandled exception. But they hang if you do too much (allocate too much threads with dependencies between them. Task.wait() is a typical example).

@cjosepha
Copy link
Contributor Author

cjosepha commented Jun 4, 2018

@natario1 I had a look at #646 conversation a few days ago and I would say that it doesn't seem to be related to this issue.

This unit test doesn't reproduce the lock because it misses some code to create the conditions of an app relaunch, and then attempt to save again. This is exactly what to do, on a fresh install, to make the lock occur, whatever the device. You can try it with my test app mentioned in #827

@cjosepha
Copy link
Contributor Author

cjosepha commented Jun 10, 2018

I've updated the test and now it reproduces the deadlock responsible for #827

@cjosepha
Copy link
Contributor Author

Is there a design/spec document describing how the ParseObject.operationSetQueue is expected to work?

I found that the problem is the way this queue it's managed after a saveEventually :

  • In case of success, this queue is modified and persisted to LDS
  • In case of failure, this queue is modified and not persisted to LDS
    I have tested a fix that seems to work well, but I need to know the expected behavior to make sure the fix is done the right way.

@flovilmart
Copy link
Contributor

@dangtz can you shoot away the fix so we can have a look and evaluate from there?

* Improved test for parse-community#827 :
- Generalize to any server error that is different to CONNECTION_FAILED (not only INVALID_SESSION_TOKEN)
- Added isDirty() checks
- fixed test tearDown
@cjosepha
Copy link
Contributor Author

On my side I get regression only at ParseInstallationTest.java:193 and ParseInstallationTest.java:151 but I can see that on Travis there is another regression on ParseCorePluginsTest.java:41

About the ParseInstallation tests : can someone explain why saveAsync is expected to be called twice?

@flovilmart
Copy link
Contributor

can someone explain why saveAsync is expected to be called twice?

I believe @dangtz a better question is why is the save called less times now :)

@cjosepha
Copy link
Contributor Author

cjosepha commented Jun 13, 2018

@flovilmart indeed it is :)
This is because a CACHE_MISS exception is thrown here due to test conditions.

@cjosepha
Copy link
Contributor Author

cjosepha commented Jul 6, 2018

@flovilmart when is the review planned? Do you need more info?

@flovilmart
Copy link
Contributor

flovilmart commented Jul 7, 2018

@parse-community/android can you guys get a look?

@Jawnnypoo
Copy link
Member

Just to clarify @dangtz , does this solve the save eventually issue? Or does it create a test that shows how to reproduce it?

@cjosepha
Copy link
Contributor Author

@Jawnnypoo this does both : test and solve. The fix is a modification of the ParseObject. handleSaveResultAsync() method to save the state of the object in LDS when the saveEventually fails.
The test ParseUserTest.testSaveEventuallyWhenServerError() will fail if you remove the fix.
Note that I had to also modify an existing test for my fix to pass : 0f5e5af#diff-5a838ec813bd191cc3fd2173ff95b238

@Jawnnypoo
Copy link
Member

@dangtz Can you review some of the comments I made when you get a chance?

@cjosepha
Copy link
Contributor Author

@Jawnnypoo sure! Where can I see them ?

@Jawnnypoo
Copy link
Member

I believe they are marked in this PR if you go to the Files changed tab

@cjosepha
Copy link
Contributor Author

Sorry I can't see any comment in Files changed tab.

@Jawnnypoo
Copy link
Member

@cjosepha
Copy link
Contributor Author

No, sorry, only code.

@Jawnnypoo
Copy link
Member

Even if you scroll through it? The comments appear inline, within the files

@@ -256,7 +260,11 @@ public static void initialize(Configuration configuration) {
// isLocalDataStoreEnabled() to perform additional behavior.
isLocalDatastoreEnabled = configuration.localDataStoreEnabled;

ParsePlugins.initialize(configuration.context, configuration);
if (parsePlugins == null) {
Copy link
Member

Choose a reason for hiding this comment

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

@dangtz Can you explain what the reasoning is for these changes here?

Copy link
Contributor Author

@cjosepha cjosepha Jul 20, 2018

Choose a reason for hiding this comment

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

These changes allow to initialize Parse with a mock ParsePlugins object, for test purpose.
The mock ParsePlugins object is needed to reproduce the issue, and calling Parse.initialize() is also needed to reproduce it. These changes are the solution to have both.

@@ -311,6 +319,8 @@ public Void then(Task<Void> task) throws Exception {
}

static void destroy() {
ParseObject.unregisterParseSubclasses();
Copy link
Member

Choose a reason for hiding this comment

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

Was this causing problems? Curious if this was added to help the tests pass, or if it was related to the bug?

Copy link
Contributor Author

@cjosepha cjosepha Jul 20, 2018

Choose a reason for hiding this comment

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

This was not causing the problem, and yes it's to help test teardown here by aggregating deallocations. Note that Parse.destroy() was not used anywhere in the project before my changes.

@@ -322,6 +332,8 @@ static void destroy() {

ParseCorePlugins.getInstance().reset();
ParsePlugins.reset();

setLocalDatastore(null);
Copy link
Member

Choose a reason for hiding this comment

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

Same question here, is this for the tests, or related to the bug?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For tests, as Parse.destroy() is only used in tests.

@Jawnnypoo
Copy link
Member

That was my fault, I guess I didn't submit the review 🤔

@cjosepha
Copy link
Contributor Author

No problem, replied to your comments.

Copy link
Contributor Author

@cjosepha cjosepha left a comment

Choose a reason for hiding this comment

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

@Jawnnypoo could you share your opinion about this?

@Override
public Task<Void> then(Task<Void> task) throws Exception {
if (task.isFaulted()) {
return Task.forResult(null);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is to not propagate the exception in case fetching the store here fails. If exception was propagated, existing ParseInstallationTests would fail (testMissingRequiredFieldWhenSaveAsync and testObjectNotFoundWhenSaveAsync) because they don't create the right conditions for the fetch to work (a CACHE_MISS exception is then thrown). Maybe we should update those tests instead, by creating the right conditions, and let the exception propagate by using onSuccessTask at line 1451 as on my previous commit.

Copy link
Member

Choose a reason for hiding this comment

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

Yep, I think it is best to update the tests to make them have the right conditions for success/expected failure rather than modifying the library to work with the expectations of the tests. I think we can go ahead and merge this in though, as it fixes the issue at hand, and keep this in mind for potentially another pull request if that is cool with you.

@Jawnnypoo Jawnnypoo merged commit b0b00d1 into parse-community:master Jul 20, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants