-
Notifications
You must be signed in to change notification settings - Fork 19
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
SharedPreferences are sometimes not saved properly #36
Comments
Additional logging added in pull request #35 |
A potential solution might be to use commit() instead of apply().
On the other hand, this is a service, not an activity. Maybe that's why it is confusing? |
If this refers specifically to the call on SharedPrefs in TripDiaryStateMachineReceiver, then it is due to the lifecycle of BroadcastReceiver, which is not a service, but ceases to exist as a separate component almost immediately after completion of any running code. You may wish to extend WakefulBroadcastReceiver, use call goAsync within the onReceive(Context,Intent) method, or simply refactor the BroadcastReceiver as an inner static class of a service, which is a bit more idiomatic in my experience. I tend to prefer use of the CommonsWare WakefulIntentService for state machines and other short small class that act as short-lived data synchronizers: https://github.com/commonsguy/cwac-wakeful. |
Yes, it refers to TripDiaryStateMachineReceiver. However, as I quoted above, the documentation for apply() specifically says that
Presumably, this includes switching from the existing to the not-existing state. Ah, I think that the confusion was because I said "this is a service". You are absolutely right that it is not. I meant to say "this is not an activity" and was sloppy. But the question about why the save failed is still, IMHO valid. |
There is some lack of detail in the Android documentation about killing a process with a thread running asynchronously. Check out: http://developer.android.com/reference/android/content/BroadcastReceiver.html#onReceive(android.content.Context, android.content.Intent) http://developer.android.com/reference/android/content/BroadcastReceiver.html#onReceive(android.content.Context,%20android.content.Intent) and compare with: Which says that you should not run asynchronous processes from the broadcast receiver’s onReceive(). Whereas: http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply() http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply() despite SharedPreferences.Editor.apply() being an async call, the docs indicate that you need not worry about state change, as you’ve mentioned. So, my understanding is that even if the process is killed, even if your processing is not done within 10 secs any calls to async.apply() should eventually complete. However, reading down a bit, I notice your last call in onReceive() is to connect to a service, which itself may be long-running… those Callbacks down there are worrisome… (I’ve experienced IntentServices killed off when connecting to a service after onStartCommand (though this can be ameliorated on a case-specific basis somewhat with START_STICKY). OK, so aside from the Callbacks in the service (which may or may not return if the object reference is destroyed), the instant you call .connect(), the BR's onReceive() must return, which makes it eligible to be killed off immediately. The background processing may still run, though, if you manage to connect before Android kills the BackgroundReceiver. This may be dangerous; however, if several intents are received sequentially, since the context of the BroadcastReceiver may be leaked. Well, this is perhaps intentional, since I see you are maintaining the context as an instance variable. So, another possibility is that the action bearing end_trip is waiting to be written; however, another .apply() is called almost immediately afterwards on the waiting_for_trip_start Action. Despite what is written there, my hunch is that the .apply()s are not completed synchronously, since they are, as indicated, asynchronous writes (despite living in separate processes and saving immediately to memory, the actual IO on the SharedPreference (which is an XML file) must be synchronous. The ordering and safety guarantee, I believe, on applies to, um, .apply()s, which are initiated in the same process (i.e., the BroadcastReceiver), but not necessarily to .apply()s called from different processes, which is what would happen if the BroadcastReceiver responds to Intent B before Intent A’s .apply() calls have a chance to complete. Do you think these reasons apply in your situation? Sid Feygin Ph. D Student smartcities.berkeley.edu
|
Which service are you talking about? The callbacks for creating the geofence, etc? IIRC, those are executed in the thread of the geofence creation, not the thread of the broadcast receiver. So basically, we start a new thread to perform a new operation and provide code to be run in that thread once the operation is complete.
My plan for this issue was as follows:
#2 above hasn't happened yet, so I haven't made the change. If it doesn't recur until I am ready to deploy to the play store, I will probably make the change anyway just to be on the safe side. The performance implications don't appear to be very significant. |
Looks like the file handler option to log to file is in fact not thread safe. Logging with both the filehandler and a database handler at the same time indicates that chunks of log are just dropped on the floor, although the presence of a See #42 for more details. So it might just be that the change in the preferences was not logged correctly and this might be a non-issue after all. |
The mention of services above was to reiterate that despite the fact that you would run the geofence creation in a separate thread, the BroadcastReceiver component and process would certainly will be destroyed. This may or may not a problem for the thread, since it maintains the object reference (hence the leaked context) and won't be garbage collected, but the thread interaction with repeated calls to the BroadcastIntent (and any services invoked therein) may result in race conditions. See also: https://groups.google.com/forum/#!topic/android-developers/iqwmDdUumXg . This describes a very similar issue. Even improved logging will make it difficult to catch a race condition. However, SharedPreferences.OnSharedPreferenceChangeListener may be useful here if you would like to log exactly when the shared prefs are changed. I can imagine you could test this by running a long-running service from your main activity (bound or unbound) and listen for changes from the broadcast receiver. Also, are you able to recover the logcat? |
No. The events happened on 1st Jul. I noticed this on 5th Jul and filed the bug. This incident had already moved out of logcat by then.
Agreed. But once we know that the file based logging is not reliable, there is no indication that there was a race condition, or even that the shared preferences were not saved correctly. I originally thought that the shared preferences were not saved correctly because I saw a newState = ongoing_trip followed by read from the shared preferences of waiting_for_trip_state. But if we could have missing logs, then it could be that we detected a trip end in the interim and that legitimately caused the state the change, except the trip end detection was overriden. I am going to close this and the other "mysterious behavior with location stuff" bugs for now since they were not based on reliable logs. I will reopen them if I see the same behavior in the database-based logs. |
I am seeing similar behavior even with the reliable logs, so it looks like the callbacks don't work properly from a BroadcastReceiver, as @sfwatergit suggested. Below, we show three instances where this happened. First, let us show what a successful invocation looks like.
Here are the failures.
|
Another one.
|
In both those cases, the thread is terminated at that point. I can tell because the log ID gets reset right after that.
It is really clear in the first of these, since the implementation of handleAction is basically a simple if/then statement that should have called I am converting this to a service. Thanks to @sfwatergit for the catch! |
I see this behavior even after converting to a service. However, I think that in this case, there might be a race caused by the delay in write. Here are the related logs: We detect the end of the trip
Almost immediately, the geofence exits so we read the value from a different context.The value read is still the old one (
I can think of two possible reasons:
Next steps - figure out the lifecycle of an intent service by reading docs. I have already done this a bit, and it is not clear when it is terminated but I could try some more. Next, I will try two options, in this order:
|
Actually, this is probably a result of (2) above. Once the Will fix this by using a standard |
There may be an issue with maintenance of the wake lock when activating the
|
@sfwatergit I really don't think that a wakelock is the issue. While I am using a From the documentation for your suggested library (emphasis mine)
I have checked in my fix to use a regular |
Should be fixed by ec63703 |
It looks like SharedPreferences do not save properly in some cases.
Here's what I saw from the app logs for a trip in which I left Tied House at 8:30pm and came home.
The app appeared to have been killed and relaunched by the geofence exit, since the id was 9.
After processing the geofence exit, the new state was ongoing_trip.
However, when the state is next read, it is still waiting_for_trip_start
So when the trip end is detected, we think that the current state is already waiting_for_trip_start,
so we do not turn off ongoing tracking, and we do not establish the geofence.
The text was updated successfully, but these errors were encountered: