blob: fd67bb4b1c3dbef147761b712f7542263a39e18c [file] [log] [blame]
// Copyright 2019 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/xhr/url_fetcher_buffer_writer.h"
#include "base/logging.h"
#include "net/base/net_errors.h"
#include "starboard/memory.h"
namespace cobalt {
namespace xhr {
namespace {
// Allocate 64KB if the total size is unknown to avoid allocating small buffer
// too many times.
const int64_t kDefaultPreAllocateSizeInBytes = 64 * 1024;
// Set max allocate size to avoid erroneous size estimate.
const int64_t kMaxPreAllocateSizeInBytes = 10 * 1024 * 1024;
const uint8_t kResizingMultiplier = 2;
void ReleaseMemory(std::string* str) {
DCHECK(str);
std::string empty;
str->swap(empty);
}
void ReleaseMemory(script::PreallocatedArrayBufferData* data) {
DCHECK(data);
script::PreallocatedArrayBufferData empty;
data->Swap(&empty);
}
} // namespace
URLFetcherResponseWriter::Buffer::Buffer(Type type) : type_(type) {}
void URLFetcherResponseWriter::Buffer::DisablePreallocate() {
base::AutoLock auto_lock(lock_);
DCHECK_EQ(GetSize_Locked(), 0u);
allow_preallocate_ = false;
}
void URLFetcherResponseWriter::Buffer::Clear() {
base::AutoLock auto_lock(lock_);
ReleaseMemory(&data_as_string_);
ReleaseMemory(&copy_of_data_as_string_);
ReleaseMemory(&data_as_array_buffer_);
download_progress_ = 0;
data_as_array_buffer_size_ = 0;
}
int64_t URLFetcherResponseWriter::Buffer::GetAndResetDownloadProgress() {
base::AutoLock auto_lock(lock_);
download_progress_ = GetSize_Locked();
return static_cast<int64_t>(download_progress_);
}
bool URLFetcherResponseWriter::Buffer::HasProgressSinceLastGetAndReset() const {
base::AutoLock auto_lock(lock_);
return GetSize_Locked() > download_progress_;
}
const std::string&
URLFetcherResponseWriter::Buffer::GetReferenceOfStringAndSeal() {
base::AutoLock auto_lock(lock_);
UpdateType_Locked(kString);
allow_write_ = false;
return data_as_string_;
}
const std::string&
URLFetcherResponseWriter::Buffer::GetTemporaryReferenceOfString() {
base::AutoLock auto_lock(lock_);
// This function can be further optimized by always return reference of
// |data_as_string_|, and only make a copy when |data_as_string_| is extended.
// It is not done as GetTemporaryReferenceOfString() is currently not
// triggered. It will only be called when JS app is retrieving responseText
// while the request is still in progress.
if (type_ == kString) {
copy_of_data_as_string_ = data_as_string_;
} else {
DCHECK_EQ(type_, kArrayBuffer);
const char* begin = static_cast<const char*>(data_as_array_buffer_.data());
copy_of_data_as_string_.assign(begin, begin + data_as_array_buffer_size_);
}
return copy_of_data_as_string_;
}
void URLFetcherResponseWriter::Buffer::GetAndResetDataAndDownloadProgress(
std::string* str) {
DCHECK(str);
ReleaseMemory(str);
base::AutoLock auto_lock(lock_);
UpdateType_Locked(kString);
if (capacity_known_ && data_as_string_.size() != data_as_string_.capacity()) {
DLOG(WARNING) << "String size " << data_as_string_.size()
<< " is different than its preset capacity "
<< data_as_string_.capacity();
}
data_as_string_.swap(*str);
// It is important to reset the |download_progress_| and return the data in
// one function to avoid potential race condition that may prevent the last
// bit of data of Fetcher from being downloaded, because the data download is
// guarded by HasProgressSinceLastGetAndReset().
download_progress_ = 0;
}
void URLFetcherResponseWriter::Buffer::GetAndResetData(
PreallocatedArrayBufferData* data) {
DCHECK(data);
ReleaseMemory(data);
base::AutoLock auto_lock(lock_);
UpdateType_Locked(kArrayBuffer);
if (data_as_array_buffer_.byte_length() != data_as_array_buffer_size_) {
DCHECK_LT(data_as_array_buffer_size_, data_as_array_buffer_.byte_length());
DLOG_IF(WARNING, capacity_known_)
<< "ArrayBuffer size " << data_as_array_buffer_size_
<< " is different than its preset capacity "
<< data_as_array_buffer_.byte_length();
data_as_array_buffer_.Resize(data_as_array_buffer_size_);
}
data_as_array_buffer_.Swap(data);
}
void URLFetcherResponseWriter::Buffer::MaybePreallocate(int64_t capacity) {
base::AutoLock auto_lock(lock_);
if (!allow_preallocate_) {
return;
}
if (capacity < 0) {
capacity = kDefaultPreAllocateSizeInBytes;
} else if (capacity > kMaxPreAllocateSizeInBytes) {
LOG(WARNING) << "Allocation of " << capacity << " bytes is capped to "
<< kMaxPreAllocateSizeInBytes;
capacity = kMaxPreAllocateSizeInBytes;
} else {
capacity_known_ = true;
}
// Record the desired_capacity_ to avoid reserving unused memory during
// resizing.
desired_capacity_ = static_cast<size_t>(capacity);
if (capacity == 0) {
return;
}
switch (type_) {
case kString:
DCHECK_EQ(data_as_string_.size(), 0u);
data_as_string_.reserve(capacity);
return;
case kArrayBuffer:
DCHECK_EQ(data_as_array_buffer_size_, 0u);
data_as_array_buffer_.Resize(capacity);
return;
}
NOTREACHED();
}
void URLFetcherResponseWriter::Buffer::Write(const void* buffer,
int num_bytes) {
DCHECK_GE(num_bytes, 0);
if (num_bytes <= 0) {
return;
}
base::AutoLock auto_lock(lock_);
DCHECK(allow_write_);
if (!allow_write_) {
return;
}
if (type_ == kString) {
if (capacity_known_ &&
num_bytes + data_as_string_.size() >= data_as_string_.capacity()) {
SB_LOG(WARNING) << "Data written is larger than the preset capacity "
<< data_as_string_.capacity();
}
data_as_string_.append(static_cast<const char*>(buffer), num_bytes);
return;
}
DCHECK_EQ(type_, kArrayBuffer);
if (data_as_array_buffer_size_ + num_bytes >
data_as_array_buffer_.byte_length()) {
if (capacity_known_) {
SB_LOG(WARNING) << "Data written is larger than the preset capacity "
<< data_as_array_buffer_.byte_length();
}
size_t new_size = std::max(
std::min(data_as_array_buffer_.byte_length() * kResizingMultiplier,
desired_capacity_),
data_as_array_buffer_size_ + num_bytes);
if (new_size > desired_capacity_) {
// Content-length is wrong, response size is completely unknown.
// Double the capacity to avoid frequent resizing.
new_size *= kResizingMultiplier;
}
data_as_array_buffer_.Resize(new_size);
}
auto destination = static_cast<uint8_t*>(data_as_array_buffer_.data()) +
data_as_array_buffer_size_;
SbMemoryCopy(destination, buffer, num_bytes);
data_as_array_buffer_size_ += num_bytes;
}
size_t URLFetcherResponseWriter::Buffer::GetSize_Locked() const {
lock_.AssertAcquired();
switch (type_) {
case kString:
return data_as_string_.size();
case kArrayBuffer:
return data_as_array_buffer_size_;
}
NOTREACHED();
return 0;
}
void URLFetcherResponseWriter::Buffer::UpdateType_Locked(Type type) {
lock_.AssertAcquired();
if (type_ == type) {
return;
}
DCHECK(allow_write_);
DLOG_IF(WARNING, GetSize_Locked() > 0)
<< "Change response type from " << type_ << " to " << type
<< " after response is started, which is less efficient.";
if (type_ == kString) {
DCHECK_EQ(type, kArrayBuffer);
DCHECK_EQ(data_as_array_buffer_size_, 0u);
DCHECK_EQ(data_as_array_buffer_.byte_length(), 0u);
} else {
DCHECK_EQ(type_, kArrayBuffer);
DCHECK_EQ(type, kString);
DCHECK_EQ(data_as_string_.size(), 0u);
}
type_ = type;
if (type == kArrayBuffer) {
data_as_array_buffer_.Resize(data_as_string_.capacity());
data_as_array_buffer_size_ = data_as_string_.size();
SbMemoryCopy(data_as_array_buffer_.data(), data_as_string_.data(),
data_as_array_buffer_size_);
ReleaseMemory(&data_as_string_);
ReleaseMemory(&copy_of_data_as_string_);
return;
}
data_as_string_.reserve(data_as_array_buffer_.byte_length());
data_as_string_.append(static_cast<const char*>(data_as_array_buffer_.data()),
data_as_array_buffer_size_);
ReleaseMemory(&data_as_array_buffer_);
data_as_array_buffer_size_ = 0;
}
URLFetcherResponseWriter::URLFetcherResponseWriter(
const scoped_refptr<Buffer>& buffer)
: buffer_(buffer) {
DCHECK(buffer_);
}
URLFetcherResponseWriter::~URLFetcherResponseWriter() = default;
int URLFetcherResponseWriter::Initialize(net::CompletionOnceCallback callback) {
return net::OK;
}
void URLFetcherResponseWriter::OnResponseStarted(int64_t content_length) {
buffer_->MaybePreallocate(content_length);
}
int URLFetcherResponseWriter::Write(net::IOBuffer* buffer, int num_bytes,
net::CompletionOnceCallback callback) {
buffer_->Write(buffer->data(), num_bytes);
return num_bytes;
}
int URLFetcherResponseWriter::Finish(int net_error,
net::CompletionOnceCallback callback) {
return net::OK;
}
} // namespace xhr
} // namespace cobalt