mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
265e672179
# ignore-this-changeset --HG-- extra : amend_source : 4d301d3b0b8711c4692392aa76088ba7fd7d1022
248 lines
8.1 KiB
C++
248 lines
8.1 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* CopyOnWrite<T> allows code to safely read from a data structure without
|
|
* worrying that reentrant code will modify it.
|
|
*/
|
|
|
|
#ifndef mozilla_image_CopyOnWrite_h
|
|
#define mozilla_image_CopyOnWrite_h
|
|
|
|
#include "mozilla/RefPtr.h"
|
|
#include "MainThreadUtils.h"
|
|
#include "nsISupportsImpl.h"
|
|
|
|
namespace mozilla {
|
|
namespace image {
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Implementation Details
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace detail {
|
|
|
|
template <typename T>
|
|
class CopyOnWriteValue final {
|
|
public:
|
|
NS_INLINE_DECL_REFCOUNTING(CopyOnWriteValue)
|
|
|
|
explicit CopyOnWriteValue(T* aValue)
|
|
: mValue(aValue), mReaders(0), mWriter(false) {}
|
|
explicit CopyOnWriteValue(already_AddRefed<T>& aValue)
|
|
: mValue(aValue), mReaders(0), mWriter(false) {}
|
|
explicit CopyOnWriteValue(already_AddRefed<T>&& aValue)
|
|
: mValue(aValue), mReaders(0), mWriter(false) {}
|
|
explicit CopyOnWriteValue(const RefPtr<T>& aValue)
|
|
: mValue(aValue), mReaders(0), mWriter(false) {}
|
|
explicit CopyOnWriteValue(RefPtr<T>&& aValue)
|
|
: mValue(aValue), mReaders(0), mWriter(false) {}
|
|
|
|
T* get() { return mValue.get(); }
|
|
const T* get() const { return mValue.get(); }
|
|
|
|
bool HasReaders() const { return mReaders > 0; }
|
|
bool HasWriter() const { return mWriter; }
|
|
bool HasUsers() const { return HasReaders() || HasWriter(); }
|
|
|
|
void LockForReading() {
|
|
MOZ_ASSERT(!HasWriter());
|
|
mReaders++;
|
|
}
|
|
void UnlockForReading() {
|
|
MOZ_ASSERT(HasReaders());
|
|
mReaders--;
|
|
}
|
|
|
|
struct MOZ_STACK_CLASS AutoReadLock {
|
|
explicit AutoReadLock(CopyOnWriteValue* aValue) : mValue(aValue) {
|
|
mValue->LockForReading();
|
|
}
|
|
~AutoReadLock() { mValue->UnlockForReading(); }
|
|
CopyOnWriteValue<T>* mValue;
|
|
};
|
|
|
|
void LockForWriting() {
|
|
MOZ_ASSERT(!HasUsers());
|
|
mWriter = true;
|
|
}
|
|
void UnlockForWriting() {
|
|
MOZ_ASSERT(HasWriter());
|
|
mWriter = false;
|
|
}
|
|
|
|
struct MOZ_STACK_CLASS AutoWriteLock {
|
|
explicit AutoWriteLock(CopyOnWriteValue* aValue) : mValue(aValue) {
|
|
mValue->LockForWriting();
|
|
}
|
|
~AutoWriteLock() { mValue->UnlockForWriting(); }
|
|
CopyOnWriteValue<T>* mValue;
|
|
};
|
|
|
|
private:
|
|
CopyOnWriteValue(const CopyOnWriteValue&) = delete;
|
|
CopyOnWriteValue(CopyOnWriteValue&&) = delete;
|
|
|
|
~CopyOnWriteValue() {}
|
|
|
|
RefPtr<T> mValue;
|
|
uint64_t mReaders = 0;
|
|
bool mWriter = false;
|
|
};
|
|
|
|
} // namespace detail
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Public API
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* CopyOnWrite<T> allows code to safely read from a data structure without
|
|
* worrying that reentrant code will modify it. If reentrant code would modify
|
|
* the data structure while other code is reading from it, a copy is made so
|
|
* that readers can continue to use the old version.
|
|
*
|
|
* Note that it's legal to nest a writer inside any number of readers, but
|
|
* nothing can be nested inside a writer. This is because it's assumed that the
|
|
* state of the contained data structure may not be consistent during the write.
|
|
*
|
|
* This is a main-thread-only data structure.
|
|
*
|
|
* To work with CopyOnWrite<T>, a type T needs to be reference counted and to
|
|
* support copy construction.
|
|
*/
|
|
template <typename T>
|
|
class CopyOnWrite final {
|
|
typedef detail::CopyOnWriteValue<T> CopyOnWriteValue;
|
|
|
|
public:
|
|
explicit CopyOnWrite(T* aValue) : mValue(new CopyOnWriteValue(aValue)) {}
|
|
|
|
explicit CopyOnWrite(already_AddRefed<T>& aValue)
|
|
: mValue(new CopyOnWriteValue(aValue)) {}
|
|
|
|
explicit CopyOnWrite(already_AddRefed<T>&& aValue)
|
|
: mValue(new CopyOnWriteValue(aValue)) {}
|
|
|
|
explicit CopyOnWrite(const RefPtr<T>& aValue)
|
|
: mValue(new CopyOnWriteValue(aValue)) {}
|
|
|
|
explicit CopyOnWrite(RefPtr<T>&& aValue)
|
|
: mValue(new CopyOnWriteValue(aValue)) {}
|
|
|
|
/// @return true if it's safe to read at this time.
|
|
bool CanRead() const { return !mValue->HasWriter(); }
|
|
|
|
/**
|
|
* Read from the contained data structure using the function @aReader.
|
|
* @aReader will be passed a pointer of type |const T*|. It's not legal to
|
|
* call this while a writer is active.
|
|
*
|
|
* @return whatever value @aReader returns, or nothing if @aReader is a void
|
|
* function.
|
|
*/
|
|
template <typename ReadFunc>
|
|
auto Read(ReadFunc aReader) const
|
|
-> decltype(aReader(static_cast<const T*>(nullptr))) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(CanRead());
|
|
|
|
// Run the provided function while holding a read lock.
|
|
RefPtr<CopyOnWriteValue> cowValue = mValue;
|
|
typename CopyOnWriteValue::AutoReadLock lock(cowValue);
|
|
return aReader(cowValue->get());
|
|
}
|
|
|
|
/**
|
|
* Read from the contained data structure using the function @aReader.
|
|
* @aReader will be passed a pointer of type |const T*|. If it's currently not
|
|
* possible to read because a writer is currently active, @aOnError will be
|
|
* called instead.
|
|
*
|
|
* @return whatever value @aReader or @aOnError returns (their return types
|
|
* must be consistent), or nothing if the provided functions are void.
|
|
*/
|
|
template <typename ReadFunc, typename ErrorFunc>
|
|
auto Read(ReadFunc aReader, ErrorFunc aOnError) const
|
|
-> decltype(aReader(static_cast<const T*>(nullptr))) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!CanRead()) {
|
|
return aOnError();
|
|
}
|
|
|
|
return Read(aReader);
|
|
}
|
|
|
|
/// @return true if it's safe to write at this time.
|
|
bool CanWrite() const { return !mValue->HasWriter(); }
|
|
|
|
/**
|
|
* Write to the contained data structure using the function @aWriter.
|
|
* @aWriter will be passed a pointer of type |T*|. It's not legal to call this
|
|
* while another writer is active.
|
|
*
|
|
* If readers are currently active, they will be able to continue reading from
|
|
* a copy of the old version of the data structure. The copy will be destroyed
|
|
* when all its readers finish. Later readers and writers will see the
|
|
* version of the data structure produced by the most recent call to Write().
|
|
*
|
|
* @return whatever value @aWriter returns, or nothing if @aWriter is a void
|
|
* function.
|
|
*/
|
|
template <typename WriteFunc>
|
|
auto Write(WriteFunc aWriter) -> decltype(aWriter(static_cast<T*>(nullptr))) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(CanWrite());
|
|
|
|
// If there are readers, we need to copy first.
|
|
if (mValue->HasReaders()) {
|
|
mValue = new CopyOnWriteValue(new T(*mValue->get()));
|
|
}
|
|
|
|
// Run the provided function while holding a write lock.
|
|
RefPtr<CopyOnWriteValue> cowValue = mValue;
|
|
typename CopyOnWriteValue::AutoWriteLock lock(cowValue);
|
|
return aWriter(cowValue->get());
|
|
}
|
|
|
|
/**
|
|
* Write to the contained data structure using the function @aWriter.
|
|
* @aWriter will be passed a pointer of type |T*|. If it's currently not
|
|
* possible to write because a writer is currently active, @aOnError will be
|
|
* called instead.
|
|
*
|
|
* If readers are currently active, they will be able to continue reading from
|
|
* a copy of the old version of the data structure. The copy will be destroyed
|
|
* when all its readers finish. Later readers and writers will see the
|
|
* version of the data structure produced by the most recent call to Write().
|
|
*
|
|
* @return whatever value @aWriter or @aOnError returns (their return types
|
|
* must be consistent), or nothing if the provided functions are void.
|
|
*/
|
|
template <typename WriteFunc, typename ErrorFunc>
|
|
auto Write(WriteFunc aWriter, ErrorFunc aOnError)
|
|
-> decltype(aWriter(static_cast<T*>(nullptr))) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!CanWrite()) {
|
|
return aOnError();
|
|
}
|
|
|
|
return Write(aWriter);
|
|
}
|
|
|
|
private:
|
|
CopyOnWrite(const CopyOnWrite&) = delete;
|
|
CopyOnWrite(CopyOnWrite&&) = delete;
|
|
|
|
RefPtr<CopyOnWriteValue> mValue;
|
|
};
|
|
|
|
} // namespace image
|
|
} // namespace mozilla
|
|
|
|
#endif // mozilla_image_CopyOnWrite_h
|