Index: chrome/browser/google_apis/drive_uploader.cc |
diff --git a/chrome/browser/google_apis/drive_uploader.cc b/chrome/browser/google_apis/drive_uploader.cc |
index 9be960f48138b0a7623d3bd2e3bedf337c25b9b2..7d9c89e5cc212cf215076fb123c97edc67363ab5 100644 |
--- a/chrome/browser/google_apis/drive_uploader.cc |
+++ b/chrome/browser/google_apis/drive_uploader.cc |
@@ -9,7 +9,9 @@ |
#include "base/bind.h" |
#include "base/callback.h" |
#include "base/message_loop.h" |
+#include "base/rand_util.h" |
#include "base/string_number_conversions.h" |
+#include "base/time.h" |
#include "chrome/browser/google_apis/drive_service_interface.h" |
#include "chrome/browser/google_apis/drive_upload_mode.h" |
#include "chrome/browser/google_apis/gdata_wapi_parser.h" |
@@ -36,6 +38,29 @@ int64 OpenFileStreamAndGetSizeOnBlockingPool(net::FileStream* file_stream, |
return file_stream->Available(); |
} |
+base::TimeDelta GetWaitDuration(int trial_count) { |
+ // The waiting duration for the exponential back is |
+ // 1st trial: 1sec + rand millisecs |
+ // 2nd trial: 2secs + rand millisecs |
+ // 3rd trial: 4secs + rand millisecs |
+ // 4th trial: 8secs + rand millisecs |
+ // : |
+ // nth trial: 2^(n-1) + rand millisecs |
+ // Please see also |
+ // https://developers.google.com/drive/manage-uploads#exp-backoff |
+ // for more details. |
satorux1
2013/02/07 05:48:32
I thought we had similar code elsewhere. zork@ wha
Zachary Kuznia
2013/02/07 06:26:36
This should call into the scheduler instead. That
hidehiko
2013/02/07 06:53:26
So, all retrying logic will be in DriveScheduler s
|
+ DCHECK_GT(trial_count, 0); |
+ |
+ // Note: The range for RandInt is inclusive. |
+ return base::TimeDelta::FromSeconds(1 << (trial_count - 1)) |
+ + base::TimeDelta::FromMilliseconds(base::RandInt(0, 9)); |
+} |
+ |
+// Returns true if the status_code is 5xx SERVER ERROR, otherwise false. |
+bool IsHttp5xxServerError(int status_code) { |
+ return status_code >= 500 && status_code < 600; |
+} |
+ |
} // namespace |
namespace google_apis { |
@@ -61,9 +86,10 @@ struct DriveUploader::UploadFileInfo { |
etag(etag), |
completion_callback(callback), |
content_length(0), |
- next_send_position(0), |
file_stream(new net::FileStream(NULL)), |
buf(new net::IOBuffer(kUploadChunkSize)), |
+ buf_position(0), |
+ buf_size(0), |
blocking_task_runner(task_runner), |
power_save_blocker(content::PowerSaveBlocker::Create( |
content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, |
@@ -76,8 +102,8 @@ struct DriveUploader::UploadFileInfo { |
// Bytes left to upload. |
int64 SizeRemaining() const { |
- DCHECK(content_length >= next_send_position); |
- return content_length - next_send_position; |
+ DCHECK(content_length >= buf_position + buf_size); |
+ return content_length - (buf_position + buf_size); |
} |
// Useful for printf debugging. |
@@ -120,9 +146,6 @@ struct DriveUploader::UploadFileInfo { |
// Header content-Length. |
int64 content_length; |
- // The start position of the contents to be sent as the next upload chunk. |
- int64 next_send_position; |
- |
// For opening and reading from physical file. |
// |
// File operations are posted to |blocking_task_runner|, while the ownership |
@@ -135,6 +158,16 @@ struct DriveUploader::UploadFileInfo { |
// Holds current content to be uploaded. |
const scoped_refptr<net::IOBuffer> buf; |
+ // The start position of the |buf| in the file. |
+ int64 buf_position; |
+ |
+ // The size of available data in |buf|. |
+ int64 buf_size; |
+ |
+ // The number of trials for the current buffer data. |
+ // This is used to calculate exponential backoff waiting duration. |
+ int trial_count; |
+ |
// Runner for net::FileStream tasks. |
const scoped_refptr<base::SequencedTaskRunner> blocking_task_runner; |
@@ -329,10 +362,27 @@ void DriveUploader::ReadCompletionCallback( |
return; |
} |
- int64 start_position = upload_file_info->next_send_position; |
- upload_file_info->next_send_position += bytes_read; |
- int64 end_position = upload_file_info->next_send_position; |
+ // Update the buffer's position. |
+ // First, move the start position of the buffer by previous buffer size, |
+ // and then store the current buffer size. |
+ upload_file_info->buf_position += upload_file_info->buf_size; |
+ upload_file_info->buf_size = bytes_read; |
+ |
+ // Try to send all the data in the buffer. |
+ int64 start_position = upload_file_info->buf_position; |
+ ResumeUpload(upload_file_info.Pass(), start_position); |
+} |
+void DriveUploader::ResumeUpload(scoped_ptr<UploadFileInfo> upload_file_info, |
+ int64 start_position) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ int64 end_position = |
+ upload_file_info->buf_position + upload_file_info->buf_size; |
+ DCHECK_GE(start_position, upload_file_info->buf_position); |
+ DCHECK_LE(start_position, end_position); |
+ int64 buffer_offset = start_position - upload_file_info->buf_position; |
+ |
+ // Try to send all the data in the buffer. |
UploadFileInfo* info_ptr = upload_file_info.get(); |
drive_service_->ResumeUpload( |
ResumeUploadParams(info_ptr->upload_mode, |
@@ -341,6 +391,7 @@ void DriveUploader::ReadCompletionCallback( |
info_ptr->content_length, |
info_ptr->content_type, |
info_ptr->buf, |
+ buffer_offset, |
info_ptr->upload_location, |
info_ptr->drive_path), |
base::Bind(&DriveUploader::OnUploadRangeResponseReceived, |
@@ -374,19 +425,30 @@ void DriveUploader::OnUploadRangeResponseReceived( |
return; |
} |
+ if (IsHttp5xxServerError(response.code)) { |
+ // The upload is interrupted. So retry with after waiting for a short |
+ // period. |
+ ResumeInterruptedUpload(upload_file_info.Pass()); |
+ return; |
+ } |
+ |
// If code is 308 (RESUME_INCOMPLETE) and range_received is what has been |
// previously uploaded (i.e. = upload_file_info->end_position), proceed to |
// upload the next chunk. |
if (response.code != HTTP_RESUME_INCOMPLETE || |
response.start_position_received != 0 || |
- response.end_position_received != upload_file_info->next_send_position) { |
+ response.end_position_received < upload_file_info->buf_position || |
+ response.end_position_received > |
+ upload_file_info->buf_position + upload_file_info->buf_size) { |
// TODO(achuith): Handle error cases, e.g. |
// - when previously uploaded data wasn't received by Google Docs server, |
// i.e. when end_position_received < upload_file_info->end_position |
LOG(ERROR) << "UploadNextChunk http code=" << response.code |
<< ", start_position_received=" << response.start_position_received |
<< ", end_position_received=" << response.end_position_received |
- << ", expected end range=" << upload_file_info->next_send_position; |
+ << ", sending buffer = [" << upload_file_info->buf_position |
+ << ":" << upload_file_info->buf_position + upload_file_info->buf_size |
+ << ")"; |
UploadFailed(upload_file_info.Pass(), |
response.code == HTTP_FORBIDDEN ? |
DRIVE_UPLOAD_ERROR_NO_SPACE : DRIVE_UPLOAD_ERROR_ABORT); |
@@ -398,7 +460,56 @@ void DriveUploader::OnUploadRangeResponseReceived( |
<< " for [" << upload_file_info->title << "]"; |
// Continue uploading. |
- UploadNextChunk(upload_file_info.Pass()); |
+ if (response.end_position_received < |
+ upload_file_info->buf_position + upload_file_info->buf_size) { |
+ // There is the remaining data in the buffer, so resend it. |
+ // PostTask is necessary here because we have to finish the callback |
+ // before calling ResumeUpload due to the implementation of |
+ // OperationRegistry. (http://crbug.com/134814) |
+ MessageLoop::current()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&DriveUploader::ResumeUpload, |
+ weak_ptr_factory_.GetWeakPtr(), |
+ base::Passed(&upload_file_info), |
+ response.end_position_received)); |
+ } else { |
+ // The whole data in the current buffer is successfully sent. |
+ // Send the next chunk. |
+ UploadNextChunk(upload_file_info.Pass()); |
+ } |
+} |
+ |
+void DriveUploader::ResumeInterruptedUpload( |
+ scoped_ptr<UploadFileInfo> upload_file_info) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ |
+ // Calculate the waiting duration based on the number of trial. |
+ // See GetWaitDuration for more details. |
+ ++upload_file_info->trial_count; |
+ base::TimeDelta wait_duration = |
+ GetWaitDuration(upload_file_info->trial_count); |
+ |
+ MessageLoop::current()->PostDelayedTask( |
+ FROM_HERE, |
+ base::Bind(&DriveUploader::ResumeInterruptedUploadAfterWait, |
+ weak_ptr_factory_.GetWeakPtr(), |
+ base::Passed(&upload_file_info)), |
+ wait_duration); |
+} |
+ |
+void DriveUploader::ResumeInterruptedUploadAfterWait( |
+ scoped_ptr<UploadFileInfo> upload_file_info) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ |
+ UploadFileInfo* info_ptr = upload_file_info.get(); |
+ drive_service_->GetUploadStatus( |
+ info_ptr->upload_mode, |
+ info_ptr->drive_path, |
+ info_ptr->upload_location, |
+ info_ptr->content_length, |
+ base::Bind(&DriveUploader::OnUploadRangeResponseReceived, |
+ weak_ptr_factory_.GetWeakPtr(), |
+ base::Passed(&upload_file_info))); |
} |
void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info, |