14
14
15
15
package com .google .firebase .storage ;
16
16
17
+ import static com .google .firebase .storage .internal .ExponentialBackoffSender .RND_MAX ;
18
+
17
19
import android .content .ContentResolver ;
18
20
import android .content .Context ;
19
21
import android .net .Uri ;
25
27
import androidx .annotation .VisibleForTesting ;
26
28
import com .google .android .gms .common .api .Status ;
27
29
import com .google .android .gms .common .internal .Preconditions ;
30
+ import com .google .android .gms .common .util .Clock ;
31
+ import com .google .android .gms .common .util .DefaultClock ;
28
32
import com .google .firebase .appcheck .interop .InternalAppCheckTokenProvider ;
29
33
import com .google .firebase .auth .internal .InternalAuthProvider ;
30
34
import com .google .firebase .storage .internal .AdaptiveStreamBuffer ;
31
35
import com .google .firebase .storage .internal .ExponentialBackoffSender ;
36
+ import com .google .firebase .storage .internal .Sleeper ;
37
+ import com .google .firebase .storage .internal .SleeperImpl ;
32
38
import com .google .firebase .storage .internal .Util ;
33
39
import com .google .firebase .storage .network .NetworkRequest ;
34
40
import com .google .firebase .storage .network .ResumableUploadByteRequest ;
40
46
import java .io .FileNotFoundException ;
41
47
import java .io .IOException ;
42
48
import java .io .InputStream ;
49
+ import java .util .Random ;
43
50
import java .util .concurrent .atomic .AtomicLong ;
44
51
import org .json .JSONException ;
45
52
@@ -72,6 +79,12 @@ public class UploadTask extends StorageTask<UploadTask.TaskSnapshot> {
72
79
private volatile Exception mServerException = null ;
73
80
private volatile int mResultCode = 0 ;
74
81
private volatile String mServerStatus ;
82
+ private volatile long maxSleepTime ;
83
+ private static final Random random = new Random ();
84
+ /*package*/ static Sleeper sleeper = new SleeperImpl ();
85
+ /*package*/ static Clock clock = DefaultClock .getInstance ();
86
+ private int sleepTime = 0 ;
87
+ private final int minimumSleepInterval = 1000 ;
75
88
76
89
UploadTask (StorageReference targetRef , StorageMetadata metadata , byte [] bytes ) {
77
90
Preconditions .checkNotNull (targetRef );
@@ -89,6 +102,7 @@ public class UploadTask extends StorageTask<UploadTask.TaskSnapshot> {
89
102
new AdaptiveStreamBuffer (new ByteArrayInputStream (bytes ), PREFERRED_CHUNK_SIZE );
90
103
this .mIsStreamOwned = true ;
91
104
105
+ this .maxSleepTime = storage .getMaxChunkUploadRetry ();
92
106
mSender =
93
107
new ExponentialBackoffSender (
94
108
storage .getApp ().getApplicationContext (),
@@ -110,6 +124,7 @@ public class UploadTask extends StorageTask<UploadTask.TaskSnapshot> {
110
124
this .mAppCheckProvider = storage .getAppCheckProvider ();
111
125
this .mUri = file ;
112
126
InputStream inputStream = null ;
127
+ this .maxSleepTime = storage .getMaxChunkUploadRetry ();
113
128
mSender =
114
129
new ExponentialBackoffSender (
115
130
mStorageRef .getApp ().getApplicationContext (),
@@ -173,12 +188,13 @@ public class UploadTask extends StorageTask<UploadTask.TaskSnapshot> {
173
188
this .mStreamBuffer = new AdaptiveStreamBuffer (stream , PREFERRED_CHUNK_SIZE );
174
189
this .mIsStreamOwned = false ;
175
190
this .mUri = null ;
191
+ this .maxSleepTime = storage .getMaxChunkUploadRetry ();
176
192
mSender =
177
193
new ExponentialBackoffSender (
178
194
mStorageRef .getApp ().getApplicationContext (),
179
195
mAuthProvider ,
180
196
mAppCheckProvider ,
181
- mStorageRef . getStorage () .getMaxUploadRetryTimeMillis ());
197
+ storage .getMaxUploadRetryTimeMillis ());
182
198
}
183
199
184
200
/** @return the target of the upload. */
@@ -321,15 +337,18 @@ private boolean shouldContinue() {
321
337
}
322
338
323
339
boolean inErrorState = mServerException != null || mResultCode < 200 || mResultCode >= 300 ;
340
+ long deadLine = clock .elapsedRealtime () + this .maxSleepTime ;
341
+ long currentTime = clock .elapsedRealtime () + this .sleepTime ;
324
342
// we attempt to recover by calling recoverStatus(true)
325
- if (inErrorState && !recoverStatus (true )) {
326
- // we failed to recover.
327
- if (serverStateValid ()) {
328
- tryChangeState (INTERNAL_STATE_FAILURE , false );
343
+ if (inErrorState ) {
344
+ if (currentTime > deadLine || !recoverStatus (true )) {
345
+ if (serverStateValid ()) {
346
+ tryChangeState (INTERNAL_STATE_FAILURE , false );
347
+ }
348
+ return false ;
329
349
}
330
- return false ;
350
+ sleepTime = Math . max ( sleepTime * 2 , minimumSleepInterval ) ;
331
351
}
332
-
333
352
return true ;
334
353
}
335
354
@@ -410,6 +429,36 @@ private boolean recoverStatus(boolean withRetry) {
410
429
return true ;
411
430
}
412
431
432
+ /**
433
+ * Send with a delay that uses sleepTime to delay sending a request to the server. Will reset
434
+ * sleepTime upon send success. TODO: Create an exponential backoff helper to consolidate code
435
+ * here and in ExponentialBackoffSender.java
436
+ *
437
+ * @param request to send
438
+ * @return whether the delay and send were successful
439
+ */
440
+ private boolean delaySend (NetworkRequest request ) {
441
+ try {
442
+ Log .d (TAG , "Waiting " + sleepTime + " milliseconds" );
443
+ sleeper .sleep (sleepTime + random .nextInt (RND_MAX ));
444
+ } catch (InterruptedException e ) {
445
+ Log .w (TAG , "thread interrupted during exponential backoff." );
446
+
447
+ Thread .currentThread ().interrupt ();
448
+ mServerException = e ;
449
+ return false ;
450
+ }
451
+ boolean sendRes = send (request );
452
+ // We reset the sleepTime if the send was successful. For example,
453
+ // uploadChunk(request) // false, then sleepTime becomes 1000
454
+ // uploadChunk(request) // false, then sleepTime becomes 2000
455
+ // uploadChunk(request) // true, then sleepTime becomes 0 again
456
+ if (sendRes ) {
457
+ sleepTime = 0 ;
458
+ }
459
+ return sendRes ;
460
+ }
461
+
413
462
private void uploadChunk () {
414
463
try {
415
464
mStreamBuffer .fill (mCurrentChunkSize );
@@ -425,7 +474,7 @@ private void uploadChunk() {
425
474
bytesToUpload ,
426
475
mStreamBuffer .isFinished ());
427
476
428
- if (!send (uploadRequest )) {
477
+ if (!delaySend (uploadRequest )) {
429
478
mCurrentChunkSize = PREFERRED_CHUNK_SIZE ;
430
479
Log .d (TAG , "Resetting chunk size to " + mCurrentChunkSize );
431
480
return ;
0 commit comments