Storage Android bug: Exception when calling cancel() of in progress upload

2,262 views
Skip to first unread message

Mike Wadhera

unread,
Oct 27, 2016, 12:21:27 AM10/27/16
to Firebase Google Group
We're using Firebase Storage on Android for seamless background upload of large files.

We wish to cancel active uploads when a user moves from WiFi to Cellular as to not tax their data plans.

The ideal behavior we're going for:

1. User enters WiFi
2. Create or resume upload tasks <uploads begin>
3. User leaves WiFi
4. Cancel active uploads

1-3 work great. The problem is during #4 - a StorageException is thrown when we attempt to cancel any active upload tasks

Stack Trace:

W/StorageTask: unable to change internal state to: INTERNAL_STATE_CANCELED isUser: true from state:INTERNAL_STATE_IN_PROGRESS
E/StorageException: StorageException has occurred.
The operation retry limit has been exceeded.
Code: -13030 HttpResult: -2
E/StorageException: java.io.IOException: 
at bpc.a(:com.google.android.gms.DynamiteModulesC:430)
at bpc.a(:com.google.android.gms.DynamiteModulesC:1410)
at bow.onTransact(:com.google.android.gms.DynamiteModulesC:53)
at android.os.Binder.transact(Binder.java:387)
at com.google.android.gms.internal.zzans$zza$zza.zzuj(Unknown Source)
at com.google.android.gms.internal.zzanv.zza(Unknown Source)
at com.google.firebase.storage.UploadTask.zza(Unknown Source)
at com.google.firebase.storage.UploadTask.aA(Unknown Source)
at com.google.firebase.storage.UploadTask.run(Unknown Source)
at com.google.firebase.storage.StorageTask$5.run(Unknown Source)


Code:

for (UploadTask uploadTask : mStorageRef.getActiveUploadTasks()) {
   
if (!uploadTask.isCanceled()) {
       
Log.d(TAG, "Canceling task");
        uploadTask
.cancel();
   
}
}

On app restart these tasks are then stuck in CANCELING 

Canceling at that point works as advertised and we can resume.

Mike Wadhera

unread,
Oct 27, 2016, 6:34:52 PM10/27/16
to Firebase Google Group
Is there a better way to achieve what i'm trying? It seems pause() also triggers a similar exception.

Mike Wadhera

unread,
Oct 27, 2016, 8:42:47 PM10/27/16
to fireba...@googlegroups.com
I just upgraded to 9.8.0 from 9.6.1 and still seeing this behavior


--
You received this message because you are subscribed to a topic in the Google Groups "Firebase Google Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/firebase-talk/7zFyDrKdxD8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to firebase-talk+unsubscribe@googlegroups.com.
To post to this group, send email to fireba...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/e9009c73-de9a-40cf-8dd0-6c7dc8464fd1%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Doug Stevenson

unread,
Oct 31, 2016, 11:17:08 AM10/31/16
to Firebase Google Group
Mike, Two questions.  Are you saying that the cancel() method itself throws an exception?  That shouldn't happen.  Or are you observing that an exception is received through on OnFailureListener?  That is expected for cancelations, but the message you're seeing makes it look like there is no data service available at all at the time of the cancel (which I suppose is entirely possible if you've just left wifi service).

Second question - Wouldn't you rather pause the transfer when you leave wifi, then resume it later when it comes back?  Canceled transfers can't be resumed.

Doug
To unsubscribe from this group and all its topics, send an email to firebase-tal...@googlegroups.com.

Benjamin Wulfe

unread,
Oct 31, 2016, 1:57:02 PM10/31/16
to Firebase Google Group
Hi Mike.  I've taken a look and I think what is happening is that some of our messages (which are not errors) are throwing you off here.

a few observations:
a) The message "W/StorageTask: unable to change internal state to: INTERNAL_STATE_CANCELED isUser: true from state:INTERNAL_STATE_IN_PROGRESS" is actually not an error and should be debug level and made not as scary.  This message occurs when the task is already in flight and in a state where your call will actually place the task into a "canceling" state so the background operation can clean up properly.  We do not block the calling method until the task is fully canceled.  If the task cannot be placed into a "canceling" state, then you will get a "false" return value (and we should log in that case).

b) The actual error message you are getting:
  E/StorageException: StorageException has occurred.
  The operation retry limit has been exceeded.
  Is actually indicating that the task failed because it reached the maximum amount of time attempting to connect to the server (were you testing without a connection?)

c) When you restart and check for "isCanceled" this will return false on tasks that have failed, but were not canceled (such as the connection error described in b).  Instead of doing this, you should instead check for "isInProgress".

I think the confusing aspect here is that we do not allow you to cancel a task that already failed -- where a simpler design would let you cancel anything, including an already failed task.  We'll look to see if we can improve this.
I hope this explains a bit of whats going on.   I also have a very simple test app you can run to see cancel work for yourself:

StorageTask<UploadTask.TaskSnapshot> myTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
    setContentView
(R.layout.activity_main);

   
this.runOnUiThread(new Runnable() {
       
@Override
        public void run() {
           
FirebaseStorage.getInstance().setMaxUploadRetryTimeMillis(15000); //<== turn off your data connection to see the retry error after 15 seconds
           
StorageReference ref = FirebaseStorage.getInstance().getReference("bigFile.txt");
           
StringBuilder builder = new StringBuilder();
           
for(int i=0; i<50;i++) { //50 MB should be enough to cancel while in flight.
              for(int j=0;j<1024;j++) {
                 
for (int k = 1; k<1024;k++) {
                      builder
.append('3');
                 
}
             
}
           
}
           
myTask = ref.putBytes(builder.toString().getBytes()).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
               
@Override
                public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                   
Log.w("TAG","success!");
               
}
           
}).addOnFailureListener(new OnFailureListener() {
               
@Override
                public void onFailure(@NonNull Exception e) {
                   
Log.e("TAG", e.toString());
               
}
           
}).addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
               
@Override
                public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
                   
Log.d("TAG", "progress.");
               
}
           
});
       
}
   
});
}

public void onCancelClick(View v) {
   
myTask.cancel();
}


Benjamin Wulfe

unread,
Oct 31, 2016, 1:57:44 PM10/31/16
to Firebase Google Group
Also, in the future, please give support a try as they can help you work through these issues:

Mike Wadhera

unread,
Oct 31, 2016, 4:41:19 PM10/31/16
to fireba...@googlegroups.com
Benjamin/Doug thank you for thoughtful responses and code. 
Noted the support channel for future issues.

Few points after parsing through this on our side:

* Yes, for our purposes, pause() is better here - thanks for confirming
* In our testing we were disabling all network on the device to simulate a WiFi loss. 
* Since our code is triggered by a broadcast receiver on CONNECTIVITY_CHANGE, our cancel/pause code was probably running on tasks that were already failed from the network unavailability

We're going to retry a more measured test (forcing 3G rather than Airplane mode). But yes a more ideal situation here would to allow our code to progress with cancel/pause even if the task is failed. I think a safe addition to our cancel code is to check first for isFailed or no connectivity. 


To unsubscribe from this group and all its topics, send an email to firebase-talk+unsubscribe@googlegroups.com.

To post to this group, send email to fireba...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages