Commit af294a5a authored by Min Qin's avatar Min Qin Committed by Commit Bot

Allow each download stream to take some data for validation purpose

Currently for each download stream, the data read from the network
was immediately written to the disk.
This CL allows each SourceStream to read some data before the file
write offset for content validation purpose. And data will only be
written after all the initial validation data are consumed.
A new field called starting_file_write_offset_ is added to SourceStream
to identify this checkpoint for file validation.

BUG=965215

Change-Id: Ia46de1a588bc0c2fb4e6eb19e62ef794d706ad1d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1625461
Commit-Queue: Min Qin <qinmin@chromium.org>
Reviewed-by: default avatarXing Liu <xingliu@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#664030}
parent 918987af
...@@ -102,7 +102,7 @@ bool CanRecoverFromError( ...@@ -102,7 +102,7 @@ bool CanRecoverFromError(
// the error stream. // the error stream.
if (error_stream->length() > 0) { if (error_stream->length() > 0) {
return error_stream->offset() + error_stream->length() <= return error_stream->offset() + error_stream->length() <=
preceding_neighbor->offset() + preceding_neighbor->bytes_written(); preceding_neighbor->offset() + preceding_neighbor->bytes_read();
} }
return false; return false;
......
...@@ -43,7 +43,8 @@ class ParallelDownloadUtilsRecoverErrorTest ...@@ -43,7 +43,8 @@ class ParallelDownloadUtilsRecoverErrorTest
EXPECT_CALL(*input_stream_, GetCompletionStatus()) EXPECT_CALL(*input_stream_, GetCompletionStatus())
.WillRepeatedly(Return(DOWNLOAD_INTERRUPT_REASON_NONE)); .WillRepeatedly(Return(DOWNLOAD_INTERRUPT_REASON_NONE));
return std::make_unique<DownloadFileImpl::SourceStream>( return std::make_unique<DownloadFileImpl::SourceStream>(
offset, length, std::unique_ptr<MockInputStream>(input_stream_)); offset, length, offset,
std::unique_ptr<MockInputStream>(input_stream_));
} }
protected: protected:
...@@ -166,7 +167,7 @@ TEST_P(ParallelDownloadUtilsRecoverErrorTest, ...@@ -166,7 +167,7 @@ TEST_P(ParallelDownloadUtilsRecoverErrorTest,
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
// Even if it has written some data. // Even if it has written some data.
preceding_stream->OnWriteBytesToDisk(1000u); preceding_stream->OnBytesConsumed(1000u, 1000u);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
// Now capped the length of preceding stream with different values. // Now capped the length of preceding stream with different values.
...@@ -177,14 +178,15 @@ TEST_P(ParallelDownloadUtilsRecoverErrorTest, ...@@ -177,14 +178,15 @@ TEST_P(ParallelDownloadUtilsRecoverErrorTest,
preceding_stream->set_finished(false); preceding_stream->set_finished(false);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
preceding_stream->set_finished(true); preceding_stream->set_finished(true);
preceding_stream->OnWriteBytesToDisk(kErrorStreamOffset - preceding_offset); int64_t bytes_consumed = kErrorStreamOffset - preceding_offset;
preceding_stream->OnBytesConsumed(bytes_consumed, bytes_consumed);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
// Inject an error results in failure, even if data written exceeds the first // Inject an error results in failure, even if data written exceeds the first
// byte of error stream. // byte of error stream.
EXPECT_CALL(*input_stream_, GetCompletionStatus()) EXPECT_CALL(*input_stream_, GetCompletionStatus())
.WillRepeatedly(Return(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE)); .WillRepeatedly(Return(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE));
preceding_stream->OnWriteBytesToDisk(1000u); preceding_stream->OnBytesConsumed(1000u, 1000u);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
// Make preceding stream can reach the first byte of error stream. // Make preceding stream can reach the first byte of error stream.
...@@ -194,9 +196,9 @@ TEST_P(ParallelDownloadUtilsRecoverErrorTest, ...@@ -194,9 +196,9 @@ TEST_P(ParallelDownloadUtilsRecoverErrorTest,
preceding_stream->set_finished(false); preceding_stream->set_finished(false);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
preceding_stream->set_finished(true); preceding_stream->set_finished(true);
preceding_stream->OnWriteBytesToDisk(kErrorStreamOffset - preceding_offset); preceding_stream->OnBytesConsumed(bytes_consumed, bytes_consumed);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
preceding_stream->OnWriteBytesToDisk(1); preceding_stream->OnBytesConsumed(1, 1);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
// Preceding stream that never download data won't recover the error stream. // Preceding stream that never download data won't recover the error stream.
...@@ -229,11 +231,13 @@ TEST_P(ParallelDownloadUtilsRecoverErrorTest, ...@@ -229,11 +231,13 @@ TEST_P(ParallelDownloadUtilsRecoverErrorTest,
// Since the preceding stream can never reach the starting offset, for an // Since the preceding stream can never reach the starting offset, for an
// unfinished stream, we rely on length instead of bytes written. // unfinished stream, we rely on length instead of bytes written.
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
preceding_stream->OnWriteBytesToDisk(kErrorStreamOffset - preceding_offset); int64_t bytes_consumed = kErrorStreamOffset - preceding_offset;
preceding_stream->OnBytesConsumed(bytes_consumed, bytes_consumed);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
preceding_stream->OnWriteBytesToDisk(kErrorStreamLength - 1); preceding_stream->OnBytesConsumed(kErrorStreamLength - 1,
kErrorStreamLength - 1);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
preceding_stream->OnWriteBytesToDisk(1); preceding_stream->OnBytesConsumed(1, 1);
// Create preceding stream that can reach the upper bound of error stream. // Create preceding stream that can reach the upper bound of error stream.
// Since it's unfinished, it potentially can take over error stream's work // Since it's unfinished, it potentially can take over error stream's work
...@@ -248,11 +252,12 @@ TEST_P(ParallelDownloadUtilsRecoverErrorTest, ...@@ -248,11 +252,12 @@ TEST_P(ParallelDownloadUtilsRecoverErrorTest,
// Finished preceding stream only checks data written. // Finished preceding stream only checks data written.
preceding_stream = CreateSourceStream(preceding_offset, 1); preceding_stream = CreateSourceStream(preceding_offset, 1);
preceding_stream->set_finished(true); preceding_stream->set_finished(true);
preceding_stream->OnWriteBytesToDisk(kErrorStreamOffset - preceding_offset); preceding_stream->OnBytesConsumed(bytes_consumed, bytes_consumed);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
preceding_stream->OnWriteBytesToDisk(kErrorStreamLength - 1); preceding_stream->OnBytesConsumed(kErrorStreamLength - 1,
kErrorStreamLength - 1);
EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
preceding_stream->OnWriteBytesToDisk(1); preceding_stream->OnBytesConsumed(1, 1);
EXPECT_TRUE(CanRecoverFromError(error_stream.get(), preceding_stream.get())); EXPECT_TRUE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
// Even if inject an error, since data written has cover the upper bound of // Even if inject an error, since data written has cover the upper bound of
......
...@@ -99,17 +99,19 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadFileImpl : public DownloadFile { ...@@ -99,17 +99,19 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadFileImpl : public DownloadFile {
public: public:
SourceStream(int64_t offset, SourceStream(int64_t offset,
int64_t length, int64_t length,
int64_t starting_file_write_offset,
std::unique_ptr<InputStream> stream); std::unique_ptr<InputStream> stream);
~SourceStream(); ~SourceStream();
void Initialize(); void Initialize();
// Called after successfully writing a buffer to disk. // Called after successfully reading and writing a buffer from stream.
void OnWriteBytesToDisk(int64_t bytes_write); void OnBytesConsumed(int64_t bytes_read, int64_t bytes_written);
// Given a data block that is already written, truncate the length of this // Given a data block that is already written, truncate the length of this
// object to avoid overwriting that block. // object to avoid overwriting that block. Data used for validation purpose
void TruncateLengthWithWrittenDataBlock(int64_t offset, // will not be truncated.
void TruncateLengthWithWrittenDataBlock(int64_t received_slice_offset,
int64_t bytes_written); int64_t bytes_written);
// Registers the callback that will be called when data is ready. // Registers the callback that will be called when data is ready.
...@@ -136,8 +138,15 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadFileImpl : public DownloadFile { ...@@ -136,8 +138,15 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadFileImpl : public DownloadFile {
InputStream::StreamState Read(scoped_refptr<net::IOBuffer>* data, InputStream::StreamState Read(scoped_refptr<net::IOBuffer>* data,
size_t* length); size_t* length);
// Returning the remaining bytes to validate.
size_t GetRemainingBytesToValidate();
int64_t offset() const { return offset_; } int64_t offset() const { return offset_; }
int64_t length() const { return length_; } int64_t length() const { return length_; }
int64_t starting_file_write_offset() const {
return starting_file_write_offset_;
}
int64_t bytes_read() const { return bytes_read_; }
int64_t bytes_written() const { return bytes_written_; } int64_t bytes_written() const { return bytes_written_; }
bool is_finished() const { return finished_; } bool is_finished() const { return finished_; }
void set_finished(bool finish) { finished_ = finish; } void set_finished(bool finish) { finished_ = finish; }
...@@ -145,15 +154,24 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadFileImpl : public DownloadFile { ...@@ -145,15 +154,24 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadFileImpl : public DownloadFile {
void set_index(size_t index) { index_ = index; } void set_index(size_t index) { index_ = index; }
private: private:
// Starting position for the stream to write to disk. // Starting position of the stream, this is from the network response.
int64_t offset_; int64_t offset_;
// The maximum length to write to the disk. If set to 0, keep writing until // The maximum length to write to the disk. If set to 0, keep writing until
// the stream depletes. // the stream depletes.
int64_t length_; int64_t length_;
// Number of bytes written to disk from the stream. // All the data received before this offset are used for validation purpose
// Next write position is (|offset_| + |bytes_written_|). // and will not be written to disk. This value should always be no less than
// |offset_|.
int64_t starting_file_write_offset_;
// Number of bytes read from the stream.
// Next read position is (|offset_| + |bytes_read_|).
int64_t bytes_read_;
// Number of bytes written to the disk. This does not include the bytes used
// for validation.
int64_t bytes_written_; int64_t bytes_written_;
// If all the data read from the stream has been successfully written to // If all the data read from the stream has been successfully written to
...@@ -172,11 +190,13 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadFileImpl : public DownloadFile { ...@@ -172,11 +190,13 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadFileImpl : public DownloadFile {
protected: protected:
// For test class overrides. // For test class overrides.
// Write data from the offset to the file. // Validate the first |bytes_to_validate| bytes and write the next
// On OS level, it will seek to the |offset| and write from there. // |bytes_to_write| bytes of data from the offset to the file.
virtual DownloadInterruptReason WriteDataToFile(int64_t offset, virtual DownloadInterruptReason ValidateAndWriteDataToFile(
const char* data, int64_t offset,
size_t data_len); const char* data,
size_t bytes_to_validate,
size_t bytes_to_write);
virtual base::TimeDelta GetRetryDelayForFailedRename(int attempt_number); virtual base::TimeDelta GetRetryDelayForFailedRename(int attempt_number);
...@@ -232,13 +252,14 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadFileImpl : public DownloadFile { ...@@ -232,13 +252,14 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadFileImpl : public DownloadFile {
void WillWriteToDisk(size_t data_len); void WillWriteToDisk(size_t data_len);
// For a given SourceStream object and the bytes available to write, determine // For a given SourceStream object and the bytes available to write, determine
// the actual number of bytes it can write to the disk. For parallel // the number of bytes to validate and the number of bytes it can write to the
// downloading, if the first disk IO writes to a location that is already // disk. For parallel downloading, if the first disk IO writes to a location
// written by another stream, the current stream should stop writing. Returns // that is already written by another stream, the current stream should stop
// true if the stream can write no more data and should be finished, returns // writing. Returns true if the stream can write no more data and should be
// false otherwise. // finished, returns false otherwise.
bool CalculateBytesToWrite(SourceStream* source_stream, bool CalculateBytesToWrite(SourceStream* source_stream,
size_t bytes_available_to_write, size_t bytes_available_to_write,
size_t* bytes_to_validate,
size_t* bytes_to_write); size_t* bytes_to_write);
// Called when a new SourceStream object is added. // Called when a new SourceStream object is added.
......
...@@ -15,4 +15,8 @@ DownloadSaveInfo::~DownloadSaveInfo() = default; ...@@ -15,4 +15,8 @@ DownloadSaveInfo::~DownloadSaveInfo() = default;
DownloadSaveInfo::DownloadSaveInfo(DownloadSaveInfo&& that) = default; DownloadSaveInfo::DownloadSaveInfo(DownloadSaveInfo&& that) = default;
int64_t DownloadSaveInfo::GetStartingFileWriteOffset() {
return file_offset >= 0 ? file_offset : offset;
}
} // namespace download } // namespace download
...@@ -30,6 +30,8 @@ struct COMPONENTS_DOWNLOAD_EXPORT DownloadSaveInfo { ...@@ -30,6 +30,8 @@ struct COMPONENTS_DOWNLOAD_EXPORT DownloadSaveInfo {
~DownloadSaveInfo(); ~DownloadSaveInfo();
DownloadSaveInfo(DownloadSaveInfo&& that); DownloadSaveInfo(DownloadSaveInfo&& that);
int64_t GetStartingFileWriteOffset();
// If non-empty, contains the full target path of the download that has been // If non-empty, contains the full target path of the download that has been
// determined prior to download initiation. This is considered to be a trusted // determined prior to download initiation. This is considered to be a trusted
// path. // path.
...@@ -42,9 +44,18 @@ struct COMPONENTS_DOWNLOAD_EXPORT DownloadSaveInfo { ...@@ -42,9 +44,18 @@ struct COMPONENTS_DOWNLOAD_EXPORT DownloadSaveInfo {
// If valid, contains the source data stream for the file contents. // If valid, contains the source data stream for the file contents.
base::File file; base::File file;
// The file offset at which to start the download. // The offset sent to the server when requesting the download. During
// resumption, |offset| could be smaller than the downloaded content length.
// This is because download may request some data to validate whether the
// content has changed.
int64_t offset = 0; int64_t offset = 0;
// The file offset to start writing to disk. If this value is negative,
// download stream will be writing to the disk starting at |offset|.
// Otherwise, this value will be used. Data received before |file_offset| are
// used for validation purpose.
int64_t file_offset = -1;
// The number of the bytes to download from |offset|. // The number of the bytes to download from |offset|.
// Ask to retrieve segment of the download file when length is greater than 0. // Ask to retrieve segment of the download file when length is greater than 0.
// Request the rest of the file starting from |offset|, when length is // Request the rest of the file starting from |offset|, when length is
......
...@@ -52,9 +52,11 @@ class DownloadFileWithError : public download::DownloadFileImpl { ...@@ -52,9 +52,11 @@ class DownloadFileWithError : public download::DownloadFileImpl {
bool is_parallelizable) override; bool is_parallelizable) override;
// DownloadFile interface. // DownloadFile interface.
download::DownloadInterruptReason WriteDataToFile(int64_t offset, download::DownloadInterruptReason ValidateAndWriteDataToFile(
const char* data, int64_t offset,
size_t data_len) override; const char* data,
size_t bytes_to_validate,
size_t bytes_to_write) override;
download::DownloadInterruptReason HandleStreamCompletionStatus( download::DownloadInterruptReason HandleStreamCompletionStatus(
SourceStream* source_stream) override; SourceStream* source_stream) override;
...@@ -174,13 +176,15 @@ void DownloadFileWithError::Initialize( ...@@ -174,13 +176,15 @@ void DownloadFileWithError::Initialize(
received_slices, is_parallelizable); received_slices, is_parallelizable);
} }
download::DownloadInterruptReason DownloadFileWithError::WriteDataToFile( download::DownloadInterruptReason
int64_t offset, DownloadFileWithError::ValidateAndWriteDataToFile(int64_t offset,
const char* data, const char* data,
size_t data_len) { size_t bytes_to_validate,
size_t bytes_to_write) {
return ShouldReturnError( return ShouldReturnError(
TestFileErrorInjector::FILE_OPERATION_WRITE, TestFileErrorInjector::FILE_OPERATION_WRITE,
download::DownloadFileImpl::WriteDataToFile(offset, data, data_len)); download::DownloadFileImpl::ValidateAndWriteDataToFile(
offset, data, bytes_to_validate, bytes_to_write));
} }
download::DownloadInterruptReason download::DownloadInterruptReason
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment