+ /**
+ * WritableRangeCache represents a cache of current writable tail and provides
+ * cheap and simple interface to append to it that avoids paying the cost of
+ * preallocate/postallocate pair (i.e. indirections and checks).
+ *
+ * The cache is flushed on destruction/copy/move and on non-const accesses to
+ * the underlying IOBufQueue.
+ *
+ * Note: there can be only one active cache for a given IOBufQueue, i.e. when
+ * you fill a cache object it automatically invalidates other
+ * cache (if any).
+ */
+ class WritableRangeCache {
+ public:
+ explicit WritableRangeCache(folly::IOBufQueue* q = nullptr) : queue_(q) {
+ if (queue_) {
+ fillCache();
+ }
+ }
+
+ /**
+ * Move constructor/assignment can move the cached range, but must update
+ * the reference in IOBufQueue.
+ */
+ WritableRangeCache(WritableRangeCache&& other)
+ : data_(std::move(other.data_)), queue_(other.queue_) {
+ if (data_.attached) {
+ queue_->updateCacheRef(data_);
+ }
+ }
+ WritableRangeCache& operator=(WritableRangeCache&& other) {
+ if (data_.attached) {
+ queue_->clearWritableRangeCache();
+ }
+
+ data_ = std::move(other.data_);
+ queue_ = other.queue_;
+
+ if (data_.attached) {
+ queue_->updateCacheRef(data_);
+ }
+
+ return *this;
+ }
+
+ /**
+ * Copy constructor/assignment cannot copy the cached range.
+ */
+ WritableRangeCache(const WritableRangeCache& other)
+ : queue_(other.queue_) {}
+ WritableRangeCache& operator=(const WritableRangeCache& other) {
+ if (data_.attached) {
+ queue_->clearWritableRangeCache();
+ }
+
+ queue_ = other.queue_;
+
+ return *this;
+ }
+
+ ~WritableRangeCache() {
+ if (data_.attached) {
+ queue_->clearWritableRangeCache();
+ }
+ }
+
+ /**
+ * Reset the underlying IOBufQueue, will flush current cache if present.
+ */
+ void reset(IOBufQueue* q) {
+ if (data_.attached) {
+ queue_->clearWritableRangeCache();
+ }
+
+ queue_ = q;
+
+ if (queue_) {
+ fillCache();
+ }
+ }
+
+ /**
+ * Get a pointer to the underlying IOBufQueue object.
+ */
+ IOBufQueue* queue() {
+ return queue_;
+ }
+
+ /**
+ * Return a pointer to the start of cached writable tail.
+ *
+ * Note: doesn't populate cache.
+ */
+ uint8_t* writableData() {
+ dcheckIntegrity();
+ return data_.cachedRange.first;
+ }
+
+ /**
+ * Return a length of cached writable tail.
+ *
+ * Note: doesn't populate cache.
+ */
+ size_t length() {
+ dcheckIntegrity();
+ return data_.cachedRange.second - data_.cachedRange.first;
+ }
+
+ /**
+ * Mark n bytes as occupied (e.g. postallocate).
+ */
+ void append(size_t n) {
+ dcheckIntegrity();
+ // This can happen only if somebody is misusing the interface.
+ // E.g. calling append after touching IOBufQueue or without checking
+ // the length().
+ if (LIKELY(data_.cachedRange.first != nullptr)) {
+ DCHECK_LE(n, length());
+ data_.cachedRange.first += n;
+ } else {
+ appendSlow(n);
+ }
+ }
+
+ /**
+ * Same as append(n), but avoids checking if there is a cache.
+ * The caller must guarantee that the cache is set (e.g. the caller just
+ * called fillCache or checked that it's not empty).
+ */
+ void appendUnsafe(size_t n) {
+ data_.cachedRange.first += n;
+ }
+
+ /**
+ * Fill the cache of writable tail from the underlying IOBufQueue.
+ */
+ void fillCache() {
+ queue_->fillWritableRangeCache(data_);
+ }
+
+ private:
+ WritableRangeCacheData data_;
+ IOBufQueue* queue_;
+
+ FOLLY_NOINLINE void appendSlow(size_t n) {
+ queue_->postallocate(n);
+ }
+
+ void dcheckIntegrity() {
+ // Tail start should always be less than tail end.
+ DCHECK_LE(
+ (void*)data_.cachedRange.first, (void*)data_.cachedRange.second);
+ DCHECK(
+ data_.cachedRange.first != nullptr ||
+ data_.cachedRange.second == nullptr);
+
+ // Cached range should be always empty if the cache is not attached.
+ DCHECK(
+ data_.attached ||
+ (data_.cachedRange.first == nullptr &&
+ data_.cachedRange.second == nullptr));
+
+ // We cannot be in attached state if the queue_ is not set.
+ DCHECK(queue_ != nullptr || !data_.attached);
+
+ // If we're attached and the cache is not empty, then it should coincide
+ // with the tail buffer.
+ DCHECK(
+ !data_.attached || data_.cachedRange.first == nullptr ||
+ (queue_->head_ != nullptr &&
+ data_.cachedRange.first >= queue_->head_->prev()->writableTail() &&
+ data_.cachedRange.second ==
+ queue_->head_->prev()->writableTail() +
+ queue_->head_->prev()->tailroom()));
+ }
+ };
+