| Index: content/browser/download/download_file_impl.cc
|
| diff --git a/content/browser/download/download_file_impl.cc b/content/browser/download/download_file_impl.cc
|
| index 11e98cb726bc6bfc566686bdd281647cb8ebf7c8..78fe15face7cf43a30db54e5c400cfbcb751f2aa 100644
|
| --- a/content/browser/download/download_file_impl.cc
|
| +++ b/content/browser/download/download_file_impl.cc
|
| @@ -25,6 +25,12 @@ namespace content {
|
| const int kUpdatePeriodMs = 500;
|
| const int kMaxTimeBlockingFileThreadMs = 1000;
|
|
|
| +// These constants control the default retry behavior for failing renames. Each
|
| +// retry is performed after a delay that is twice the previous delay. The
|
| +// initial delay is specified by kInitialRenameRetryDelayMs.
|
| +const int kMaxRenameRetries = 3;
|
| +const int kInitialRenameRetryDelayMs = 200;
|
| +
|
| int DownloadFile::number_active_objects_ = 0;
|
|
|
| DownloadFileImpl::DownloadFileImpl(
|
| @@ -36,20 +42,20 @@ DownloadFileImpl::DownloadFileImpl(
|
| scoped_ptr<ByteStreamReader> stream,
|
| const net::BoundNetLog& bound_net_log,
|
| base::WeakPtr<DownloadDestinationObserver> observer)
|
| - : file_(save_info->file_path,
|
| - url,
|
| - referrer_url,
|
| - save_info->offset,
|
| - calculate_hash,
|
| - save_info->hash_state,
|
| - save_info->file.Pass(),
|
| - bound_net_log),
|
| - default_download_directory_(default_download_directory),
|
| - stream_reader_(stream.Pass()),
|
| - bytes_seen_(0),
|
| - bound_net_log_(bound_net_log),
|
| - observer_(observer),
|
| - weak_factory_(this) {
|
| + : file_(save_info->file_path,
|
| + url,
|
| + referrer_url,
|
| + save_info->offset,
|
| + calculate_hash,
|
| + save_info->hash_state,
|
| + save_info->file.Pass(),
|
| + bound_net_log),
|
| + default_download_directory_(default_download_directory),
|
| + stream_reader_(stream.Pass()),
|
| + bytes_seen_(0),
|
| + bound_net_log_(bound_net_log),
|
| + observer_(observer),
|
| + weak_factory_(this) {
|
| }
|
|
|
| DownloadFileImpl::~DownloadFileImpl() {
|
| @@ -103,47 +109,82 @@ DownloadInterruptReason DownloadFileImpl::AppendDataToFile(
|
| void DownloadFileImpl::RenameAndUniquify(
|
| const base::FilePath& full_path,
|
| const RenameCompletionCallback& callback) {
|
| - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
| -
|
| - base::FilePath new_path(full_path);
|
| -
|
| - int uniquifier = base::GetUniquePathNumber(
|
| - new_path, base::FilePath::StringType());
|
| - if (uniquifier > 0) {
|
| - new_path = new_path.InsertBeforeExtensionASCII(
|
| - base::StringPrintf(" (%d)", uniquifier));
|
| - }
|
| -
|
| - DownloadInterruptReason reason = file_.Rename(new_path);
|
| - if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
|
| - // Make sure our information is updated, since we're about to
|
| - // error out.
|
| - SendUpdate();
|
| + RenameWithRetryInternal(
|
| + full_path, UNIQUIFY, kMaxRenameRetries, base::TimeTicks(), callback);
|
| +}
|
|
|
| - // Null out callback so that we don't do any more stream processing.
|
| - stream_reader_->RegisterCallback(base::Closure());
|
| +void DownloadFileImpl::RenameAndAnnotate(
|
| + const base::FilePath& full_path,
|
| + const RenameCompletionCallback& callback) {
|
| + RenameWithRetryInternal(full_path,
|
| + ANNOTATE_WITH_SOURCE_INFORMATION,
|
| + kMaxRenameRetries,
|
| + base::TimeTicks(),
|
| + callback);
|
| +}
|
|
|
| - new_path.clear();
|
| - }
|
| +base::TimeDelta DownloadFileImpl::GetRetryDelayForFailedRename(
|
| + int attempt_number) {
|
| + DCHECK_GE(attempt_number, 0);
|
| + // |delay| starts at kInitialRenameRetryDelayMs and increases by a factor of
|
| + // 2 at each subsequent retry. Assumes that |retries_left| starts at
|
| + // kMaxRenameRetries. Also assumes that kMaxRenameRetries is less than the
|
| + // number of bits in an int.
|
| + return base::TimeDelta::FromMilliseconds(kInitialRenameRetryDelayMs) *
|
| + (1 << attempt_number);
|
| +}
|
|
|
| - BrowserThread::PostTask(
|
| - BrowserThread::UI, FROM_HERE,
|
| - base::Bind(callback, reason, new_path));
|
| +bool DownloadFileImpl::ShouldRetryFailedRename(DownloadInterruptReason reason) {
|
| + return reason == DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
|
| }
|
|
|
| -void DownloadFileImpl::RenameAndAnnotate(
|
| +void DownloadFileImpl::RenameWithRetryInternal(
|
| const base::FilePath& full_path,
|
| + RenameOption option,
|
| + int retries_left,
|
| + base::TimeTicks time_of_first_failure,
|
| const RenameCompletionCallback& callback) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
|
|
| base::FilePath new_path(full_path);
|
|
|
| - DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE;
|
| - // Short circuit null rename.
|
| - if (full_path != file_.full_path())
|
| - reason = file_.Rename(new_path);
|
| + if ((option & UNIQUIFY) && full_path != file_.full_path()) {
|
| + int uniquifier =
|
| + base::GetUniquePathNumber(new_path, base::FilePath::StringType());
|
| + if (uniquifier > 0)
|
| + new_path = new_path.InsertBeforeExtensionASCII(
|
| + base::StringPrintf(" (%d)", uniquifier));
|
| + }
|
| +
|
| + DownloadInterruptReason reason = file_.Rename(new_path);
|
| +
|
| + // Attempt to retry the rename if possible. If the rename failed and the
|
| + // subsequent open also failed, then in_progress() would be false. We don't
|
| + // try to retry renames if the in_progress() was false to begin with since we
|
| + // have less assurance that the file at file_.full_path() was the one we were
|
| + // working with.
|
| + if (ShouldRetryFailedRename(reason) && file_.in_progress() &&
|
| + retries_left > 0) {
|
| + int attempt_number = kMaxRenameRetries - retries_left;
|
| + BrowserThread::PostDelayedTask(
|
| + BrowserThread::FILE,
|
| + FROM_HERE,
|
| + base::Bind(&DownloadFileImpl::RenameWithRetryInternal,
|
| + weak_factory_.GetWeakPtr(),
|
| + full_path,
|
| + option,
|
| + --retries_left,
|
| + time_of_first_failure.is_null() ? base::TimeTicks::Now()
|
| + : time_of_first_failure,
|
| + callback),
|
| + GetRetryDelayForFailedRename(attempt_number));
|
| + return;
|
| + }
|
| +
|
| + RecordDownloadFileRenameResult(time_of_first_failure, reason);
|
|
|
| - if (reason == DOWNLOAD_INTERRUPT_REASON_NONE) {
|
| + if (reason == DOWNLOAD_INTERRUPT_REASON_NONE &&
|
| + (option & ANNOTATE_WITH_SOURCE_INFORMATION)) {
|
| // Doing the annotation after the rename rather than before leaves
|
| // a very small window during which the file has the final name but
|
| // hasn't been marked with the Mark Of The Web. However, it allows
|
|
|