/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ #ifndef THRIFT_TRANSPORT_TBUFFERTRANSPORTS_H_ #define THRIFT_TRANSPORT_TBUFFERTRANSPORTS_H_ 1 #include #include #include #include #include #include namespace apache { namespace thrift { namespace transport { /** * Base class for all transports that use read/write buffers for performance. * * TBufferBase is designed to implement the fast-path "memcpy" style * operations that work in the common case. It does so with small and * (eventually) nonvirtual, inlinable methods. TBufferBase is an abstract * class. Subclasses are expected to define the "slow path" operations * that have to be done when the buffers are full or empty. * */ class TBufferBase : public TVirtualTransport { public: /** * Fast-path read. * * When we have enough data buffered to fulfill the read, we can satisfy it * with a single memcpy, then adjust our internal pointers. If the buffer * is empty, we call out to our slow path, implemented by a subclass. * This method is meant to eventually be nonvirtual and inlinable. */ uint32_t read(uint8_t* buf, uint32_t len) { if (LIKELY(static_cast(len) <= rBound_ - rBase_)) { std::memcpy(buf, rBase_, len); rBase_ += len; return len; } return readSlow(buf, len); } /** * Shortcutted version of readAll. */ uint32_t readAll(uint8_t* buf, uint32_t len) { if (LIKELY(static_cast(len) <= rBound_ - rBase_)) { std::memcpy(buf, rBase_, len); rBase_ += len; return len; } return apache::thrift::transport::readAll(*this, buf, len); } /** * Fast-path write. * * When we have enough empty space in our buffer to accommodate the write, we * can satisfy it with a single memcpy, then adjust our internal pointers. * If the buffer is full, we call out to our slow path, implemented by a * subclass. This method is meant to eventually be nonvirtual and * inlinable. */ void write(const uint8_t* buf, uint32_t len) { if (LIKELY(static_cast(len) <= wBound_ - wBase_)) { // wBase_ can be null if the buffer is empty and passing null to memcpy is // a UB even if len is zero. Prevent this by skipping memcpy altogether // in this case. if (LIKELY(len != 0)) { std::memcpy(wBase_, buf, len); wBase_ += len; } return; } writeSlow(buf, len); } /** * Fast-path borrow. A lot like the fast-path read. */ const uint8_t* borrow(uint8_t* buf, uint32_t* len) { if (LIKELY(static_cast(*len) <= rBound_ - rBase_)) { // With strict aliasing, writing to len shouldn't force us to // refetch rBase_ from memory. TODO(dreiss): Verify this. *len = rBound_ - rBase_; return rBase_; } return borrowSlow(buf, len); } /** * Consume doesn't require a slow path. * When the read ptr catches up with write ptr, invokes consumeEnd(). */ void consume(uint32_t len) { if (LIKELY(static_cast(len) <= rBound_ - rBase_)) { rBase_ += len; if (rBase_ == wBase_) { // This is required for TMemoryBuffer. consumeEnd(); } } else { throw TTransportException( TTransportException::BAD_ARGS, "consume did not follow a borrow."); } } protected: /// Slow path read. virtual uint32_t readSlow(uint8_t* buf, uint32_t len) = 0; /// Slow path write. virtual void writeSlow(const uint8_t* buf, uint32_t len) = 0; /// consumeEnd, invoked when all data has been consumed. virtual void consumeEnd() {} /** * Slow path borrow. * * POSTCONDITION: return == nullptr || rBound_ - rBase_ >= *len */ virtual const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) = 0; /** * Trivial constructor. * * Initialize pointers safely. Constructing is not a very * performance-sensitive operation, so it is okay to just leave it to * the concrete class to set up pointers correctly. */ TBufferBase() : rBase_(nullptr), rBound_(nullptr), wBase_(nullptr), wBound_(nullptr) {} /// Convenience mutator for setting the read buffer. void setReadBuffer(uint8_t* buf, uint32_t len) { rBase_ = buf; rBound_ = buf + len; } /// Convenience mutator for setting the write buffer. void setWriteBuffer(uint8_t* buf, uint32_t len) { wBase_ = buf; wBound_ = buf + len; } ~TBufferBase() override {} /// Reads begin here. uint8_t* rBase_; /// Reads may extend to just before here. uint8_t* rBound_; /// Writes begin here. uint8_t* wBase_; /// Writes may extend to just before here. uint8_t* wBound_; }; /** * Buffered transport. For reads it will read more data than is requested * and will serve future data out of a local buffer. For writes, data is * stored to an in memory buffer before being written out. * */ class TBufferedTransport : public TVirtualTransport { public: static const int DEFAULT_BUFFER_SIZE = 512; /// Use default buffer sizes. explicit TBufferedTransport(std::shared_ptr transport) : transport_(transport), rBufSize_(DEFAULT_BUFFER_SIZE), wBufSize_(DEFAULT_BUFFER_SIZE), wBufResetSize_(0), wBufResetEveryN_(0), wBufResetCount_(0), rBuf_(new uint8_t[rBufSize_]), wBuf_(new uint8_t[wBufSize_]) { initPointers(); } /// Use specified buffer sizes. TBufferedTransport(std::shared_ptr transport, uint32_t sz) : transport_(transport), rBufSize_(sz), wBufSize_(sz), wBufResetSize_(0), wBufResetEveryN_(0), wBufResetCount_(0), rBuf_(new uint8_t[rBufSize_]), wBuf_(new uint8_t[wBufSize_]) { initPointers(); } /** * Ctor with initial read and write buffer sizes and write buffer reset * behaviour settings. * * @param transport Underlying transport. * @param sz Initial buffer size. * @param reset_sz Buffer size after a reset. See also reset_every_n. * @param reset_every_n Reset the buffer after every N calls to flush(). * If set to zero (default), no reset is done. */ TBufferedTransport( std::shared_ptr transport, uint32_t rsz, uint32_t wsz, uint32_t reset_sz = 0, uint32_t reset_every_n = 0) : transport_(transport), rBufSize_(rsz), wBufSize_(wsz), wBufResetSize_(reset_sz), wBufResetEveryN_(reset_every_n), wBufResetCount_(0), rBuf_(new uint8_t[rBufSize_]), wBuf_(new uint8_t[wBufSize_]) { initPointers(); } // Tries to put some data back in the beginning of the read buffer. void putBack(uint8_t* buf, uint32_t len); void open() override { transport_->open(); } bool isOpen() override { return transport_->isOpen(); } bool peek() override { if (rBase_ == rBound_) { setReadBuffer(rBuf_.get(), transport_->read(rBuf_.get(), rBufSize_)); } return (rBound_ > rBase_); } void close() override { flush(); transport_->close(); } uint32_t readSlow(uint8_t* buf, uint32_t len) override; void writeSlow(const uint8_t* buf, uint32_t len) override; void flush() override; /** * The following behavior is currently implemented by TBufferedTransport, * but that may change in a future version: * 1/ If len is at most rBufSize_, borrow will never return nullptr. * Depending on the underlying transport, it could throw an exception * or hang forever. * 2/ Some borrow requests may copy bytes internally. However, * if len is at most rBufSize_/2, none of the copied bytes * will ever have to be copied again. For optimal performance, * stay under this limit. */ const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) override; std::shared_ptr getUnderlyingTransport() { return transport_; } /* * TVirtualTransport provides a default implementation of readAll(). * We want to use the TBufferBase version instead. */ using TBufferBase::readAll; protected: virtual void shrinkWriteBuffer(); void initPointers() { setReadBuffer(rBuf_.get(), 0); setWriteBuffer(wBuf_.get(), wBufSize_); // Write size never changes. } std::shared_ptr transport_; uint32_t rBufSize_; uint32_t wBufSize_; uint32_t wBufResetSize_; uint32_t wBufResetEveryN_; uint32_t wBufResetCount_; std::unique_ptr rBuf_; std::unique_ptr wBuf_; }; /** * Wraps a transport into a buffered one. * */ class TBufferedTransportFactory : public TTransportFactory { public: TBufferedTransportFactory() {} ~TBufferedTransportFactory() override {} /** * Wraps the transport into a buffered one. */ std::shared_ptr getTransport( std::shared_ptr trans) override { return std::shared_ptr(new TBufferedTransport(trans)); } }; /** * Framed transport. All writes go into an in-memory buffer until flush is * called, at which point the transport writes the length of the entire * binary chunk followed by the data payload. This allows the receiver on the * other end to always do fixed-length reads. * */ class TFramedTransport : public TVirtualTransport { public: static const int DEFAULT_BUFFER_SIZE = 512; /// Use default buffer sizes. explicit TFramedTransport(std::shared_ptr transport) : transport_(transport), rBufSize_(0), wBufSize_(DEFAULT_BUFFER_SIZE), wBufResetSize_(0), wBufResetEveryN_(0), wBufResetCount_(0), maxFrameSize_(0x7fffffff), rBuf_(), wBuf_(new uint8_t[wBufSize_]) { initPointers(); } /** * Ctor with initial buffer size and write buffer reset behaviour settings. * * @param transport Underlying transport. * @param sz Initial buffer size. * @param reset_sz Buffer size after a reset. See also reset_every_n. * @param reset_every_n Reset the buffer after every N calls to flush(). * If set to zero (default), no reset is done. */ TFramedTransport( std::shared_ptr transport, uint32_t sz, uint32_t reset_sz = 0, uint32_t reset_every_n = 0, uint32_t max_frame_size = 0x7fffffff) : transport_(transport), rBufSize_(0), wBufSize_(sz), wBufResetSize_(reset_sz), wBufResetEveryN_(reset_every_n), wBufResetCount_(0), maxFrameSize_(max_frame_size), rBuf_(), wBuf_(new uint8_t[wBufSize_]) { assert(wBufResetSize_ == 0 || wBufSize_ <= wBufResetSize_); initPointers(); } void open() override { transport_->open(); } bool isOpen() override { return transport_->isOpen(); } bool peek() override { return (rBase_ < rBound_) || transport_->peek(); } void close() override { flush(); transport_->close(); } uint32_t readSlow(uint8_t* buf, uint32_t len) override; void writeSlow(const uint8_t* buf, uint32_t len) override; void flush() override; uint32_t readEnd() override; uint32_t writeEnd() override; const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) override; std::shared_ptr getUnderlyingTransport() { return transport_; } /* * TVirtualTransport provides a default implementation of readAll(). * We want to use the TBufferBase version instead. */ using TBufferBase::readAll; protected: /// Constructor for subclassing. TFramedTransport() : rBufSize_(0), wBufSize_(DEFAULT_BUFFER_SIZE), wBufResetSize_(0), wBufResetEveryN_(0), wBufResetCount_(0), maxFrameSize_(0x7fffffff), rBuf_(), wBuf_(new uint8_t[wBufSize_]) { initPointers(); } /** * Reads a frame of input from the underlying stream. * * Returns true if a frame was read successfully, or false on EOF. * (Raises a TTransportException if EOF occurs after a partial frame.) * * @param req_sz The size of the requested data. readFrame may read more * than this amount, but should not read less. */ virtual bool readFrame(uint32_t min_frame_sz); virtual void shrinkWriteBuffer(); void initPointers() { setReadBuffer(nullptr, 0); setWriteBuffer(wBuf_.get(), wBufSize_); // Pad the buffer so we can insert the size later. int32_t pad = 0; this->write((uint8_t*)&pad, sizeof(pad)); } std::shared_ptr transport_; uint32_t rBufSize_; uint32_t wBufSize_; uint32_t wBufResetSize_; uint32_t wBufResetEveryN_; uint32_t wBufResetCount_; uint32_t maxFrameSize_; std::unique_ptr rBuf_; std::unique_ptr wBuf_; }; /** * Wraps a transport into a framed one. * */ class TFramedTransportFactory : public TTransportFactory { public: TFramedTransportFactory() {} ~TFramedTransportFactory() override {} /** * Wraps the transport into a framed one. */ std::shared_ptr getTransport( std::shared_ptr trans) override { return std::shared_ptr(new TFramedTransport(trans)); } }; /** * A memory buffer is a transport that simply reads from and writes to an * in memory buffer. Anytime you call write on it, the data is simply placed * into a buffer, and anytime you call read, data is read from that buffer. * * The buffers are allocated using C constructs malloc,realloc, and the size * doubles as necessary. We've considered using scoped * */ class TMemoryBuffer : public TVirtualTransport { private: TMemoryBuffer(const TMemoryBuffer&); TMemoryBuffer& operator=(const TMemoryBuffer&); // Common initialization done by all constructors. void initCommon(uint8_t* buf, uint32_t size, bool owner, uint32_t wPos) { if (buf == nullptr && size != 0) { assert(owner); buf = (uint8_t*)std::malloc(size); if (buf == nullptr) { throw std::bad_alloc(); } } buffer_ = buf; bufferSize_ = size; rBase_ = buffer_; rBound_ = buffer_ + wPos; // TODO(dreiss): Investigate nullptr-ing this if !owner. wBase_ = buffer_ + wPos; wBound_ = buffer_ + bufferSize_; owner_ = owner; linkedBuffer_ = nullptr; observerCount_ = 0; // rBound_ is really an artifact. In principle, it should always be // equal to wBase_. We update it in a few places (computeRead, etc.). } public: static const uint32_t defaultSize = 1024; /** * This enum specifies how a TMemoryBuffer should treat * memory passed to it via constructors or resetBuffer. * * OBSERVE: * TMemoryBuffer will simply store a pointer to the memory. * It is the callers responsibility to ensure that the pointer * remains valid for the lifetime of the TMemoryBuffer, * and that it is properly cleaned up. * Note that no data can be written to observed buffers. * * COPY: * TMemoryBuffer will make an internal copy of the buffer. * The caller has no responsibilities. * * TAKE_OWNERSHIP: * TMemoryBuffer will become the "owner" of the buffer, * and will be responsible for freeing it. * The memory must have been allocated with malloc. */ enum MemoryPolicy { OBSERVE = 1, COPY = 2, TAKE_OWNERSHIP = 3, }; /** * Construct a TMemoryBuffer with a default-sized buffer, * owned by the TMemoryBuffer object. */ TMemoryBuffer() { initCommon(nullptr, defaultSize, true, 0); } /** * Construct a TMemoryBuffer with a buffer of a specified size, * owned by the TMemoryBuffer object. * * @param sz The initial size of the buffer. */ explicit TMemoryBuffer(uint32_t sz) { initCommon(nullptr, sz, true, 0); } /** * Construct a TMemoryBuffer with buf as its initial contents. * * @param buf The initial contents of the buffer. * Note that, while buf is a non-const pointer, * TMemoryBuffer will not write to it if policy == OBSERVE, * so it is safe to const_cast(whatever). * @param sz The size of @c buf. * @param policy See @link MemoryPolicy @endlink . */ TMemoryBuffer(uint8_t* buf, uint32_t sz, MemoryPolicy policy = OBSERVE) { if (buf == nullptr && sz != 0) { throw TTransportException( TTransportException::BAD_ARGS, "TMemoryBuffer given null buffer with non-zero size."); } switch (policy) { case OBSERVE: case TAKE_OWNERSHIP: initCommon(buf, sz, policy == TAKE_OWNERSHIP, sz); break; case COPY: initCommon(nullptr, sz, true, 0); this->write(buf, sz); break; default: throw TTransportException( TTransportException::BAD_ARGS, "Invalid MemoryPolicy for TMemoryBuffer"); } } explicit TMemoryBuffer(TMemoryBuffer* buffer) { initCommon( buffer->rBase_, buffer->available_read(), false, buffer->available_read()); // Have to set buffer_ appropriately so this buffer can take ownership // later if necessary. initCommon sets all other state correctly. buffer_ = buffer->buffer_; linkedBuffer_ = buffer; linkedBuffer_->observe(this); } ~TMemoryBuffer() override { cleanup(); } // Set this buffer to observe the next length bytes of buffer. void link(TMemoryBuffer* buffer, uint32_t length) { assert(length <= buffer->available_read()); cleanup(); initCommon(buffer->rBase_, length, false, length); buffer_ = buffer->buffer_; linkedBuffer_ = buffer; linkedBuffer_->observe(this); } bool isOpen() override { return true; } bool peek() override { return (rBase_ < wBase_); } void open() override {} void close() override {} uint32_t getBufferSize() const { return bufferSize_; } // TODO(dreiss): Make bufPtr const. void getBuffer(uint8_t** bufPtr, uint32_t* sz) const { *bufPtr = rBase_; *sz = wBase_ - rBase_; } std::string getBufferAsString() const { if (buffer_ == nullptr) { return ""; } uint8_t* buf; uint32_t sz; getBuffer(&buf, &sz); return std::string((char*)buf, (std::string::size_type)sz); } std::unique_ptr cloneBufferAsIOBuf() const { if (buffer_ == nullptr) { return nullptr; } uint8_t* buf; uint32_t sz; getBuffer(&buf, &sz); return folly::IOBuf::copyBuffer(buf, sz); } std::unique_ptr wrapBufferAsIOBuf() const { if (buffer_ == nullptr) { return nullptr; } uint8_t* buf; uint32_t sz; getBuffer(&buf, &sz); return folly::IOBuf::wrapBuffer(buf, sz); } void appendBufferToString(std::string& str) { if (buffer_ == nullptr) { return; } uint8_t* buf; uint32_t sz; getBuffer(&buf, &sz); str.append((char*)buf, sz); } void resetBuffer() { rBase_ = buffer_; rBound_ = buffer_; wBase_ = buffer_; // It isn't safe to write into a buffer we don't own. if (!owner_) { wBound_ = wBase_; bufferSize_ = 0; } } // Register an observer of the memory in this buffer. // // A TMemoryBuffer with one or more observers has special handling // for operations that would invalidate the data being observed. If // an operation would invalidate the buffer pointer (eg: realloc, free), // ownership of that buffer is first transferred to one of the observing // TMemoryBuffers. // // Calls that reset an apparently empty buffer are delayed until all // observers are detached. void observe(TMemoryBuffer* observer) { if (observer != nullptr) { observers_.push_back(observer); } observerCount_++; } // Remove an observer of the memory in this buffer. If there is a pending // consumeEnd call, process it void unobserve(TMemoryBuffer* observer) { if (UNLIKELY(observerCount_ == 0)) { throw TTransportException( "Tried to unobserve a buffer with no " "observers"); } bool found = false; if (observer != nullptr) { for (TMemoryBufferContainer::iterator it = observers_.begin(); it != observers_.end(); ++it) { if (*it == observer) { observers_.erase(it); found = true; break; } } } if (!found) { throw TTransportException( "Tried to remove an observer that was not " "observing the buffer"); } observerCount_--; if (observerCount_ == 0 && rBase_ == wBase_) { consumeEnd(); } } // Remove link to an observed memory buffer. If the buffer is not linked // this is a no-op. void unlink() { if (linkedBuffer_ != nullptr) { linkedBuffer_->unobserve(this); linkedBuffer_ = nullptr; } } /// See constructor documentation. void resetBuffer(uint8_t* buf, uint32_t sz, MemoryPolicy policy = OBSERVE) { // Use a variant of the copy-and-swap trick for assignment operators. // This is sub-optimal in terms of performance for two reasons: // 1/ The constructing and swapping of the (small) values // in the temporary object takes some time, and is not necessary. // 2/ If policy == COPY, we allocate the new buffer before // freeing the old one, precluding the possibility of // reusing that memory. // I doubt that either of these problems could be optimized away, // but the second is probably no a common case, and the first is minor. // I don't expect resetBuffer to be a common operation, so I'm willing to // bite the performance bullet to make the method this simple. // Construct the new buffer. TMemoryBuffer new_buffer(buf, sz, policy); // Move it into ourself. this->swap(new_buffer); // Our old self gets destroyed. } /// See constructor documentation. void resetBuffer(uint32_t sz) { // Construct the new buffer. TMemoryBuffer new_buffer(sz); // Move it into ourself. this->swap(new_buffer); // Our old self gets destroyed. } std::string readAsString(uint32_t len) { std::string str; (void)readAppendToString(str, len); return str; } uint32_t readAppendToString(std::string& str, uint32_t len); // return number of bytes read uint32_t readEnd() override { uint32_t bytes = rBase_ - buffer_; if (rBase_ == wBase_) { resetBuffer(); } return bytes; } // Return number of bytes written uint32_t writeEnd() override { return wBase_ - buffer_; } uint32_t available_read() const { // Remember, wBase_ is the real rBound_. return wBase_ - rBase_; } uint32_t available_write() const { return wBound_ - wBase_; } // Returns a pointer to where the client can write data to append to // the TMemoryBuffer, and ensures the buffer is big enough to accommodate a // write of the provided length. The returned pointer is very convenient for // passing to read(), recv(), or similar. You must call wroteBytes() as soon // as data is written or the buffer will not be aware that data has changed. uint8_t* getWritePtr(uint32_t len) { ensureCanWrite(len); return wBase_; } // Informs the buffer that the client has written 'len' bytes into storage // that had been provided by getWritePtr(). void wroteBytes(uint32_t len); /* * TVirtualTransport provides a default implementation of readAll(). * We want to use the TBufferBase version instead. */ using TBufferBase::readAll; /** * Extract the read buffer from the TMemoryBuffer, or make a copy if this * buffer does not own it. It's not safe to assume that a buffer created * with TAKE_OWNERSHIP or COPY is still the owner of it's underlying buffer, * because some TMemoryBuffer APIs (resetBuffer, link) change the ownership. * Because of that, it's more consistent to make a copy rather than throw. * * The TMemoryBuffer's internal buffer is returned, and the caller is * given ownership of the buffer. The caller is responsible for eventually * freeing the buffer using free(). * * The TMemoryBuffer itself will be reset and will allocate a new buffer the * next time data is written into it. * * @param buf The returned buffer pointer is stored at the location * specified by this argument. * @param buflen This parameter is used to return the number of bytes of * readable data available at the start of the returned buffer. * (Note that this is the amount of available data, not the * buffer capacity.) */ void extractReadBuffer(uint8_t** buf, uint32_t* buflen) { *buflen = available_read(); if (owner_) { if (rBase_ != buffer_) { memmove(buffer_, rBase_, *buflen); } *buf = buffer_; } else { *buf = (uint8_t*)malloc(*buflen); if (*buf == nullptr) { throw std::bad_alloc(); } memcpy(*buf, rBase_, *buflen); cleanup(); } initCommon(nullptr, 0, true, 0); } protected: void swap(TMemoryBuffer& that) { using std::swap; swap(buffer_, that.buffer_); swap(bufferSize_, that.bufferSize_); swap(rBase_, that.rBase_); swap(rBound_, that.rBound_); swap(wBase_, that.wBase_); swap(wBound_, that.wBound_); swap(owner_, that.owner_); swap(linkedBuffer_, that.linkedBuffer_); swap(observerCount_, that.observerCount_); observers_.swap(that.observers_); for (auto buf : observers_) { buf->linkedBuffer_ = this; } for (auto buf : that.observers_) { buf->linkedBuffer_ = &that; } if (linkedBuffer_ != nullptr) { linkedBuffer_->unobserve(&that); linkedBuffer_->observe(this); } if (that.linkedBuffer_ != nullptr) { that.linkedBuffer_->unobserve(this); that.linkedBuffer_->observe(&that); } } void transferOwnership() { TMemoryBuffer* newOwner = nullptr; assert(!observers_.empty()); // The optimal case is to make the new owner the TMemoryBuffer // with the longest life-span. Use FIFO as a hueristic and assign // the new owner to the last registered observer. newOwner = observers_.back(); observers_.pop_back(); newOwner->owner_ = true; newOwner->linkedBuffer_ = nullptr; for (TMemoryBufferContainer::iterator it = observers_.begin(); it != observers_.end(); ++it) { (*it)->linkedBuffer_ = newOwner; // It's possible that an observer has other observers newOwner->observers_.push_back(*it); newOwner->observerCount_++; } observers_.clear(); observerCount_ = 0; } void cleanup() { if (owner_) { if (observerCount_ > 0) { transferOwnership(); } else { std::free(buffer_); } } if (linkedBuffer_ != nullptr) { linkedBuffer_->unobserve(this); } } void consumeEnd() override { if (observerCount_ == 0) { resetBuffer(); } } // Make sure there's at least 'len' bytes available for writing. void ensureCanWrite(uint32_t len); // Compute the position and available data for reading. void computeRead(uint32_t len, uint8_t** out_start, uint32_t* out_give); uint32_t readSlow(uint8_t* buf, uint32_t len) override; void writeSlow(const uint8_t* buf, uint32_t len) override; const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) override; // Data buffer uint8_t* buffer_; // Allocated buffer size uint32_t bufferSize_; // Is this object the owner of the buffer? bool owner_; TMemoryBuffer* linkedBuffer_; uint32_t observerCount_; // The container is a vector (rather than a list) for performance // reasons despite the O(N) penalty in unobserve. The vec keeps the // overhead lower in the common case, which is a single observer. typedef std::vector TMemoryBufferContainer; TMemoryBufferContainer observers_; // Don't forget to update constructors, initCommon, and swap if // you add new members. }; } // namespace transport } // namespace thrift } // namespace apache #endif // #ifndef THRIFT_TRANSPORT_TBUFFERTRANSPORTS_H_