summaryrefslogtreecommitdiffstats
path: root/courgette/memory_allocator.h
diff options
context:
space:
mode:
authortommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-06 17:42:45 +0000
committertommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-06 17:42:45 +0000
commit43a9e24a52fa867db98fc2195b8db85b4729e7a1 (patch)
treecad7a14c415542083ac3d1ecc2df1fd86f789557 /courgette/memory_allocator.h
parent6768ac0b06ae36ec7b2d27adfb9b64fc17f57cee (diff)
downloadchromium_src-43a9e24a52fa867db98fc2195b8db85b4729e7a1.zip
chromium_src-43a9e24a52fa867db98fc2195b8db85b4729e7a1.tar.gz
chromium_src-43a9e24a52fa867db98fc2195b8db85b4729e7a1.tar.bz2
Switch out use of std::string and std::vector for large allocations for a buffer class that doesn't throw exceptions.
The new buffer class is pretty simple and relies on the MemoryAllocator class that I previously to back large allocations with mapped files when memory is scarce. That reduced the number of crashes quite a bit but we still crash on machines that are simply out of diskspace as well. So, the right thing to do is to expect and handle failures which is what this cl is all about. What we should see once this has landed is that crash dumps due to courgette running out of disk space should disappear from crash/ and instead we should see the number of users that run into this particular problem in dashboards. TEST=Courgette out-of-memory/out-of-diskspace errors should disappear from crash/ BUG=74777 Review URL: http://codereview.chromium.org/6677141 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80648 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'courgette/memory_allocator.h')
-rw-r--r--courgette/memory_allocator.h281
1 files changed, 252 insertions, 29 deletions
diff --git a/courgette/memory_allocator.h b/courgette/memory_allocator.h
index 4774b88..08defe4 100644
--- a/courgette/memory_allocator.h
+++ b/courgette/memory_allocator.h
@@ -66,9 +66,9 @@ class TempFile {
TempFile();
~TempFile();
- __declspec(noinline) void Create();
+ bool Create();
void Close();
- __declspec(noinline) void SetSize(size_t size);
+ bool SetSize(size_t size);
// Returns true iff the temp file is currently open.
bool valid() const;
@@ -77,15 +77,8 @@ class TempFile {
// a temp file has not been created.
base::PlatformFile handle() const;
- // Returns the size of the temp file. If the temp file doesn't exist,
- // the return value is 0.
- size_t size() const;
-
protected:
- __declspec(noinline) FilePath PrepareTempFile();
-
base::PlatformFile file_;
- size_t size_;
};
// Manages a read/write virtual mapping of a physical file.
@@ -95,7 +88,7 @@ class FileMapping {
~FileMapping();
// Map a file from beginning to |size|.
- __declspec(noinline) void Create(HANDLE file, size_t size);
+ bool Create(HANDLE file, size_t size);
void Close();
// Returns true iff a mapping has been created.
@@ -106,7 +99,7 @@ class FileMapping {
void* view() const;
protected:
- __declspec(noinline) void InitializeView(size_t size);
+ bool InitializeView(size_t size);
HANDLE mapping_;
void* view_;
@@ -122,12 +115,15 @@ class TempMapping {
~TempMapping();
// Creates a temporary file of size |size| and maps it into the current
- // process' address space.
- __declspec(noinline) void Initialize(size_t size);
+ // process's address space.
+ bool Initialize(size_t size);
// Returns a writable pointer to the reserved memory.
void* memory() const;
+ // Returns true if the mapping is valid and memory is available.
+ bool valid() const;
+
// Returns a pointer to the TempMapping instance that allocated the |mem|
// block of memory. It's the callers responsibility to make sure that
// the memory block was allocated by the TempMapping class.
@@ -138,10 +134,11 @@ class TempMapping {
FileMapping mapping_;
};
-// An STL compatible memory allocator class that allocates memory either
-// from the heap or via a temporary file. A file allocation will be made
-// if either the requested memory size exceeds |kMaxHeapAllocationSize|
-// or if a heap allocation fails.
+// A memory allocator class that allocates memory either from the heap or via a
+// temporary file. The interface is STL inspired but the class does not throw
+// STL exceptions on allocation failure. Instead it returns NULL.
+// A file allocation will be made if either the requested memory size exceeds
+// |kMaxHeapAllocationSize| or if a heap allocation fails.
// Allocating the memory as a mapping of a temporary file solves the problem
// that there might not be enough physical memory and pagefile to support the
// allocation. This can happen because these resources are too small, or
@@ -174,7 +171,7 @@ class MemoryAllocator {
template<class OtherT>
struct rebind {
- // convert an MemoryAllocator<T> to a MemoryAllocator<OtherT>
+ // convert a MemoryAllocator<T> to a MemoryAllocator<OtherT>
typedef MemoryAllocator<OtherT> other;
};
@@ -183,11 +180,11 @@ class MemoryAllocator {
// We can't use an explicit constructor here, as dictated by our style guide.
// The implementation of basic_string in Visual Studio 2010 prevents this.
- MemoryAllocator(const MemoryAllocator<T>& other) _THROW0() {
+ MemoryAllocator(const MemoryAllocator<T>& other) _THROW0() { // NOLINT
}
template<class OtherT>
- explicit MemoryAllocator(const MemoryAllocator<OtherT>& other) _THROW0() {
+ MemoryAllocator(const MemoryAllocator<OtherT>& other) _THROW0() { // NOLINT
}
~MemoryAllocator() {
@@ -213,7 +210,7 @@ class MemoryAllocator {
count++;
if (count > max_size())
- throw std::length_error("overflow");
+ return NULL;
size_type bytes = count * sizeof(T);
uint8* mem = NULL;
@@ -226,12 +223,13 @@ class MemoryAllocator {
} else {
// If either the heap allocation failed or the request exceeds the
// max heap allocation threshold, we back the allocation with a temp file.
- TempMapping* mapping = new TempMapping();
- mapping->Initialize(bytes);
- mem = reinterpret_cast<uint8*>(mapping->memory());
- mem[0] = static_cast<uint8>(FILE_ALLOCATION);
+ TempMapping* mapping = new(std::nothrow) TempMapping();
+ if (mapping && mapping->Initialize(bytes)) {
+ mem = reinterpret_cast<uint8*>(mapping->memory());
+ mem[0] = static_cast<uint8>(FILE_ALLOCATION);
+ }
}
- return reinterpret_cast<pointer>(mem + sizeof(T));
+ return mem ? reinterpret_cast<pointer>(mem + sizeof(T)) : NULL;
}
pointer allocate(size_type count, const void* hint) {
@@ -246,7 +244,7 @@ class MemoryAllocator {
ptr->~T();
}
- size_t max_size() const _THROW0() {
+ size_type max_size() const _THROW0() {
size_type count = static_cast<size_type>(-1) / sizeof(T);
return (0 < count ? count : 1);
}
@@ -254,14 +252,239 @@ class MemoryAllocator {
#else // OS_WIN
-// On Mac, Linux, we just use the default STL allocator.
+// On Mac, Linux, we use a bare bones implementation that only does
+// heap allocations.
template<class T>
-class MemoryAllocator : public std::allocator<T> {
+class MemoryAllocator {
public:
+ typedef T value_type;
+ typedef value_type* pointer;
+ typedef value_type& reference;
+ typedef const value_type* const_pointer;
+ typedef const value_type& const_reference;
+ typedef size_t size_type;
+ typedef ptrdiff_t difference_type;
+
+ template<class OtherT>
+ struct rebind {
+ // convert a MemoryAllocator<T> to a MemoryAllocator<OtherT>
+ typedef MemoryAllocator<OtherT> other;
+ };
+
+ MemoryAllocator() {
+ }
+
+ explicit MemoryAllocator(const MemoryAllocator<T>& other) {
+ }
+
+ template<class OtherT>
+ explicit MemoryAllocator(const MemoryAllocator<OtherT>& other) {
+ }
+
+ ~MemoryAllocator() {
+ }
+
+ void deallocate(pointer ptr, size_type size) {
+ delete [] ptr;
+ }
+
+ pointer allocate(size_type count) {
+ if (count > max_size())
+ return NULL;
+ return reinterpret_cast<pointer>(
+ new(std::nothrow) uint8[count * sizeof(T)]);
+ }
+
+ pointer allocate(size_type count, const void* hint) {
+ return allocate(count);
+ }
+
+ void construct(pointer ptr, const T& value) {
+ ::new(ptr) T(value);
+ }
+
+ void destroy(pointer ptr) {
+ ptr->~T();
+ }
+
+ size_type max_size() const {
+ size_type count = static_cast<size_type>(-1) / sizeof(T);
+ return (0 < count ? count : 1);
+ }
};
#endif // OS_WIN
+// Manages a growable buffer. The buffer allocation is done by the
+// MemoryAllocator class. This class will not throw exceptions so call sites
+// must be prepared to handle memory allocation failures.
+// The interface is STL inspired to avoid having to make too many changes
+// to code that previously was using STL.
+template<typename T, class Allocator = MemoryAllocator<T> >
+class NoThrowBuffer {
+ public:
+ typedef T value_type;
+ static const size_t kAllocationFailure = 0xffffffff;
+ static const size_t kStartSize = sizeof(T) > 0x100 ? 1 : 0x100 / sizeof(T);
+
+ NoThrowBuffer() : buffer_(NULL), size_(0), alloc_size_(0) {
+ }
+
+ ~NoThrowBuffer() {
+ clear();
+ }
+
+ void clear() {
+ if (buffer_) {
+ alloc_.deallocate(buffer_, alloc_size_);
+ buffer_ = NULL;
+ size_ = 0;
+ alloc_size_ = 0;
+ }
+ }
+
+ bool empty() const {
+ return size_ == 0;
+ }
+
+ CheckBool reserve(size_t size) WARN_UNUSED_RESULT {
+ if (failed())
+ return false;
+
+ if (size <= alloc_size_)
+ return true;
+
+ if (size < kStartSize)
+ size = kStartSize;
+
+ T* new_buffer = alloc_.allocate(size);
+ if (!new_buffer) {
+ clear();
+ alloc_size_ = kAllocationFailure;
+ } else {
+ if (buffer_) {
+ memcpy(new_buffer, buffer_, size_ * sizeof(T));
+ alloc_.deallocate(buffer_, alloc_size_);
+ }
+ buffer_ = new_buffer;
+ alloc_size_ = size;
+ }
+
+ return !failed();
+ }
+
+ CheckBool append(const T* data, size_t size) WARN_UNUSED_RESULT {
+ if (failed())
+ return false;
+
+ if (size > alloc_.max_size() - size_)
+ return false;
+
+ if (!size)
+ return true;
+
+ if ((alloc_size_ - size_) < size) {
+ const size_t max_size = alloc_.max_size();
+ size_t new_size = alloc_size_ ? alloc_size_ : kStartSize;
+ while (new_size < size_ + size) {
+ if (new_size < max_size - new_size) {
+ new_size *= 2;
+ } else {
+ new_size = max_size;
+ }
+ }
+ if (!reserve(new_size))
+ return false;
+ }
+
+ memcpy(buffer_ + size_, data, size * sizeof(T));
+ size_ += size;
+
+ return true;
+ }
+
+ CheckBool resize(size_t size, const T& init_value) WARN_UNUSED_RESULT {
+ if (size > size_) {
+ if (!reserve(size))
+ return false;
+ for (size_t i = size_; i < size; ++i)
+ buffer_[i] = init_value;
+ } else if (size < size_) {
+ // TODO(tommi): Should we allocate a new, smaller buffer?
+ // It might be faster for us to simply change the size.
+ }
+
+ size_ = size;
+
+ return true;
+ }
+
+ CheckBool push_back(const T& item) WARN_UNUSED_RESULT {
+ return append(&item, 1);
+ }
+
+ const T& back() const {
+ return buffer_[size_ - 1];
+ }
+
+ T& back() {
+ return buffer_[size_ - 1];
+ }
+
+ const T* begin() const {
+ if (!size_)
+ return NULL;
+ return &buffer_[0];
+ }
+
+ T* begin() {
+ if (!size_)
+ return NULL;
+ return &buffer_[0];
+ }
+
+ const T* end() const {
+ if (!size_)
+ return NULL;
+ return &buffer_[size_ - 1];
+ }
+
+ T* end() {
+ if (!size_)
+ return NULL;
+ return &buffer_[size_ - 1];
+ }
+
+ const T& operator[](size_t index) const {
+ DCHECK(index < size_);
+ return buffer_[index];
+ }
+
+ T& operator[](size_t index) {
+ DCHECK(index < size_);
+ return buffer_[index];
+ }
+
+ size_t size() const {
+ return size_;
+ }
+
+ T* data() const {
+ return buffer_;
+ }
+
+ // Returns true if an allocation failure has ever occurred for this object.
+ bool failed() const {
+ return alloc_size_ == kAllocationFailure;
+ }
+
+ protected:
+ T* buffer_;
+ size_t size_; // how much of the buffer we're using.
+ size_t alloc_size_; // how much space we have allocated.
+ Allocator alloc_;
+};
+
} // namespace courgette
#endif // COURGETTE_MEMORY_ALLOCATOR_H_