redux-offline
redux-offline copied to clipboard
Odd behavior when closing app during long request on android
I've been doing some testing to determine how best to recover when the app is closed during long requests. I've been using an image upload as my test case and I throttle the app phone down to 56k using charles proxy so the image upload takes a little bit (like 30s).
When I close the app the request often completes successfully even though the app is no longer active (presumably this is because network requests are happening on a separate thread that is not terminated when the app is "closed").
When the app is reopened the request is still on top of the queue and is retried (which is expected), but redux-offline moves on without waiting for the resolution of the new request. It just starts processing the next requests in the queue.
Eventually the retried upload does complete, and the redux-offline code runs in response to the success/failure of the request, but that happens after the queue has already been processed out of order.
Steps to reproduce
- Start a long request on android
- Press the "overview" button, and "close" the app. (I know it's not really closing, but terminate the activity or whatever it is that you're doing when you swipe an app away from the overview list)
- Wait, monitor server logs for the long request to succeed/fail (i'm seeing intermittent failures, presumably from the thread being terminated after awhile)
- Reopen the app.
- Monitor the redux actions being processed and the redux-offline queue. The long request will still be on top of the queue, and will start to be processed normally, but soon after the request starts redux-offline will move on to the next item in the queue without waiting for the long request to complete. The long request eventually completes and you see the redux actions associated with that completion fire.
Other info:
Testing on a Pixel with android Pie redux offline version: v2.4.0
I understand that redux offline doesn't support background requests yet, but I would expect the requests to still complete in order correctly when coming back from background.
Thanks for submitting such a thorough ticket. You can’t imagine how helpful it is to have this. I’m away on holidays but when I have a chance I’ll try to reproduce and investigate what’s happening.
When the request is performed the offline.busy state should be true blocking new actions from being dispatched.
@wacii btw I found an action called OFFLINE_SEND
in the middleware that I think is not being dispatched, ever 😂. We should remove it.
I'll take a peek at this over the weekend.
@sorodrigo Yeah, that action is for people to use manually to force the queue to resolve the top action. It was mentioned in the old documentation (I think), guess I forgot to include it in the rewrite. I never saw the use for it, and it seems awfully problematic, so I would be fine removing it.
@sorodrigo no problem, hopefully what goes around comes around and the next issue I have to resolve will be thoroughly documented...
Can't reproduce on the web. I could see it happening if on reopening the web page, both the previous state was preserved (presumably by the mobile browser) and redux-persist rehydrated the state. But that is just a guess. Maybe @sorodrigo will have more luck.
I was testing with react-native, an important piece of info I forgot to put in the issue. I'd be surprised if you could repro on web
Any update on this, I'm facing the same issue...I have an image upload request....when I terminate the app the request fails with status 500 error also, the request present after this request are executed even before it execute commit or rollback.
We actually do have an update that I've been meaning to report here.
The bug is actually even more of an edge case, it happens when you make a network request after the app is made active, but before the old request queue is hydrated.
So in our case, when our app is brought to the foreground we refetch our user's information (in case of server side changes) and some other stuff. That information gets added to the request queue, and then the old queue is hydrated into the store (overwriting whatever is there). When that happens the new first request in the queue is triggered (in my example it's the image upload), and when the user request comes back successfully redux-offline grabs the next thing in the queue and keeps working, in effect creating two request processors.
The chain of events is like this:
- Issue long request (request A), and then maybe two or three other requests in the queue behind the long request
- Close the app
- Reopen the app
- Before hydration, issue a request (request B)
- offline state is hydrated over the new queue
- Request A is then retried
- Request B completes successfully and redux-offline proceeds to process the rest of the queue
- Request A is completed (either successfully or otherwise), but requests that were supposed to be queued after request A have already been processed
We're working around this by doing the obvious thing and making sure the store is hydrated before issuing our "app is active" requests.
I think there is definitely some behavior here on the part of redux-offline that isn't desirable, but also I think that waiting for the store to hydrate is preferable in our case anyway so we can avoid that undesirable behavior all together.
I'am still getting an issue....I have a long request(upload image) and normal request(say update task). Consider a chain of request, updateTask->uploadImage->updateTask->updateTask->uploadImage->updateTask I did when app is in foreground and in offline mode.On terminating the app and restarting following occurs when internet is connected: 1.After rehydration the chain of commits and rollback is: updateTask->updateTask->updateTask->updateTask->uploadImage->uploadImage 2.Also commit method for update Task is called but for uploadImage rollback method is executed. *Also when app is in foreground a and above chain request are called and then app is connected to internet the request are executed in order successfully Can Anyone any possible solution or workaround for this situation.
Can you please provide code? Also, are you doing anything to the queue?
Yeah I'am modifying queue, my code is as follows: In my action.js:
export function uploadImage(jobId,params,userData){
return {
type:actionTypes.UPLOAD_IMAGE_OPTIMIST,
payload:{msg:'UNSYNCED',jobId:jobId.split('_')[0]},
meta:{
offline:{
effect:{
url:apiConstants.API_SERVER_HOST + "/jobs/image/" + jobId,method:'post',params, body:userData},
commit:{
type:actionTypes.UPLOAD_IMAGE_COMMIT,
meta:{msg:'OK'}
},
rollback:{
type:actionTypes.UPLOAD_IMAGE_ROLLBACK,
meta:{msg:'ERROR',jobId:jobId.split('_')[0]}
}
}
}
In my reducer.js:
case actionTypes.UPLOAD_IMAGE_OPTIMIST:
return {
...state,
UploadImage: {
...state.UploadImage,
msg: action.payload.msg
}
}
case actionTypes.UPLOAD_IMAGE_COMMIT:
return {
...state,
UploadImage: {
...state.UploadImage,
msg: action.meta.msg
}
}
case actionTypes.UPLOAD_IMAGE_ROLLBACK:
newArray = [...state.Tasks.pl]
val = action.meta.jobId
index = newArray.findIndex(function (item, i) {
return item.id === val
});
newArray[index].jobStatus = '2'
if (newArray[index].actualEndTime != null) {
let rollback_time = new Date()
newArray[index].actualStartTime = newArray[index].actualStartTime + (rollback_time - newArray[index].actualEndTime)
console.log('TIME WASTED', rollback_time - newArray[index].actualEndTime)
newArray[index].actualEndTime = null
}
return {
...state,
Tasks: {
...state.Tasks,
pl: [...newArray]
},
UploadImage: {
...state.UploadImage,
msg: action.meta.msg,
error: action.payload.response
}
}
My store.js:
queue: {
...defaultQueue,
dequeue(array, action) {
let new_array = array
if (action.type === actionTypes.UPLOAD_IMAGE_ROLLBACK) {
let jobId = array[0].payload.jobId
new_array = array.filter(function (obj) {
return obj.payload.jobId !== jobId;
});
console.log('ROLBACK NEW ARRAY IS', new_array)
return new_array
}
const [, ...rest] = array;
return rest;
}
},
Is there something wrong with my approach??
Haven't had a chance to look yet. Would you mind using the markdown code formatter please so that it's simpler to look at.
yeah done now!!