rocksdb/thrift/lib/cpp/async/TStreamAsyncChannel.h
Dhruba Borthakur 80c663882a Create leveldb server via Thrift.
Summary:
First draft.
Unit tests pass.

Test Plan: unit tests attached

Reviewers: heyongqiang

Reviewed By: heyongqiang

Differential Revision: https://reviews.facebook.net/D3969
2012-07-07 09:42:39 -07:00

442 lines
15 KiB
C++

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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_ASYNC_TSTREAMASYNCCHANNEL_H_
#define THRIFT_ASYNC_TSTREAMASYNCCHANNEL_H_ 1
#include "thrift/lib/cpp/async/TAsyncEventChannel.h"
#include "thrift/lib/cpp/async/TAsyncTransport.h"
#include "thrift/lib/cpp/async/TAsyncTimeout.h"
#include <boost/shared_ptr.hpp>
namespace apache { namespace thrift { namespace async {
class TAsyncTransport;
template <class Subclass_>
class TAsyncChannelWriteRequestBase {
public:
typedef std::tr1::function<void()> VoidCallback;
TAsyncChannelWriteRequestBase(const VoidCallback& callback,
const VoidCallback& errorCallback,
transport::TMemoryBuffer* message)
: buffer_(message),
next_(NULL),
callback_(callback),
errorCallback_(errorCallback) {
// The WriteRequest's buffer consumes all of the data in message,
// so we don't attempt to resend data; yet is also an observer
// which prevents consumed data from being overwritten while it's pending
// for the transport
uint32_t len = message->available_read();
message->borrow(NULL, &len);
message->consume(len);
}
virtual ~TAsyncChannelWriteRequestBase() {
}
void setNext(Subclass_* next) {
assert(next_ == NULL);
next_ = next;
}
Subclass_* getNext() const {
return next_;
}
protected:
apache::thrift::transport::TMemoryBuffer buffer_;
void invokeCallback() {
// unlink the buffer before invoking the callback, since we are
// now done with it. Not strictly required but faster.
buffer_.unlink();
callback_();
}
void invokeErrorCallback() {
// unlink the buffer before invoking the callback, since we are
// now done with it. Not strictly required but faster.
buffer_.unlink();
errorCallback_();
}
private:
TAsyncChannelWriteRequestBase();
Subclass_* next_;
VoidCallback callback_;
VoidCallback errorCallback_;
};
/**
* TStreamAsyncChannel is a helper class for channel implementations that use
* TAsyncTransport underneath.
*
* TStreamAsyncChannel provides the basic functionality for implementing a
* message-based asynchronous channel on top of a streaming TAsyncTransport.
*
* It requires two template arguments that control how the stream is broken up
* into messagess:
*
* WriteRequest_:
*
* This template parameter controls how messages are written to the
* underlying stream. It must implement the following methods:
*
* - WriteRequest_(const VoidCallback& callback,
* const VoidCallback& errorCallback,
* transport::TMemoryBuffer* message);
*
* The WriteRequest_ constructor accepts the success and error callbacks,
* and the TMemoryBuffer containing the data to send. The WriteRequest_
* may consume data from the message, but does not own the TMemoryBuffer
* (i.e., it should not delete the TMemoryBuffer.)
*
* - void setNext(WriteRequest_* next);
* - WriteRequest_* getNext() const;
*
* These two methods support chaining together a list of WriteRequest_
* objects. This is used when multiple write requests are pending on the
* channel.
*
* - void write(TAsyncTransport* transport,
* TAsyncTransport::WriteCallback* callback) THRIFT_NOEXCEPT;
*
* This method will be called to schedule the write. The WriteRequest_
* should invoke the transport's write() or writev() method with the data
* to send, and set the specified callback as the transport callback.
*
* Note that this API requires the WriteRequest_ to write the entire
* message with a single write() or writev() call. This allows the code
* to let the TAsyncTransport perform the write queuing when multiple
* messages are pending. (If needed we could rewrite this API in the
* future to relax this restriction.)
*
* - void writeSuccess() THRIFT_NOEXCEPT;
* - void writeError(size_t bytesWritten,
* const TTransportException& ex) THRIFT_NOEXCEPT;
*
* Either writeSuccess() or writeError() will be invoked once the message
* write has completed.
*
* ReadState_:
*
* This template parameter controls how the incoming stream is broken up into
* individual messages. It must implement the following methods:
*
* - ReadState_();
*
* The ReadState_ constructor takes no arguments.
*
* - void setCallbackBuffer(transport::TMemoryBuffer* buffer);
*
* When a new read is started, setCallbackBuffer() is called to set the
* buffer into which the message data should be placed.
*
* - void unsetCallbackBuffer();
*
* unsetCallbackBuffer() is called to clear the callback buffer when after
* a full message has been read.
*
* - bool hasReadAheadData();
*
* Some ReadState_ implementations may perform read-ahead, and read past
* the end of the message when reading from the underlying transport.
* hasReadAheadData() is called when a new read starts, to see if the
* ReadState_ has pending data for a new message that has already been read
* from the transport.
*
* If hasReadAheadData() returns true, readDataAvailable(0) will be called
* immediately, rather than waiting for new data from the transport.
*
* - bool hasPartialMessage();
*
* When EOF is read from the underlying transport, hasPartialMessage() is
* called to see if the EOF should be treated as an error or a normal
* close. (It is an error if hasPartialMessage() returns true.)
*
* - void getReadBuffer(void** bufReturn, size_t* lenReturn);
*
* When data becomes available on the underlying transport, getReadBuffer()
* is called to get the buffer where the data should be placed.
*
* - bool readDataAvailable(size_t len);
*
* readDataAvailable() is called when new data has been read from the
* underlying transport. The data will have been placed in the buffer
* returned by the previous getReadBuffer() call.
*/
template<typename WriteRequest_, typename ReadState_>
class TStreamAsyncChannel : public TAsyncEventChannel,
protected TAsyncTransport::ReadCallback,
protected TAsyncTransport::WriteCallback,
protected TAsyncTimeout {
public:
TStreamAsyncChannel(const boost::shared_ptr<TAsyncTransport>& transport);
/**
* Helper function to create a shared_ptr<TStreamAsyncChannel>.
*
* This passes in the correct destructor object, since TStreamAsyncChannel's
* destructor is protected and cannot be invoked directly.
*/
static boost::shared_ptr<TStreamAsyncChannel> newChannel(
const boost::shared_ptr<TAsyncTransport>& transport) {
return boost::shared_ptr<TStreamAsyncChannel>(
new TStreamAsyncChannel(transport), Destructor());
}
/**
* Destroy the channel.
*
* destroy() must be called to destroy the channel. The normal destructor
* is private, and should not be invoked directly. This prevents callers
* from deleting a TStreamAsyncChannel while it is invoking a callback.
*/
virtual void destroy();
// Methods inherited from TAsyncEventChannel
virtual bool readable() const;
virtual bool good() const;
virtual bool error() const;
virtual bool timedOut() const;
/**
* Send a message to the channel; note that "errorCob" will be called
* after a partial write as well as other errors. We will call "errorCob"
* immediately (before return) if the channel is unusable for some reason,
* and "cob" immediately if we're able to perform the write without delay.
*/
virtual void sendMessage(const VoidCallback& cob,
const VoidCallback& errorCob,
apache::thrift::transport::TMemoryBuffer* message);
/**
* Receive a message from the channel; note that "errorCob" will be called
* after a partial read as well as other errors. We will call "errorCob"
* immediately (before return) if the channel is unusable for some reason,
* and "cob" immediately if we're able to perform the read without delay.
*
* Note that an EOF is considered normal, so "cob" will be called although
* "good()" will be false.
*/
virtual void recvMessage(const VoidCallback& cob,
const VoidCallback& errorCob,
apache::thrift::transport::TMemoryBuffer* message);
/**
* Send a message to the channel and receive the response; note that the
* "errorCob: will be called after a write error and no receive is attempted.
* Also, a partial write or read will result in errorCob being called.
* We call "errorCob" before return if the channel is unusable for some
* reason. It is conceivable that "cob" will be called before return if data
* is somehow available in the channel when a read is first attempted.
*/
virtual void sendAndRecvMessage(
const VoidCallback& cob,
const VoidCallback& errorCob,
transport::TMemoryBuffer* sendBuf,
transport::TMemoryBuffer* recvBuf);
/**
* Close this channel.
*
* This gracefully closes the channel, waiting for all pending send
* requests to complete before actually closing the underlying transport.
*
* If a recvMessage() call is pending, it will be immediately failed.
*/
void close();
/**
* Close the channel immediately.
*
* This closes the channel immediately, dropping any outstanding messages
* waiting to be sent.
*
* If a recvMessage() call is pending, it will be immediately failed.
*/
void closeNow();
/**
* Attach the channel to a TEventBase.
*
* This may only be called if the channel is not currently attached to a
* TEventBase (by an earlier call to detachEventBase()).
*
* This method must be invoked in the TEventBase's thread.
*/
void attachEventBase(TEventBase* eventBase);
/**
* Detach the channel from its TEventBase.
*
* This may only be called when the channel is idle and has no reads or
* writes pending. Once detached, the channel may not be used again until it
* is re-attached to a TEventBase by calling attachEventBase().
*
* This method must be called from the current TEventBase's thread.
*/
void detachEventBase();
/**
* Get the TEventBase used by this channel.
*/
TEventBase* getEventBase() const;
/**
* Set the timeout for receiving messages.
*
* When set to a non-zero value, the entire message must be received within
* the specified number of milliseconds, or the receive will fail and the
* channel will be closed.
*
* If setRecvTimeout() is invoked while a recvMessage() call is currently in
* progress, the timeout will be restarted using the new value.
*/
void setRecvTimeout(uint32_t milliseconds);
/**
* Get the receive timeout.
*
* @return Returns the current receive timeout, in milliseconds. A return
* value of 0 indicates that no timeout is set.
*/
uint32_t getRecvTimeout() const {
return recvTimeout_;
}
/**
* Get the TAsyncTransport used by this channel.
*/
virtual boost::shared_ptr<TAsyncTransport> getTransport() {
return transport_;
}
/**
* Determine if this channel is idle (i.e., has no outstanding reads or
* writes).
*/
bool isIdle() const {
return (writeReqHead_ == NULL) && (!readCallback_) &&
!transport_->connecting();
}
protected:
struct ReadQueueEntry {
ReadQueueEntry(const VoidCallback& cob,
const VoidCallback& errorCob,
apache::thrift::transport::TMemoryBuffer* message) {
readCallback = cob;
readErrorCallback = errorCob;
readBuffer = message;
}
VoidCallback readCallback;
VoidCallback readErrorCallback;
transport::TMemoryBuffer *readBuffer;
int64_t startTime;
};
/**
* Protected destructor.
*
* Users of TStreamAsyncChannel must never delete it directly. Instead,
* invoke destroy().
*/
virtual ~TStreamAsyncChannel() {}
// callbacks from TAsyncTransport
void getReadBuffer(void** bufReturn, size_t* lenReturn);
void readDataAvailable(size_t len) THRIFT_NOEXCEPT;
void readEOF() THRIFT_NOEXCEPT;
void readError(const transport::TTransportException& ex) THRIFT_NOEXCEPT;
void writeSuccess() THRIFT_NOEXCEPT;
void writeError(size_t bytesWritten,
const transport::TTransportException& ex) THRIFT_NOEXCEPT;
// callback from TAsyncTimeout
void timeoutExpired() THRIFT_NOEXCEPT;
bool invokeReadDataAvailable(size_t len) THRIFT_NOEXCEPT;
void processReadEOF() THRIFT_NOEXCEPT;
void invokeReadCallback(VoidCallback cb,
char const* callbackName) THRIFT_NOEXCEPT;
void pushWriteRequest(WriteRequest_* req) {
if (writeReqTail_ == NULL) {
assert(writeReqHead_ == NULL);
writeReqHead_ = req;
} else {
writeReqTail_->setNext(req);
}
writeReqTail_ = req;
}
WriteRequest_* popWriteRequest() {
assert(writeReqHead_ != NULL);
WriteRequest_* req = writeReqHead_;
writeReqHead_ = req->getNext();
if (writeReqHead_ == NULL) {
assert(writeReqTail_ == req);
writeReqTail_ = NULL;
}
return req;
}
void failAllReads();
boost::shared_ptr<TAsyncTransport> transport_;
WriteRequest_* writeReqHead_;
WriteRequest_* writeReqTail_;
ReadState_ readState_;
VoidCallback readCallback_;
VoidCallback readErrorCallback_;
std::list<ReadQueueEntry> readCallbackQ_;
uint32_t recvTimeout_;
// true if a timeout has occurred
bool timedOut_;
private:
// Forbidden copy constructor and assignment opererator
TStreamAsyncChannel(TStreamAsyncChannel const &);
TStreamAsyncChannel& operator=(TStreamAsyncChannel const &);
};
class TStreamAsyncChannelFactory {
public:
virtual ~TStreamAsyncChannelFactory() {}
virtual boost::shared_ptr<TAsyncEventChannel> newChannel(
const boost::shared_ptr<TAsyncTransport>& transport) = 0;
};
}}} // apache::thrift::async
#endif // THRIFT_ASYNC_TSTREAMASYNCCHANNEL_H_