This is an automated email from the ASF dual-hosted git repository.
wwbmmm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push:
new 0ec3a9dd Refactor streaming rpc (#3422)
0ec3a9dd is described below
commit 0ec3a9ddaa45f79639ebdaa018970de8d897dc53
Author: Bright Chen <[email protected]>
AuthorDate: Sun Aug 9 14:35:44 2026 +0800
Refactor streaming rpc (#3422)
---
src/brpc/controller.cpp | 8 +-
src/brpc/policy/baidu_rpc_protocol.cpp | 20 +-
src/brpc/policy/streaming_rpc_protocol.cpp | 25 +-
src/brpc/socket.h | 2 +-
src/brpc/stream.cpp | 660 +++++++++++++++++------------
src/brpc/stream.h | 9 +-
src/brpc/stream_impl.h | 105 +++--
src/brpc/versioned_ref_with_id.h | 144 ++++---
src/bthread/execution_queue_inl.h | 25 +-
test/brpc_streaming_rpc_unittest.cpp | 169 +++++---
10 files changed, 698 insertions(+), 469 deletions(-)
diff --git a/src/brpc/controller.cpp b/src/brpc/controller.cpp
index 244d5d23..5583231c 100644
--- a/src/brpc/controller.cpp
+++ b/src/brpc/controller.cpp
@@ -1480,7 +1480,7 @@ void Controller::HandleStreamConnection(Socket
*host_socket) {
return;
}
size_t stream_num = _request_streams.size();
- std::vector<SocketUniquePtr> ptrs(stream_num);
+ std::vector<StreamUniquePtr> ptrs(stream_num);
if (!FailedInline()) {
if (_remote_stream_settings == NULL) {
if (!FailedInline()) {
@@ -1488,7 +1488,7 @@ void Controller::HandleStreamConnection(Socket
*host_socket) {
}
} else {
for (size_t i = 0; i < stream_num; ++i) {
- if (Socket::Address(_request_streams[i], &ptrs[i]) != 0) {
+ if (Stream::Address(_request_streams[i], &ptrs[i]) != 0) {
if (!FailedInline()) {
SetFailed(EREQUEST, "Request stream=%" PRIu64 " was
closed before responded",
_request_streams[i]);
@@ -1511,14 +1511,14 @@ void Controller::HandleStreamConnection(Socket
*host_socket) {
}
return;
}
- Stream* s = (Stream*)ptrs[0]->conn();
+ Stream* s = ptrs[0].get();
s->SetConnected(_remote_stream_settings);
if (stream_num > 1) {
auto extra_stream_ids =
std::move(*_remote_stream_settings->mutable_extra_stream_ids());
_remote_stream_settings->clear_extra_stream_ids();
for (size_t i = 1; i < stream_num; ++i) {
if(!ptrs[i]) continue;
- Stream* extra_stream = (Stream *) ptrs[i]->conn();
+ Stream* extra_stream = ptrs[i].get();
_remote_stream_settings->set_stream_id(extra_stream_ids[i - 1]);
extra_stream->SetHostSocket(host_socket);
extra_stream->SetConnected(_remote_stream_settings);
diff --git a/src/brpc/policy/baidu_rpc_protocol.cpp
b/src/brpc/policy/baidu_rpc_protocol.cpp
index 49863b2c..41ff97ee 100644
--- a/src/brpc/policy/baidu_rpc_protocol.cpp
+++ b/src/brpc/policy/baidu_rpc_protocol.cpp
@@ -365,11 +365,11 @@ void SendRpcResponse(int64_t correlation_id, Controller*
cntl,
meta.set_attachment_size(attached_size);
}
StreamId response_stream_id = INVALID_STREAM_ID;
- SocketUniquePtr stream_ptr;
+ StreamUniquePtr stream_ptr;
if (!response_stream_ids.empty()) {
response_stream_id = response_stream_ids[0];
- if (Socket::Address(response_stream_id, &stream_ptr) == 0) {
- Stream* s = (Stream *) stream_ptr->conn();
+ if (Stream::Address(response_stream_id, &stream_ptr) == 0) {
+ Stream* s = stream_ptr.get();
StreamSettings *stream_settings = meta.mutable_stream_settings();
s->FillSettings(stream_settings);
s->SetHostSocket(sock);
@@ -431,13 +431,13 @@ void SendRpcResponse(int64_t correlation_id, Controller*
cntl,
// written user data would follower the RPC response.
// Reuse stream_ptr to avoid address first stream id again
if (stream_ptr) {
- ((Stream*)stream_ptr->conn())->SetConnected();
+ stream_ptr->SetConnected();
}
for (size_t i = 1; i < response_stream_ids.size(); ++i) {
StreamId extra_stream_id = response_stream_ids[i];
- SocketUniquePtr extra_stream_ptr;
- if (Socket::Address(extra_stream_id, &extra_stream_ptr) == 0) {
- Stream* extra_stream = (Stream *) extra_stream_ptr->conn();
+ StreamUniquePtr extra_stream_ptr;
+ if (Stream::Address(extra_stream_id, &extra_stream_ptr) == 0) {
+ Stream* extra_stream = extra_stream_ptr.get();
extra_stream->SetHostSocket(sock);
extra_stream->SetConnected();
} else {
@@ -1129,12 +1129,12 @@ void PackRpcRequest(butil::IOBuf* req_buf,
if (!request_stream_ids.empty()) {
StreamSettings* stream_settings = meta.mutable_stream_settings();
StreamId request_stream_id = request_stream_ids[0];
- SocketUniquePtr ptr;
- if (Socket::Address(request_stream_id, &ptr) != 0) {
+ StreamUniquePtr ptr;
+ if (Stream::Address(request_stream_id, &ptr) != 0) {
return cntl->SetFailed(EREQUEST, "Stream=%" PRIu64 " was closed",
request_stream_id);
}
- Stream* s = (Stream*) ptr->conn();
+ Stream* s = ptr.get();
s->FillSettings(stream_settings);
for (size_t i = 1; i < request_stream_ids.size(); ++i) {
stream_settings->mutable_extra_stream_ids()->Add(request_stream_ids[i]);
diff --git a/src/brpc/policy/streaming_rpc_protocol.cpp
b/src/brpc/policy/streaming_rpc_protocol.cpp
index b741acff..429d2bc2 100644
--- a/src/brpc/policy/streaming_rpc_protocol.cpp
+++ b/src/brpc/policy/streaming_rpc_protocol.cpp
@@ -102,11 +102,11 @@ ParseResult ParseStreamingMessage(butil::IOBuf* source,
LOG(WARNING) << "Fail to Parse StreamFrameMeta from " << *socket;
break;
}
- SocketUniquePtr ptr;
- if (Socket::Address((SocketId)fm.stream_id(), &ptr) != 0) {
- RPC_VLOG_IF(fm.frame_type() != FRAME_TYPE_RST
- && fm.frame_type() != FRAME_TYPE_CLOSE
- && fm.frame_type() != FRAME_TYPE_FEEDBACK)
+ StreamUniquePtr sptr;
+ if (Stream::Address((StreamId)fm.stream_id(), &sptr) != 0) {
+ RPC_VLOG_IF(fm.frame_type() != FRAME_TYPE_RST &&
+ fm.frame_type() != FRAME_TYPE_CLOSE &&
+ fm.frame_type() != FRAME_TYPE_FEEDBACK)
<< "Fail to find stream=" << fm.stream_id();
// It's normal that the stream is closed before receiving feedback
frames from peer.
// In this case, RST frame should not be sent to peer, otherwise
on-fly data can be lost.
@@ -116,16 +116,7 @@ ParseResult ParseStreamingMessage(butil::IOBuf* source,
break;
}
meta_buf.clear(); // to reduce memory resident
- // ptr->conn() returns the connection-level context attached to the
- // socket. It may be NULL when the socket was found by ID but has no
- // Stream object associated (e.g. during protocol probing or fuzz
- // testing). Calling OnReceived on a null pointer would crash.
- Stream* stream_conn = (Stream*)ptr->conn();
- if (stream_conn == NULL) {
- LOG(FATAL) << "No stream object found";
- break;
- }
- stream_conn->OnReceived(fm, &payload, socket);
+ sptr->OnReceived(fm, &payload, socket);
} while (0);
// Hack input messenger
@@ -136,7 +127,7 @@ void ProcessStreamingMessage(InputMessageBase* /*msg*/) {
CHECK(false) << "Should never be called";
}
-void SendStreamRst(Socket *sock, int64_t remote_stream_id) {
+void SendStreamRst(Socket* sock, int64_t remote_stream_id) {
CHECK(sock != NULL);
StreamFrameMeta fm;
fm.set_stream_id(remote_stream_id);
@@ -148,7 +139,7 @@ void SendStreamRst(Socket *sock, int64_t remote_stream_id) {
sock->Write(&out, &wopt);
}
-void SendStreamClose(Socket *sock, int64_t remote_stream_id,
+void SendStreamClose(Socket* sock, int64_t remote_stream_id,
int64_t source_stream_id) {
CHECK(sock != NULL);
StreamFrameMeta fm;
diff --git a/src/brpc/socket.h b/src/brpc/socket.h
index 7c530589..3bc90918 100644
--- a/src/brpc/socket.h
+++ b/src/brpc/socket.h
@@ -353,7 +353,7 @@ public:
// NOTE: User cannot create Socket from constructor. Use Create()
// instead. It's public just because of requirement of ResourcePool.
explicit Socket(Forbidden);
- ~Socket() override;
+ ~Socket();
// Write `msg' into this Socket and clear it. The `msg' should be an
// intact request or response. To prevent messages from interleaving
diff --git a/src/brpc/stream.cpp b/src/brpc/stream.cpp
index 2667614d..c799f2ff 100644
--- a/src/brpc/stream.cpp
+++ b/src/brpc/stream.cpp
@@ -42,23 +42,21 @@ BRPC_VALIDATE_GFLAG(stream_write_max_segment_size,
PositiveInteger);
const static butil::IOBuf *TIMEOUT_TASK = (butil::IOBuf*)-1L;
-Stream::Stream()
- : _host_socket(NULL)
- , _fake_socket_weak_ref(NULL)
+Stream::Stream(Forbidden f)
+ : VersionedRefWithId<Stream>(f)
+ , _host_socket(NULL)
, _connected(false)
- , _closed(false)
, _error_code(0)
, _produced(0)
, _remote_consumed(0)
+ , _socket_unconsumed_size(0)
, _cur_buf_size(0)
, _local_consumed(0)
, _atomic_local_consumed(0)
, _parse_rpc_response(false)
, _pending_buf(NULL)
, _start_idle_timer_us(0)
- , _idle_timer(0)
-{
- _connect_meta.on_connect = NULL;
+ , _idle_timer(0) {
CHECK_EQ(0, bthread_mutex_init(&_connect_mutex, NULL));
CHECK_EQ(0, bthread_mutex_init(&_congestion_control_mutex, NULL));
}
@@ -72,289 +70,262 @@ Stream::~Stream() {
CHECK(_host_socket == NULL);
bthread_mutex_destroy(&_connect_mutex);
bthread_mutex_destroy(&_congestion_control_mutex);
- bthread_id_list_destroy(&_writable_wait_list);
}
int Stream::Create(const StreamOptions &options,
- const StreamSettings *remote_settings,
+ const StreamSettings* remote_settings,
StreamId *id, bool parse_rpc_response) {
- Stream* s = new Stream();
- s->_host_socket = NULL;
- s->_fake_socket_weak_ref = NULL;
- s->_connected = false;
- s->_options = options;
- s->_closed = false;
- s->_error_code = 0;
- s->_cur_buf_size = options.max_buf_size > 0 ? options.max_buf_size : 0;
+ return VersionedRefWithId<Stream>::Create(
+ id, options, remote_settings, parse_rpc_response);
+}
+
+int Stream::OnCreated(const StreamOptions& options,
+ const StreamSettings* remote_settings,
+ bool parse_rpc_response) {
+ _host_socket = NULL;
+ _connected.store(false, butil::memory_order_relaxed);
+ _options = options;
+ _error_code = 0;
+ _error_text.clear();
+ _pending_writes.clear();
+ _produced = 0;
+ _remote_consumed = 0;
+ _socket_unconsumed_size = 0;
+ _local_consumed = 0;
+ _atomic_local_consumed.store(0, butil::memory_order_relaxed);
+ _parse_rpc_response = parse_rpc_response;
+ _pending_buf = NULL;
+ _start_idle_timer_us = 0;
+ _idle_timer = 0;
+ _remote_settings.Clear();
+
+ _cur_buf_size = options.max_buf_size > 0 ? options.max_buf_size : 0;
if (options.max_buf_size > 0 && options.min_buf_size >
options.max_buf_size) {
// set 0 if min_buf_size is invalid.
- s->_options.min_buf_size = 0;
+ _options.min_buf_size = 0;
LOG(WARNING) << "options.min_buf_size is larger than
options.max_buf_size, it will be set to 0.";
}
- if (FLAGS_socket_max_streams_unconsumed_bytes > 0 &&
s->_options.min_buf_size > 0) {
- s->_cur_buf_size = s->_options.min_buf_size;
+ if (FLAGS_socket_max_streams_unconsumed_bytes > 0 && _options.min_buf_size
> 0) {
+ _cur_buf_size = _options.min_buf_size;
}
if (remote_settings != NULL) {
- s->_remote_settings.MergeFrom(*remote_settings);
- }
- s->_parse_rpc_response = parse_rpc_response;
- if (bthread_id_list_init(&s->_writable_wait_list, 8, 8/*FIXME*/)) {
- delete s;
- return -1;
+ _remote_settings.MergeFrom(*remote_settings);
}
+
+ CHECK_EQ(0, bthread_id_list_init(&_writable_wait_list, 8, 8/*FIXME*/));
+
bthread::ExecutionQueueOptions q_opt;
q_opt.bthread_attr
= FLAGS_usercode_in_pthread ? BTHREAD_ATTR_PTHREAD :
BTHREAD_ATTR_NORMAL;
- if (bthread::execution_queue_start(&s->_consumer_queue, &q_opt, Consume,
s) != 0) {
+ if (bthread::execution_queue_start(&_consumer_queue, &q_opt, Consume,
this) != 0) {
LOG(FATAL) << "Fail to create ExecutionQueue";
- delete s;
return -1;
}
- SocketOptions sock_opt;
- sock_opt.conn = s;
- SocketId fake_sock_id;
- if (Socket::Create(sock_opt, &fake_sock_id) != 0) {
- s->BeforeRecycle(NULL);
- return -1;
- }
- SocketUniquePtr ptr;
- CHECK_EQ(0, Socket::Address(fake_sock_id, &ptr));
- s->_fake_socket_weak_ref = ptr.get();
- s->_id = fake_sock_id;
- *id = s->id();
+
+ // The consumer queue holds one reference to this Stream.
+ AddReference();
return 0;
}
-void Stream::BeforeRecycle(Socket *) {
- // No one holds reference now, so we don't need lock here
- bthread_id_list_reset(&_writable_wait_list, ECONNRESET);
- if (_connected) {
- // Send CLOSE frame
- RPC_VLOG << "Send close frame";
- CHECK(_host_socket != NULL);
- policy::SendStreamClose(_host_socket,
- _remote_settings.stream_id(), id());
+void Stream::OnFailed(int error_code, const std::string& error_text) {
+ bool connected = false;
+ {
+ // Record the error for on_failed callback fired in Consume(), and
discard
+ // any writes buffered before connecting.
+ BAIDU_SCOPED_LOCK(_connect_mutex);
+ _error_code = error_code;
+ _error_text = error_text;
+ connected = _connected.load(butil::memory_order_relaxed);
+ _pending_writes.clear();
}
- if (_host_socket) {
- _host_socket->RemoveStream(id());
+ // Wake up all threads blocked on writable.
+ bthread_id_list_reset(&_writable_wait_list, ECONNRESET);
+
+ // Serialize the host Socket membership removal with SetHostSocket().
+ // SetFailed() marks this Stream failed before entering OnFailed(), so a
+ // later SetHostSocket() observes Failed() and cannot add it back.
+ {
+ BAIDU_SCOPED_LOCK(_connect_mutex);
+ if (connected) {
+ RPC_VLOG << "Send close frame";
+ CHECK(_host_socket != NULL);
+ policy::SendStreamClose(
+ _host_socket, _remote_settings.stream_id(), id());
+ }
+ if (_host_socket != NULL) {
+ if (FLAGS_socket_max_streams_unconsumed_bytes > 0) {
+ BAIDU_SCOPED_LOCK(_congestion_control_mutex);
+ if (_socket_unconsumed_size != 0) {
+ _host_socket->_total_streams_unconsumed_size.fetch_sub(
+ _socket_unconsumed_size, butil::memory_order_relaxed);
+ _socket_unconsumed_size = 0;
+ }
+ }
+ _host_socket->RemoveStream(id());
+ }
}
- // The instance is to be deleted in the consumer thread
+ // Stop the consumer queue. Consume() will fire on_failed/on_closed and
+ // release the reference held by the queue, which may recycle this
instance.
bthread::execution_queue_stop(_consumer_queue);
}
-ssize_t Stream::CutMessageIntoFileDescriptor(int /*fd*/,
- butil::IOBuf **data_list,
- size_t size) {
+void Stream::BeforeRecycled() {
+ if (_pending_buf != NULL) {
+ delete _pending_buf;
+ _pending_buf = NULL;
+ }
+
+ _pending_writes.clear();
+ bthread_id_list_destroy(&_writable_wait_list);
+ if (_host_socket != NULL) {
+ DereferenceSocket(_host_socket);
+ _host_socket = NULL;
+ }
+}
+
+std::string Stream::OnDescription() const {
+ BAIDU_SCOPED_LOCK(_connect_mutex);
+ if (_host_socket != NULL) {
+ return _host_socket->description();
+ } else {
+ return "host_socket=NULL";
+ }
+}
+
+int Stream::WritePacked(const butil::IOBuf& data,
+ const StreamWriteOptions* options) {
if (_host_socket == NULL) {
CHECK(false) << "Not connected";
errno = EBADF;
return -1;
}
if (!_remote_settings.writable()) {
- LOG(WARNING) << "The remote side of Stream=" << id()
+ LOG(WARNING) << "The remote side of Stream=" << id()
<< "->" << _remote_settings.stream_id()
<< "@" << _host_socket->remote_side()
<< " doesn't have a handler";
errno = EBADF;
return -1;
}
- butil::IOBuf out;
- ssize_t len = 0;
- ssize_t unwritten_data_size = 0;
- for (size_t i = 0; i < size; ++i) {
- butil::IOBuf *data = data_list[i];
- size_t length = data->length();
- if (length > FLAGS_stream_write_max_segment_size) {
- if (unwritten_data_size) {
- WriteToHostSocket(&out);
- unwritten_data_size = 0;
- out.clear();
- }
- // segmenting large data into multiple parts
- butil::IOBuf segment_buf;
- bool has_continuation = true;
- while (has_continuation) {
- data->cutn(&segment_buf, FLAGS_stream_write_max_segment_size);
- StreamFrameMeta fm;
- fm.set_stream_id(_remote_settings.stream_id());
- fm.set_source_stream_id(id());
- fm.set_frame_type(FRAME_TYPE_DATA);
- has_continuation = !data->empty();
- fm.set_has_continuation(has_continuation);
- policy::PackStreamMessage(&out, fm, &segment_buf);
- len += segment_buf.length();
- segment_buf.clear();
- WriteToHostSocket(&out);
- out.clear();
- }
- } else {
- if (unwritten_data_size + length >
FLAGS_stream_write_max_segment_size) {
- WriteToHostSocket(&out);
- unwritten_data_size = 0;
- out.clear();
- }
- unwritten_data_size += length;
- StreamFrameMeta fm;
- fm.set_stream_id(_remote_settings.stream_id());
- fm.set_source_stream_id(id());
- fm.set_frame_type(FRAME_TYPE_DATA);
- fm.set_has_continuation(false);
- policy::PackStreamMessage(&out, fm, data_list[i]);
- len += length;
- data_list[i]->clear();
- }
- }
-
- if (!out.empty()) {
- WriteToHostSocket(&out);
- }
- return len;
-}
-
-void Stream::WriteToHostSocket(butil::IOBuf* b) {
- BRPC_HANDLE_EOVERCROWDED(_host_socket->Write(b));
-}
-
-ssize_t Stream::CutMessageIntoSSLChannel(SSL*, butil::IOBuf**, size_t) {
- CHECK(false) << "Stream does support SSL";
- errno = EINVAL;
- return -1;
-}
-void* Stream::RunOnConnect(void *arg) {
- ConnectMeta* meta = (ConnectMeta*)arg;
- if (meta->ec == 0) {
- meta->on_connect(Socket::STREAM_FAKE_FD, 0, meta->arg);
- } else {
- meta->on_connect(-1, meta->ec, meta->arg);
- }
- delete meta;
- return NULL;
-}
+ Socket::WriteOptions wopt;
+ wopt.write_in_background = options != NULL && options->write_in_background;
-int Stream::Connect(Socket* ptr, const timespec*,
- int (*on_connect)(int, int, void *), void *data) {
- CHECK_EQ(ptr->id(), _id);
- bthread_mutex_lock(&_connect_mutex);
- if (_connect_meta.on_connect != NULL) {
- CHECK(false) << "Connect is supposed to be called once";
- bthread_mutex_unlock(&_connect_mutex);
+ // Pack the whole message (splitting large data into multiple STRM frames)
+ // into a SINGLE IOBuf, then hand it to Socket::Write in one shot.
+ butil::IOBuf remaining(data);
+ butil::IOBuf out;
+ bool has_continuation = true;
+ do {
+ butil::IOBuf segment;
+ remaining.cutn(&segment, FLAGS_stream_write_max_segment_size);
+ has_continuation = !remaining.empty();
+ StreamFrameMeta fm;
+ fm.set_stream_id(_remote_settings.stream_id());
+ fm.set_source_stream_id(id());
+ fm.set_frame_type(FRAME_TYPE_DATA);
+ fm.set_has_continuation(has_continuation);
+ policy::PackStreamMessage(&out, fm, &segment);
+ } while (has_continuation);
+
+ if (BRPC_HANDLE_EOVERCROWDED(_host_socket->Write(&out, &wopt)) != 0) {
+ // Stream may be closed by peer before.
+ LOG(WARNING) << "Fail to write to host socket of stream=" << id()
+ << ", " << berror();
return -1;
}
- _connect_meta.on_connect = on_connect;
- _connect_meta.arg = data;
- if (_connected) {
- ConnectMeta* meta = new ConnectMeta;
- meta->on_connect = _connect_meta.on_connect;
- meta->arg = _connect_meta.arg;
- meta->ec = _connect_meta.ec;
- bthread_mutex_unlock(&_connect_mutex);
- bthread_t tid;
- if (bthread_start_urgent(&tid, &BTHREAD_ATTR_NORMAL, RunOnConnect,
meta) != 0) {
- LOG(FATAL) << "Fail to start bthread, " << berror();
- RunOnConnect(meta);
- }
- return 0;
- }
- bthread_mutex_unlock(&_connect_mutex);
return 0;
}
-void Stream::SetConnected() {
- return SetConnected(NULL);
-}
-
-void Stream::SetConnected(const StreamSettings* remote_settings) {
- bthread_mutex_lock(&_connect_mutex);
- if (_closed) {
- bthread_mutex_unlock(&_connect_mutex);
- return;
- }
- if (_connected) {
- CHECK(false);
- bthread_mutex_unlock(&_connect_mutex);
- return;
- }
- CHECK(_host_socket != NULL);
- if (remote_settings != NULL) {
- CHECK(!_remote_settings.IsInitialized());
- _remote_settings.MergeFrom(*remote_settings);
- } else {
- CHECK(_remote_settings.IsInitialized());
- }
- CHECK(_host_socket != NULL);
- RPC_VLOG << "stream=" << id() << " is connected to stream_id="
- << _remote_settings.stream_id() << " at host_socket=" <<
*_host_socket;
- _connected.store(true, butil::memory_order_release);
- _connect_meta.ec = 0;
- TriggerOnConnectIfNeed();
- if (remote_settings == NULL) {
- // Start the timer at server-side
- // Client-side timer would triggered in Consume after received the
first
- // message which is the very RPC response
- StartIdleTimer();
- } else {
- // send first feedback for client-side stream if it already consumed
data
- if (_remote_settings.need_feedback()) {
- auto consumed_bytes =
_atomic_local_consumed.load(butil::memory_order_acquire);
- if (consumed_bytes > 0)
- SendFeedback(consumed_bytes);
- }
- }
+void Stream::WriteToHostSocket(butil::IOBuf* b) {
+ BRPC_HANDLE_EOVERCROWDED(_host_socket->Write(b));
}
-void Stream::TriggerOnConnectIfNeed() {
- if (_connect_meta.on_connect != NULL) {
- ConnectMeta* meta = new ConnectMeta;
- meta->on_connect = _connect_meta.on_connect;
- meta->arg = _connect_meta.arg;
- meta->ec = _connect_meta.ec;
- bthread_mutex_unlock(&_connect_mutex);
- bthread_t tid;
- if (bthread_start_urgent(&tid, &BTHREAD_ATTR_NORMAL, RunOnConnect,
meta) != 0) {
- LOG(FATAL) << "Fail to start bthread, " << berror();
- RunOnConnect(meta);
- }
- return;
+inline void Stream::RollbackProduced(size_t data_length) {
+ if (_cur_buf_size > 0) {
+ BAIDU_SCOPED_LOCK(_congestion_control_mutex);
+ _produced -= data_length;
}
- bthread_mutex_unlock(&_connect_mutex);
}
int Stream::AppendIfNotFull(const butil::IOBuf &data,
const StreamWriteOptions* options) {
+ if (Failed()) {
+ errno = ECONNRESET;
+ return -1;
+ }
+
+ size_t data_length = data.length();
if (_cur_buf_size > 0) {
std::unique_lock<bthread_mutex_t> lck(_congestion_control_mutex);
if (_produced >= _remote_consumed + _cur_buf_size) {
const size_t saved_produced = _produced;
const size_t saved_remote_consumed = _remote_consumed;
lck.unlock();
- RPC_VLOG << "Stream=" << _id << " is full"
+ RPC_VLOG << "Stream=" << id() << " is full"
<< "_produced=" << saved_produced
<< " _remote_consumed=" << saved_remote_consumed
<< " gap=" << saved_produced - saved_remote_consumed
<< " max_buf_size=" << _cur_buf_size;
return 1;
}
- _produced += data.length();
+ _produced += data_length;
}
- size_t data_length = data.length();
- butil::IOBuf copied_data(data);
- Socket::WriteOptions wopt;
- wopt.write_in_background = options != NULL && options->write_in_background;
- const int rc = _fake_socket_weak_ref->Write(&copied_data, &wopt);
- if (rc != 0) {
- // Stream may be closed by peer before
- LOG(WARNING) << "Fail to write to _fake_socket, " << berror();
- BAIDU_SCOPED_LOCK(_congestion_control_mutex);
- _produced -= data_length;
+ // Fast path (the common case): once connected, write directly WITHOUT
+ // taking _connect_mutex. `_connected` is a one-way transition published
+ // by SetConnected() after flushing pending writes, so ordering is
preserved
+ // and this path stays lock-free (besides the optional congestion window).
+ if (_connected.load(butil::memory_order_acquire)) {
+ if (WritePacked(data, options) != 0) {
+ RollbackProduced(data_length);
+ return -1;
+ }
+ if (FLAGS_socket_max_streams_unconsumed_bytes > 0) {
+ BAIDU_SCOPED_LOCK(_congestion_control_mutex);
+ if (!Failed()) {
+ _host_socket->_total_streams_unconsumed_size.fetch_add(
+ data_length, butil::memory_order_relaxed);
+ _socket_unconsumed_size += data_length;
+ }
+ }
+ return 0;
+ }
+
+ // Slow path (rare): not connected yet, so the remote stream id is unknown.
+ // Buffer the raw data and options under `_connect_mutex`. SetConnected()
+ // will flush it.
+ {
+ BAIDU_SCOPED_LOCK(_connect_mutex);
+ if (Failed()) {
+ RollbackProduced(data_length);
+ return -1;
+ }
+ if (!_connected.load(butil::memory_order_acquire)) {
+ _pending_writes.emplace_back(data, options);
+ return 0;
+ }
+ // Connected between the two checks; fall through to a direct write.
+ // Ordering holds: reaching here means we acquired `_connect_mutex`,
+ // which SetConnected() releases only after it has flushed all pending
+ // writes (enqueued into the host socket) and published
`_connected=true`.
+ // Hence, this direct write is necessarily enqueued after those
pending writes.
+ }
+
+ if (WritePacked(data, options) != 0) {
+ RollbackProduced(data_length);
return -1;
}
if (FLAGS_socket_max_streams_unconsumed_bytes > 0) {
- _host_socket->_total_streams_unconsumed_size += data_length;
+ BAIDU_SCOPED_LOCK(_congestion_control_mutex);
+ if (!Failed()) {
+ _host_socket->_total_streams_unconsumed_size.fetch_add(
+ data_length, butil::memory_order_relaxed);
+ _socket_unconsumed_size += data_length;
+ }
}
return 0;
}
@@ -362,7 +333,7 @@ int Stream::AppendIfNotFull(const butil::IOBuf &data,
void Stream::SetRemoteConsumed(size_t new_remote_consumed) {
CHECK(_cur_buf_size > 0);
bthread_id_list_t tmplist;
- bthread_id_list_init(&tmplist, 0, 0);
+ CHECK_EQ(0, bthread_id_list_init(&tmplist, 0, 0));
bthread_mutex_lock(&_congestion_control_mutex);
if (_remote_consumed >= new_remote_consumed) {
bthread_mutex_unlock(&_congestion_control_mutex);
@@ -370,16 +341,28 @@ void Stream::SetRemoteConsumed(size_t
new_remote_consumed) {
}
const bool was_full = _produced >= _remote_consumed + _cur_buf_size;
- if (FLAGS_socket_max_streams_unconsumed_bytes > 0) {
- _host_socket->_total_streams_unconsumed_size -= new_remote_consumed -
_remote_consumed;
- if (_host_socket->_total_streams_unconsumed_size >
FLAGS_socket_max_streams_unconsumed_bytes) {
+ if (FLAGS_socket_max_streams_unconsumed_bytes > 0 && _host_socket != NULL)
{
+ const size_t consumed_delta = new_remote_consumed - _remote_consumed;
+ const size_t accounted_delta =
+ std::min(consumed_delta, _socket_unconsumed_size);
+ if (accounted_delta != 0) {
+ _host_socket->_total_streams_unconsumed_size.fetch_sub(
+ accounted_delta, butil::memory_order_relaxed);
+ _socket_unconsumed_size -= accounted_delta;
+ }
+ const int64_t total_unconsumed =
_host_socket->_total_streams_unconsumed_size.load(
+ butil::memory_order_relaxed);
+ if (total_unconsumed > FLAGS_socket_max_streams_unconsumed_bytes) {
if (_options.min_buf_size > 0) {
_cur_buf_size = _options.min_buf_size;
} else {
_cur_buf_size /= 2;
}
- LOG(INFO) << "stream consumers on socket " << _host_socket->id()
<< " is crowded, " << "cut stream " << id() << " buffer to " << _cur_buf_size;
- } else if (_produced >= new_remote_consumed + _cur_buf_size &&
(_options.max_buf_size <= 0 || _cur_buf_size < (size_t)_options.max_buf_size)) {
+ LOG(INFO) << "stream consumers on socket " << _host_socket->id()
+ << " is crowded, cut stream " << id()
+ << " buffer to " << _cur_buf_size;
+ } else if (_produced >= new_remote_consumed + _cur_buf_size &&
+ (_options.max_buf_size <= 0 || _cur_buf_size <
(size_t)_options.max_buf_size)) {
if (_options.max_buf_size > 0 && _cur_buf_size * 2 >
(size_t)_options.max_buf_size) {
_cur_buf_size = _options.max_buf_size;
} else {
@@ -496,15 +479,123 @@ int Stream::Wait(const timespec* due_time) {
return rc;
}
+void Stream::SetConnected() {
+ return SetConnected(NULL);
+}
+
+void Stream::SetConnected(const StreamSettings* remote_settings) {
+ bthread_mutex_lock(&_connect_mutex);
+ if (Failed()) {
+ bthread_mutex_unlock(&_connect_mutex);
+ return;
+ }
+ if (_connected.load(butil::memory_order_relaxed)) {
+ // SetConnected() may be driven more than once (and concurrently) for
+ // the same stream, notably for extra streams in batch creation. It
must
+ // be idempotent: guarded by _connect_mutex, only the first call takes
+ // effect and later calls simply return.
+ bthread_mutex_unlock(&_connect_mutex);
+ return;
+ }
+ CHECK(_host_socket != NULL);
+ if (remote_settings != NULL) {
+ CHECK(!_remote_settings.IsInitialized());
+ _remote_settings.MergeFrom(*remote_settings);
+ } else {
+ CHECK(_remote_settings.IsInitialized());
+ }
+ RPC_VLOG << "stream=" << id() << " is connected to stream_id="
+ << _remote_settings.stream_id() << " at host_socket=" <<
*_host_socket;
+
+ // Flush writes buffered before connecting FIRST, while _connected is still
+ // false so concurrent AppendIfNotFull() take the slow path and block on
+ // _connect_mutex. Only after flushing do we publish _connected=true, so
+ // subsequent lock-free fast-path writes are strictly ordered after these
+ // pending writes.
+ std::vector<PendingWrite> pending;
+ pending.swap(_pending_writes);
+ for (size_t i = 0; i < pending.size(); ++i) {
+ if (Failed()) {
+ size_t unsent_size = 0;
+ for (size_t j = i; j < pending.size(); ++j) {
+ unsent_size += pending[j].data.length();
+ }
+ RollbackProduced(unsent_size);
+ bthread_mutex_unlock(&_connect_mutex);
+ return;
+ }
+
+ size_t len = pending[i].data.length();
+ if (WritePacked(pending[i].data, &pending[i].options) != 0) {
+ int error_code = errno != 0 ? errno : EIO;
+ // The congestion window accounted for every pending write when it
+ // was accepted. Keep the successfully enqueued prefix accounted,
+ // but roll back the failed write and the unsent suffix.
+ size_t unsent_size = 0;
+ for (size_t j = i; j < pending.size(); ++j) {
+ unsent_size += pending[j].data.length();
+ }
+ RollbackProduced(unsent_size);
+ bthread_mutex_unlock(&_connect_mutex);
+ VersionedRefWithId<Stream>::SetFailed(
+ error_code, "Failed to flush pending writes during
connection");
+ return;
+ }
+ if (FLAGS_socket_max_streams_unconsumed_bytes > 0) {
+ BAIDU_SCOPED_LOCK(_congestion_control_mutex);
+ if (!Failed()) {
+ _host_socket->_total_streams_unconsumed_size.fetch_add(
+ len, butil::memory_order_relaxed);
+ _socket_unconsumed_size += len;
+ }
+ }
+ }
+
+ // Check both before and after publishing. The second check closes the
+ // window in which SetFailed() can bump the version between the first check
+ // and the store. If failure happens after the second check, connection was
+ // published first and OnFailed() will observe and close it normally.
+ if (Failed()) {
+ bthread_mutex_unlock(&_connect_mutex);
+ return;
+ }
+ _connected.store(true, butil::memory_order_release);
+ if (Failed()) {
+ _connected.store(false, butil::memory_order_relaxed);
+ bthread_mutex_unlock(&_connect_mutex);
+ return;
+ }
+ bthread_mutex_unlock(&_connect_mutex);
+
+ if (remote_settings == NULL) {
+ // Start the timer at server-side
+ // Client-side timer would triggered in Consume after received the
first
+ // message which is the very RPC response
+ StartIdleTimer();
+ } else {
+ // send first feedback for client-side stream if it already consumed
data
+ if (_remote_settings.need_feedback()) {
+ auto consumed_bytes =
_atomic_local_consumed.load(butil::memory_order_acquire);
+ if (consumed_bytes > 0)
+ SendFeedback(consumed_bytes);
+ }
+ }
+}
+
int Stream::OnReceived(const StreamFrameMeta& fm, butil::IOBuf *buf, Socket*
sock) {
- if (_host_socket == NULL) {
+ if (!_connected.load(butil::memory_order_acquire)) {
+ // Before connection is published, let the locked slow path initialize
+ // the host socket or confirm that another thread already did so.
if (SetHostSocket(sock) != 0) {
return -1;
}
}
+
switch (fm.frame_type()) {
case FRAME_TYPE_FEEDBACK:
- SetRemoteConsumed(fm.feedback().consumed_size());
+ if (_connected.load(butil::memory_order_acquire)) {
+ SetRemoteConsumed(fm.feedback().consumed_size());
+ }
CHECK(buf->empty());
break;
case FRAME_TYPE_DATA:
@@ -516,7 +607,7 @@ int Stream::OnReceived(const StreamFrameMeta& fm,
butil::IOBuf *buf, Socket* soc
_pending_buf->swap(*buf);
}
if (!fm.has_continuation()) {
- butil::IOBuf *tmp = _pending_buf;
+ butil::IOBuf* tmp = _pending_buf;
_pending_buf = NULL;
int rc = bthread::execution_queue_execute(_consumer_queue, tmp);
if (rc != 0) {
@@ -583,12 +674,10 @@ int Stream::Consume(void *meta,
bthread::TaskIterator<butil::IOBuf*>& iter) {
Stream* s = (Stream*)meta;
s->StopIdleTimer();
if (iter.is_queue_stopped()) {
- scoped_ptr<Stream> recycled_stream(s);
- // Indicating the queue was closed.
- if (s->_host_socket) {
- DereferenceSocket(s->_host_socket);
- s->_host_socket = NULL;
- }
+ // The consumer queue is stopped (the stream was SetFailed). Fire the
+ // user callbacks, then release the reference held by the queue (which
+ // was added in OnCreated). This may recycle the instance via
+ // BeforeRecycled(), so do not touch `s' afterwards.
if (s->_options.handler != NULL) {
int error_code;
std::string error_text;
@@ -603,8 +692,10 @@ int Stream::Consume(void *meta,
bthread::TaskIterator<butil::IOBuf*>& iter) {
}
s->_options.handler->on_closed(s->id());
}
+ DereferenceVersionedRefWithId(s);
return 0;
}
+
DEFINE_SMALL_ARRAY(butil::IOBuf*, buf_list, s->_options.messages_in_batch,
256);
MessageBatcher mb(buf_list, s->_options.messages_in_batch, s);
bool has_timeout_task = false;
@@ -661,18 +752,24 @@ void Stream::SendFeedback(int64_t _consumed_bytes) {
WriteToHostSocket(&out);
}
-int Stream::SetHostSocket(Socket *host_socket) {
- std::call_once(_set_host_socket_flag, [this, host_socket]() {
- SocketUniquePtr ptr;
- host_socket->ReAddress(&ptr);
- // TODO add *this to host socke
- if (ptr->AddStream(id()) != 0) {
- CHECK(false) << id() << " fail to add stream to host socket";
- return;
- }
- _host_socket = ptr.release();
- });
- return _host_socket != NULL ? 0 : -1;
+int Stream::SetHostSocket(Socket* host_socket) {
+ BAIDU_SCOPED_LOCK(_connect_mutex);
+ if (Failed()) {
+ return -1;
+ }
+ if (_host_socket != NULL) {
+ return 0;
+ }
+
+ SocketUniquePtr ptr;
+ host_socket->ReAddress(&ptr);
+ if (ptr->AddStream(id()) != 0) {
+ CHECK(false) << id() << " fail to add stream to host socket";
+ return -1;
+ }
+
+ _host_socket = ptr.release();
+ return 0;
}
void Stream::FillSettings(StreamSettings *settings) {
@@ -707,49 +804,50 @@ void Stream::StopIdleTimer() {
}
}
-void Stream::Close(int error_code, const char* reason_fmt, ...) {
- _fake_socket_weak_ref->SetFailed();
- bthread_mutex_lock(&_connect_mutex);
- if (_closed) {
- bthread_mutex_unlock(&_connect_mutex);
+void Stream::CloseV(int error_code, const char* reason_fmt, va_list ap) {
+ if (Failed()) {
return;
}
- _closed = true;
- _error_code = error_code;
+ std::string error_text;
+ butil::string_vappendf(&error_text, reason_fmt, ap);
+ VersionedRefWithId<Stream>::SetFailed(error_code, error_text);
+}
+
+void Stream::Close(int error_code, const char* reason_fmt, ...) {
va_list ap;
va_start(ap, reason_fmt);
- butil::string_vappendf(&_error_text, reason_fmt, ap);
+ CloseV(error_code, reason_fmt, ap);
va_end(ap);
+}
- if (_connected) {
- bthread_mutex_unlock(&_connect_mutex);
- return;
+int Stream::SetFailedV(StreamId id, int error_code,
+ const char* reason_fmt, va_list ap) {
+ StreamUniquePtr stream_ptr;
+ if (AddressFailedAsWell(id, &stream_ptr) == -1) {
+ // Don't care recycled stream.
+ return 0;
}
- _connect_meta.ec = ECONNRESET;
- // Trigger on connect to release the reference of socket
- return TriggerOnConnectIfNeed();
+ stream_ptr->CloseV(error_code, reason_fmt, ap);
+ return 0;
}
int Stream::SetFailed(StreamId id, int error_code, const char* reason_fmt,
...) {
- SocketUniquePtr ptr;
- if (Socket::AddressFailedAsWell(id, &ptr) == -1) {
- // Don't care recycled stream
- return 0;
- }
- Stream* s = (Stream*)ptr->conn();
va_list ap;
va_start(ap, reason_fmt);
- s->Close(error_code, reason_fmt, ap);
+ int rc = SetFailedV(id, error_code, reason_fmt, ap);
va_end(ap);
- return 0;
+ return rc;
}
int Stream::SetFailed(const StreamIds& ids, int error_code, const char*
reason_fmt, ...) {
va_list ap;
va_start(ap, reason_fmt);
- for(size_t i = 0; i< ids.size(); ++i) {
- Stream::SetFailed(ids[i], error_code, reason_fmt, ap);
+ for (auto id : ids) {
+ va_list ap_copy;
+ va_copy(ap_copy, ap);
+ SetFailedV(id, error_code, reason_fmt, ap_copy);
+ va_end(ap_copy);
}
va_end(ap);
return 0;
@@ -779,13 +877,13 @@ void Stream::HandleRpcResponse(butil::IOBuf*
response_buffer) {
policy::ProcessRpcResponse(msg);
}
-int StreamWrite(StreamId stream_id, const butil::IOBuf &message,
+int StreamWrite(StreamId stream_id, const butil::IOBuf& message,
const StreamWriteOptions* options) {
- SocketUniquePtr ptr;
- if (Socket::Address(stream_id, &ptr) != 0) {
+ StreamUniquePtr stream_ptr;
+ if (Stream::Address(stream_id, &stream_ptr) != 0) {
return EINVAL;
}
- Stream* s = (Stream*)ptr->conn();
+ Stream* s = stream_ptr.get();
const int rc = s->AppendIfNotFull(message, options);
if (rc == 0) {
return 0;
@@ -795,15 +893,15 @@ int StreamWrite(StreamId stream_id, const butil::IOBuf
&message,
void StreamWait(StreamId stream_id, const timespec *due_time,
void (*on_writable)(StreamId, void*, int), void *arg) {
- SocketUniquePtr ptr;
- if (Socket::Address(stream_id, &ptr) != 0) {
+ StreamUniquePtr stream_ptr;
+ if (Stream::Address(stream_id, &stream_ptr) != 0) {
Stream::WritableMeta* wm = new Stream::WritableMeta;
wm->id = stream_id;
wm->arg= arg;
wm->has_timer = false;
wm->on_writable = on_writable;
wm->error_code = EINVAL;
- const bthread_attr_t* attr =
+ const bthread_attr_t* attr =
FLAGS_usercode_in_pthread ? &BTHREAD_ATTR_PTHREAD
: &BTHREAD_ATTR_NORMAL;
bthread_t tid;
@@ -813,16 +911,16 @@ void StreamWait(StreamId stream_id, const timespec
*due_time,
}
return;
}
- Stream* s = (Stream*)ptr->conn();
+ Stream* s = stream_ptr.get();
return s->Wait(on_writable, arg, due_time);
}
int StreamWait(StreamId stream_id, const timespec* due_time) {
- SocketUniquePtr ptr;
- if (Socket::Address(stream_id, &ptr) != 0) {
+ StreamUniquePtr stream_ptr;
+ if (Stream::Address(stream_id, &stream_ptr) != 0) {
return EINVAL;
}
- Stream* s = (Stream*)ptr->conn();
+ Stream* s = stream_ptr.get();
return s->Wait(due_time);
}
diff --git a/src/brpc/stream.h b/src/brpc/stream.h
index 36c0def7..73e7aff5 100644
--- a/src/brpc/stream.h
+++ b/src/brpc/stream.h
@@ -19,17 +19,18 @@
#ifndef BRPC_STREAM_H
#define BRPC_STREAM_H
+#include <vector>
#include "butil/iobuf.h"
#include "butil/scoped_generic.h"
-#include "brpc/socket_id.h"
+#include "brpc/versioned_ref_with_id.h"
namespace brpc {
class Controller;
-typedef SocketId StreamId;
+typedef VRefId StreamId;
using StreamIds = std::vector<StreamId>;
-const StreamId INVALID_STREAM_ID = (StreamId)-1L;
+const StreamId INVALID_STREAM_ID = INVALID_VREF_ID;
namespace detail {
struct StreamIdTraits;
@@ -134,7 +135,7 @@ int StreamAccept(StreamIds& response_stream, Controller&
cntl,
// - EAGAIN: |stream_id| is created with positive |max_buf_size| and buf size
// which the remote side hasn't consumed yet excceeds the number.
// - EINVAL: |stream_id| is invalied or has been closed
-int StreamWrite(StreamId stream_id, const butil::IOBuf &message,
+int StreamWrite(StreamId stream_id, const butil::IOBuf& message,
const StreamWriteOptions* options = NULL);
// Write util the pending buffer size is less than |max_buf_size| or orrur
diff --git a/src/brpc/stream_impl.h b/src/brpc/stream_impl.h
index 284b33ca..f9ae065d 100644
--- a/src/brpc/stream_impl.h
+++ b/src/brpc/stream_impl.h
@@ -19,42 +19,42 @@
#ifndef BRPC_STREAM_IMPL_H
#define BRPC_STREAM_IMPL_H
-#include <mutex>
+#include <cstdarg>
+#include <vector>
#include "bthread/bthread.h"
#include "bthread/execution_queue.h"
#include "brpc/socket.h"
#include "brpc/stream.h"
+#include "brpc/versioned_ref_with_id.h"
#include "brpc/streaming_rpc_meta.pb.h"
namespace brpc {
-class BAIDU_CACHELINE_ALIGNMENT Stream : public SocketConnection {
+// Stream is implemented on top of VersionedRefWithId<Stream>, so that StreamId
+// is a self-contained versioned reference id and no longer depends on a fake
+// Socket. The instance is managed by a ResourcePool: it is reused rather than
+// re-constructed, thus all per-stream state must be (re)initialized in
+// OnCreated() and cleaned up in OnFailed()/BeforeRecycled().
+class BAIDU_CACHELINE_ALIGNMENT Stream : public VersionedRefWithId<Stream> {
public:
- // |--------------------------------------------------|
- // |----------- Implement SocketConnection -----------|
- // |--------------------------------------------------|
-
- int Connect(Socket* ptr, const timespec* due_time,
- int (*on_connect)(int, int, void *), void *data);
- ssize_t CutMessageIntoFileDescriptor(int, butil::IOBuf **data_list,
- size_t size);
- ssize_t CutMessageIntoSSLChannel(SSL*, butil::IOBuf**, size_t);
- void BeforeRecycle(Socket *);
-
- // --------------------- SocketConnection --------------
+ // NOTE: Users cannot create Stream from constructor. Use Create() instead.
+ // It's public only because of the requirement of ResourcePool.
+ explicit Stream(Forbidden);
+ ~Stream();
+ // Write `msg' into this stream. Returns 0 on success, 1 when the stream is
+ // full, -1 on error.
int AppendIfNotFull(const butil::IOBuf& msg,
const StreamWriteOptions* options = NULL);
static int Create(const StreamOptions& options,
- const StreamSettings *remote_settings,
+ const StreamSettings* remote_settings,
StreamId *id, bool parse_rpc_response = true);
- StreamId id() { return _id; }
int OnReceived(const StreamFrameMeta& fm, butil::IOBuf *buf, Socket* sock);
void SetRemoteSettings(const StreamSettings& remote_settings) {
_remote_settings.MergeFrom(remote_settings);
}
- int SetHostSocket(Socket *host_socket);
+ int SetHostSocket(Socket* host_socket);
void SetConnected();
void SetConnected(const StreamSettings *remote_settings);
@@ -62,6 +62,7 @@ public:
const timespec *due_time);
int Wait(const timespec* due_time);
void FillSettings(StreamSettings *settings);
+
static int SetFailed(StreamId id, int error_code, const char* reason_fmt,
...)
__attribute__ ((__format__ (__printf__, 3, 4)));
static int SetFailed(const StreamIds& ids, int error_code, const char*
reason_fmt, ...)
@@ -73,12 +74,21 @@ private:
friend void StreamWait(StreamId stream_id, const timespec *due_time,
void (*on_writable)(StreamId, void*, int), void *arg);
friend class MessageBatcher;
-friend struct butil::DefaultDeleter<Stream>;
- Stream();
- ~Stream();
- int Init(const StreamOptions options);
+friend class VersionedRefWithId<Stream>;
+
+ // Initialize (or reset for a reused instance) the stream.
+ // Returns 0 on success, non-zero on failure.
+ int OnCreated(const StreamOptions& options,
+ const StreamSettings* remote_settings,
+ bool parse_rpc_response);
+ // Called once when SetFailed() succeeds. Performs the close actions
+ // (wake up waiters, send CLOSE frame, stop the consumer queue, etc.).
+ void OnFailed(int error_code, const std::string& error_text);
+ // Called right before the instance is recycled to the ResourcePool.
+ void BeforeRecycled();
+ std::string OnDescription() const;
+
void SetRemoteConsumed(size_t _remote_consumed);
- void TriggerOnConnectIfNeed();
void Wait(void (*on_writable)(StreamId, void*, int), void* arg,
const timespec* due_time, bool new_thread, bthread_id_t
*join_id);
void SendFeedback(int64_t _consumed_bytes);
@@ -86,17 +96,20 @@ friend struct butil::DefaultDeleter<Stream>;
void StopIdleTimer();
void HandleRpcResponse(butil::IOBuf* response_buffer);
void WriteToHostSocket(butil::IOBuf* b);
+ // Pack `data` into one or more STRM DATA frames (splitting large data into
+ // segments) and write them into the host socket in a single Write.
+ int WritePacked(const butil::IOBuf& data, const StreamWriteOptions*
options);
+ // Roll back `_produced` by `data_length` (under
`_congestion_control_mutex`)
+ // when a write fails. No-op when the congestion window is disabled.
+ void RollbackProduced(size_t data_length);
static int Consume(void *meta, bthread::TaskIterator<butil::IOBuf*>& iter);
static int TriggerOnWritable(bthread_id_t id, void *data, int error_code);
static void *RunOnWritable(void* arg);
- static void* RunOnConnect(void* arg);
- struct ConnectMeta {
- int (*on_connect)(int, int, void*);
- int ec;
- void* arg;
- };
+ static int SetFailedV(StreamId id, int error_code,
+ const char* reason_fmt, va_list ap);
+ void CloseV(int error_code, const char* reason_fmt, va_list ap);
struct WritableMeta {
void (*on_writable)(StreamId, void*, int);
@@ -108,21 +121,36 @@ friend struct butil::DefaultDeleter<Stream>;
bthread_timer_t timer;
};
- Socket* _host_socket; // Every stream within a Socket holds a
reference
- Socket* _fake_socket_weak_ref; // Not holding reference
- StreamId _id;
+ struct PendingWrite {
+ butil::IOBuf data;
+ StreamWriteOptions options;
+
+ PendingWrite() = default;
+ explicit PendingWrite(const butil::IOBuf& d, const StreamWriteOptions*
opts)
+ : data(d) {
+ if (opts != NULL) {
+ options = *opts;
+ }
+ }
+ };
+
+ Socket* _host_socket; // Every stream within a Socket holds a reference.
StreamOptions _options;
- bthread_mutex_t _connect_mutex;
- ConnectMeta _connect_meta;
+ mutable bthread_mutex_t _connect_mutex;
butil::atomic<bool> _connected;
- bool _closed;
- int _error_code;
- std::string _error_text;
+ int _error_code;
+ std::string _error_text;
+ // Writes buffered before the stream is connected (the remote stream id
+ // is unknown until then). Flushed in SetConnected().
+ std::vector<PendingWrite> _pending_writes;
bthread_mutex_t _congestion_control_mutex;
size_t _produced;
size_t _remote_consumed;
+ // Bytes of this Stream currently included in the host Socket's aggregate
+ // unconsumed counter. Protected by _congestion_control_mutex.
+ size_t _socket_unconsumed_size;
size_t _cur_buf_size;
bthread_id_list_t _writable_wait_list;
@@ -132,12 +160,13 @@ friend struct butil::DefaultDeleter<Stream>;
bool _parse_rpc_response;
bthread::ExecutionQueueId<butil::IOBuf*> _consumer_queue;
- butil::IOBuf *_pending_buf;
+ butil::IOBuf* _pending_buf;
int64_t _start_idle_timer_us;
bthread_timer_t _idle_timer;
- std::once_flag _set_host_socket_flag;
};
+typedef VersionedRefWithIdUniquePtr<Stream> StreamUniquePtr;
+
} // namespace brpc
diff --git a/src/brpc/versioned_ref_with_id.h b/src/brpc/versioned_ref_with_id.h
index f77d5afa..3793e218 100644
--- a/src/brpc/versioned_ref_with_id.h
+++ b/src/brpc/versioned_ref_with_id.h
@@ -89,35 +89,74 @@ typename std::enable_if<!butil::is_void<Ret>::value,
Ret>::type ReturnEmpty() {
template <typename Ret>
typename std::enable_if<butil::is_void<Ret>::value, Ret>::type ReturnEmpty() {}
-// Call func_name of class_type if class_type implements func_name,
-// otherwise call default function.
-#define WRAPPER_OF(class_type, func_name, return_type)
\
- struct func_name ## Wrapper {
\
- template<typename V, typename... Args>
\
+// Detect whether a type implements the member function `func_name' callable
+// with Args..., exposing the result as a compile-time boolean:
+// HasMember_<func_name><V, Args...>::value
+// The detector is decoupled from the caller so that it can also be reused in
+// standalone static_assert to enforce interface contracts.
+#define BRPC_DEFINE_MEMBER_DETECTOR(func_name)
\
+ template <typename V, typename... Args>
\
+ struct HasMember##func_name {
\
+ template <typename U>
\
static auto Test(int) -> decltype(
\
- std::declval<V>().func_name(std::declval<Args>()...),
std::true_type()); \
- template<typename>
\
+ std::declval<U>().func_name(std::declval<Args>()...),
std::true_type()); \
+ template <typename>
\
static auto Test(...) -> std::false_type;
\
-
\
- template<typename... Args>
\
- typename std::enable_if<decltype(
\
- Test<class_type, Args...>(0))::value, return_type>::type
\
- Call(class_type* obj, Args&&... args) {
\
+ static constexpr bool value = decltype(Test<V>(0))::value;
\
+ }
+
+// Define a static caller `Call<func_name>' that invokes `obj->func_name(...)'
+// if the type implements it, otherwise returns a default-constructed value.
+// Requires the detector defined by BRPC_DEFINE_MEMBER_DETECTOR(func_name).
+// On C++20, an inline `requires' expression is used directly (no separate
+// detector needed);
+// On C++17, a single `if constexpr' branch with the detector;
+// on C++11/14, two SFINAE overloads so that the body referencing a
+// possibly-missing member is never instantiated.
+#if __cplusplus >= 202002L
+#define BRPC_DEFINE_OPTIONAL_CALLER(func_name, return_type)
\
+ template <typename U, typename... Args>
\
+ static return_type Call##func_name(U* obj, Args&&... args) {
\
+ if constexpr (requires { obj->func_name(std::forward<Args>(args)...);
}) { \
+ BAIDU_CASSERT((butil::is_result_same<
\
+ return_type, decltype(&U::func_name), U,
Args...>::value), \
+ "Params or return type mismatch");
\
+ return obj->func_name(std::forward<Args>(args)...);
\
+ } else {
\
+ return ReturnEmpty<return_type>();
\
+ }
\
+ }
+#elif __cplusplus >= 201703L
+#define BRPC_DEFINE_OPTIONAL_CALLER(func_name, return_type)
\
+ template <typename U, typename... Args>
\
+ static return_type Call##func_name(U* obj, Args&&... args) {
\
+ if constexpr (HasMember##func_name<U, Args...>::value) {
\
BAIDU_CASSERT((butil::is_result_same<
\
- return_type, decltype(&T::func_name), T,
Args...>::value), \
+ return_type, decltype(&U::func_name), U,
Args...>::value), \
"Params or return type mismatch");
\
- return obj->func_name(std::forward<Args>(args)...);
\
- }
\
-
\
- template<typename... Args>
\
- typename std::enable_if<!decltype(
\
- Test<class_type, Args...>(0))::value, return_type>::type
\
- Call(class_type* obj, Args&&...) {
\
+ return obj->func_name(std::forward<Args>(args)...);
\
+ } else {
\
return ReturnEmpty<return_type>();
\
}
\
}
-
-#define WRAPPER_CALL(func_name, obj, ...) func_name ## Wrapper().Call(obj, ##
__VA_ARGS__)
+#else
+#define BRPC_DEFINE_OPTIONAL_CALLER(func_name, return_type)
\
+ template <typename U, typename... Args>
\
+ static typename std::enable_if<
\
+ HasMember##func_name<U, Args...>::value, return_type>::type
\
+ Call##func_name(U* obj, Args&&... args) {
\
+ BAIDU_CASSERT((butil::is_result_same<
\
+ return_type, decltype(&U::func_name), U,
Args...>::value), \
+ "Params or return type mismatch");
\
+ return obj->func_name(std::forward<Args>(args)...);
\
+ }
\
+ template <typename U, typename... Args>
\
+ static typename std::enable_if<
\
+ !HasMember##func_name<U, Args...>::value, return_type>::type
\
+ Call##func_name(U*, Args&&...) {
\
+ return ReturnEmpty<return_type>();
\
+ }
+#endif
// VersionedRefWithId is an efficient data structure, which can be find
// in O(1)-time by VRefId.
@@ -205,7 +244,11 @@ public:
, _this_id(0)
, _additional_ref_status(ADDITIONAL_REF_USING) {}
- virtual ~VersionedRefWithId() = default;
+ // Non-virtual on purpose: CRTP static polymorphism needs no vtable, and
+ // instances are always recycled via return_resource() (never deleted
+ // through a base pointer), so a virtual destructor would only add a
+ // useless vptr and hurt cacheline layout.
+ ~VersionedRefWithId() = default;
DISALLOW_COPY_AND_ASSIGN(VersionedRefWithId);
// Create a VersionedRefWithId, put the identifier into `id'.
@@ -219,14 +262,14 @@ public:
// of scope (w/o explicit std::move). User can still access `ptr'
// after calling ptr->SetFailed() before release of `ptr'.
// This function is wait-free.
- // Returns 0 on success, -1 when the Socket was SetFailed().
+ // Returns 0 on success, -1 when the object was SetFailed().
static int Address(VRefId id, VersionedRefWithIdUniquePtr<T>* ptr);
- // Returns 0 on success, 1 on failed socket, -1 on recycled.
+ // Returns 0 on success, 1 on failed object, -1 on recycled.
static int AddressFailedAsWell(VRefId id, VersionedRefWithIdUniquePtr<T>*
ptr);
// Re-address current VersionedRefWithId into `ptr'.
- // Always succeed even if this socket is failed.
+ // Always succeed even if this object is failed.
void ReAddress(VersionedRefWithIdUniquePtr<T>* ptr);
// Returns signed 32-bit referenced-count.
@@ -239,12 +282,12 @@ public:
// Any later Address() of the identifier shall return NULL. The
// VersionedRefWithId is NOT recycled after calling this function,
// instead it will be recycled when no one references it. Internal
- // fields of the Socket are still accessible after calling this
+ // fields of the object are still accessible after calling this
// function. Calling SetFailed() of a VersionedRefWithId more than
// once is OK.
// T::OnFailed() will be called when SetFailed() successfully.
// This function is lock-free.
- // Returns -1 when the Socket was already SetFailed(), 0 otherwise.
+ // Returns -1 when the object was already SetFailed(), 0 otherwise.
template<typename... Args>
static int SetFailedById(VRefId id, Args&&... args);
@@ -301,7 +344,7 @@ friend void DereferenceVersionedRefWithId<>(T* r);
_versioned_ref.fetch_add(1, butil::memory_order_release);
}
- // Make this socket addressable again.
+ // Make this object addressable again.
// If nref is less than `at_least_nref', VersionedRefWithId was
// abandoned during revival and cannot be revived.
void Revive(int32_t at_least_nref);
@@ -310,17 +353,21 @@ private:
typedef butil::ResourceId<T> resource_id_t;
// 1. When `failed_as_well=true', returns 0 on success,
- // 1 on failed socket, -1 on recycled.
+ // 1 on failed object, -1 on recycled.
// 2. When `failed_as_well=true', returns 0 on success,
- // -1 when the Socket was SetFailed().
+ // -1 when the object was SetFailed().
static int AddressImpl(VRefId id, bool failed_as_well,
VersionedRefWithIdUniquePtr<T>* ptr);
- // Callback wrapper of Derived classes.
- WRAPPER_OF(T, OnFailed, void);
- WRAPPER_OF(T, BeforeAdditionalRefReleased, void);
- WRAPPER_OF(T, AfterRevived, void);
- WRAPPER_OF(T, OnDescription, std::string);
+ // Detectors + static callers for optional Derived-class callbacks.
+ BRPC_DEFINE_MEMBER_DETECTOR(OnFailed);
+ BRPC_DEFINE_OPTIONAL_CALLER(OnFailed, void);
+ BRPC_DEFINE_MEMBER_DETECTOR(BeforeAdditionalRefReleased);
+ BRPC_DEFINE_OPTIONAL_CALLER(BeforeAdditionalRefReleased, void);
+ BRPC_DEFINE_MEMBER_DETECTOR(AfterRevived);
+ BRPC_DEFINE_OPTIONAL_CALLER(AfterRevived, void);
+ BRPC_DEFINE_MEMBER_DETECTOR(OnDescription);
+ BRPC_DEFINE_OPTIONAL_CALLER(OnDescription, std::string);
// unsigned 32-bit version + signed 32-bit referenced-count.
// Meaning of version:
@@ -329,14 +376,14 @@ private:
// of a VersionedRefWithId on the slot, the version is added with 1
twice.
// This is also the version encoded in VRefId.
// * Failed version: = created version + 1, SetFailed()-ed but returned.
- // * Other versions: the socket is already recycled.
+ // * Other versions: the object is already recycled.
butil::atomic<uint64_t> BAIDU_CACHELINE_ALIGNMENT _versioned_ref;
// The unique identifier.
VRefId _this_id;
// Indicates whether additional reference has increased,
// decreased, or is increasing.
// additional ref status:
- // `Socket'、`Create': REF_USING
+ // constructor / `Create': REF_USING
// `SetFailed': REF_USING -> REF_RECYCLED
// `Revive' REF_RECYCLED -> REF_REVIVING -> REF_USING
butil::atomic<AdditionalRefStatus> _additional_ref_status;
@@ -444,7 +491,7 @@ int VersionedRefWithId<T>::AddressImpl(
// Addressed a free slot.
}
} else {
- CHECK(false) << "Over dereferenced SocketId=" << id;
+ CHECK(false) << "Over dereferenced VRefId=" << id;
}
}
return -1;
@@ -488,7 +535,7 @@ int VersionedRefWithId<T>::SetFailedImpl(Args&&... args) {
butil::memory_order_release,
butil::memory_order_relaxed)) {
// Call T::OnFailed() to notify the failure of T.
- WRAPPER_CALL(OnFailed, static_cast<T*>(this),
std::forward<Args>(args)...);
+ CallOnFailed(static_cast<T*>(this), std::forward<Args>(args)...);
// Deref additionally which is added at creation so that this
// queue's reference will hit 0(recycle) when no one addresses it.
ReleaseAdditionalReference();
@@ -507,7 +554,7 @@ int VersionedRefWithId<T>::ReleaseAdditionalReference() {
expect, ADDITIONAL_REF_RECYCLED,
butil::memory_order_relaxed,
butil::memory_order_relaxed)) {
- WRAPPER_CALL(BeforeAdditionalRefReleased, static_cast<T*>(this));
+ CallBeforeAdditionalRefReleased(static_cast<T*>(this));
return Dereference();
}
@@ -529,7 +576,7 @@ int VersionedRefWithId<T>::Dereference() {
if (nref > 1) {
return 0;
}
- if (__builtin_expect(nref == 1, 1)) {
+ if (BAIDU_LIKELY(nref == 1)) {
const uint32_t ver = VersionOfVRef(vref);
const uint32_t id_ver = VersionOfVRefId(id);
// Besides first successful SetFailed() adds 1 to version, one of
@@ -541,9 +588,9 @@ int VersionedRefWithId<T>::Dereference() {
//
// Note: `ver == id_ver' means this VersionedRefWithId has been
`SetRecycle'
// before rather than `SetFailed'; `ver == ide_ver+1' means we
- // had `SetFailed' this socket before. We should destroy the
- // socket under both situation
- if (__builtin_expect(ver == id_ver || ver == id_ver + 1, 1)) {
+ // had `SetFailed' this object before. We should destroy the
+ // object under both situation
+ if (BAIDU_LIKELY(ver == id_ver || ver == id_ver + 1)) {
// sees nref:1->0, try to set version=id_ver+2,--nref.
// No retry: if version changes, the slot is already returned by
// another one who sees nref:1->0 concurrently; if nref changes,
@@ -590,7 +637,7 @@ void VersionedRefWithId<T>::Revive(int32_t at_least_nref) {
int32_t nref = NRefOfVRef(vref);
if (nref < at_least_nref) {
- // Set the status to REF_RECYCLED since no one uses this socket
+ // Set the status to REF_RECYCLED since no one uses this object
_additional_ref_status.store(
ADDITIONAL_REF_RECYCLED, butil::memory_order_relaxed);
CHECK_EQ(1, nref);
@@ -606,7 +653,7 @@ void VersionedRefWithId<T>::Revive(int32_t at_least_nref) {
// Set the status to REF_USING since we add additional ref again
_additional_ref_status.store(
ADDITIONAL_REF_USING, butil::memory_order_relaxed);
- WRAPPER_CALL(AfterRevived, static_cast<T*>(this));
+ CallAfterRevived(static_cast<T*>(this));
return;
}
}
@@ -617,8 +664,7 @@ std::string VersionedRefWithId<T>::description() const {
std::string result;
result.reserve(128);
butil::string_appendf(&result, "%s{id=%" PRIu64 " ",
butil::class_name<T>(), id());
- result.append(WRAPPER_CALL(
- OnDescription, const_cast<T*>(static_cast<const T*>(this))));
+ result.append(CallOnDescription(const_cast<T*>(static_cast<const
T*>(this))));
butil::string_appendf(&result, "} (%p)", this);
return result;
}
diff --git a/src/bthread/execution_queue_inl.h
b/src/bthread/execution_queue_inl.h
index ddf7bc6b..9c12e192 100644
--- a/src/bthread/execution_queue_inl.h
+++ b/src/bthread/execution_queue_inl.h
@@ -348,11 +348,10 @@ inline ExecutionQueueOptions::ExecutionQueueOptions()
{}
template <typename T>
-inline int execution_queue_start(
- ExecutionQueueId<T>* id,
- const ExecutionQueueOptions* options,
- int (*execute)(void* meta, TaskIterator<T>&),
- void* meta) {
+inline int execution_queue_start(ExecutionQueueId<T>* id,
+ const ExecutionQueueOptions* options,
+ int (*execute)(void* meta, TaskIterator<T>&),
+ void* meta) {
return ExecutionQueue<T>::create(id, options, execute, meta);
}
@@ -364,7 +363,7 @@ execution_queue_address(ExecutionQueueId<T> id) {
template <typename T>
inline int execution_queue_execute(ExecutionQueueId<T> id,
- typename butil::add_const_reference<T>::type task) {
+ typename
butil::add_const_reference<T>::type task) {
return execution_queue_execute(id, task, NULL);
}
@@ -377,9 +376,8 @@ inline int execution_queue_execute(ExecutionQueueId<T> id,
template <typename T>
inline int execution_queue_execute(ExecutionQueueId<T> id,
- typename butil::add_const_reference<T>::type task,
- const TaskOptions* options,
- TaskHandle* handle) {
+ typename
butil::add_const_reference<T>::type task,
+ const TaskOptions* options, TaskHandle*
handle) {
typename ExecutionQueue<T>::scoped_ptr_t
ptr = ExecutionQueue<T>::address(id);
if (ptr != NULL) {
@@ -390,21 +388,18 @@ inline int execution_queue_execute(ExecutionQueueId<T> id,
}
template <typename T>
-inline int execution_queue_execute(ExecutionQueueId<T> id,
- T&& task) {
+inline int execution_queue_execute(ExecutionQueueId<T> id, T&& task) {
return execution_queue_execute(id, std::forward<T>(task), NULL);
}
template <typename T>
-inline int execution_queue_execute(ExecutionQueueId<T> id,
- T&& task,
+inline int execution_queue_execute(ExecutionQueueId<T> id, T&& task,
const TaskOptions* options) {
return execution_queue_execute(id, std::forward<T>(task), options, NULL);
}
template <typename T>
-inline int execution_queue_execute(ExecutionQueueId<T> id,
- T&& task,
+inline int execution_queue_execute(ExecutionQueueId<T> id, T&& task,
const TaskOptions* options,
TaskHandle* handle) {
typename ExecutionQueue<T>::scoped_ptr_t
diff --git a/test/brpc_streaming_rpc_unittest.cpp
b/test/brpc_streaming_rpc_unittest.cpp
index d6ad1694..a759bae5 100644
--- a/test/brpc_streaming_rpc_unittest.cpp
+++ b/test/brpc_streaming_rpc_unittest.cpp
@@ -143,10 +143,14 @@ static void* SendTwoMessagesOnServerExtraStream(void*
arg) {
const int64_t connect_deadline_us = butil::gettimeofday_us() + 2 * 1000 *
1000L;
bool connected = false;
while (butil::gettimeofday_us() < connect_deadline_us) {
- brpc::SocketUniquePtr ptr;
- if (brpc::Socket::Address(sid, &ptr) == 0) {
- brpc::Stream* s = static_cast<brpc::Stream*>(ptr->conn());
- if (s->_host_socket != NULL && s->_connected) {
+ brpc::StreamUniquePtr ptr;
+ if (brpc::Stream::Address(sid, &ptr) == 0) {
+ brpc::Stream* s = ptr.get();
+ // SetConnected() publishes _connected only after _host_socket and
+ // the remote settings are ready. Check the acquire flag first
+ // before reading the non-atomic host pointer.
+ if (s->_connected.load(butil::memory_order_acquire) &&
+ s->_host_socket != NULL) {
connected = true;
break;
}
@@ -286,20 +290,6 @@ TEST_F(StreamingRpcTest,
batch_create_stream_feedback_race) {
ASSERT_EQ(2u, request_streams.size());
state.client_extra_stream_id = request_streams[1];
- // Block SetConnected() on the extra stream to enlarge the race window.
- brpc::SocketUniquePtr client_extra_ptr;
- ASSERT_EQ(0, brpc::Socket::Address(state.client_extra_stream_id,
&client_extra_ptr));
- brpc::Stream* client_extra_stream =
static_cast<brpc::Stream*>(client_extra_ptr->conn());
- bthread_mutex_lock(&client_extra_stream->_connect_mutex);
- struct UnlockGuard {
- bthread_mutex_t* m;
- ~UnlockGuard() {
- if (m) {
- bthread_mutex_unlock(m);
- }
- }
- } unlock_guard{&client_extra_stream->_connect_mutex};
-
BRPC_SCOPE_EXIT {
if (state.server_extra_stream_id != brpc::INVALID_STREAM_ID) {
brpc::StreamClose(state.server_extra_stream_id);
@@ -317,13 +307,6 @@ TEST_F(StreamingRpcTest,
batch_create_stream_feedback_race) {
server.Stop(0);
server.Join();
- // Release the SocketUniquePtr held above so the fake socket can be
- // recycled. Otherwise BeforeRecycle / on_closed for the extra stream
- // is deferred until `client_extra_ptr` destructs at scope exit, which
- // happens *after* `client_handler` and `state` are destroyed -> UAF
- // inside Stream::Consume on Linux.
- client_extra_ptr.reset();
-
// on_closed() runs asynchronously on each client stream's consumer
// bthread. Wait for both before letting handler/state go out of
// scope, otherwise Stream::Consume will dereference freed memory.
@@ -338,13 +321,11 @@ TEST_F(StreamingRpcTest,
batch_create_stream_feedback_race) {
stub.Echo(&cntl, &request, &response, brpc::NewCallback(SetAtomicTrue,
&state.rpc_done));
// Wait until client consumes the first 64B payload on extra stream.
+ // This increases the chance that Consume() runs before SetConnected()
+ // finishes on the extra stream, exercising the SetConnected()/Consume()
+ // ordering relevant to FEEDBACK sending via the atomic _local_consumed.
ASSERT_TRUE(WaitForTrue(state.client_got_first_msg, 2000));
- // Unblock SetConnected(); the fix in PR 3215 should send the first
FEEDBACK
- // with consumed_size=64 here, making server-side stream writable again.
- bthread_mutex_unlock(&client_extra_stream->_connect_mutex);
- unlock_guard.m = NULL;
-
ASSERT_TRUE(WaitForTrue(state.rpc_done, 2000));
ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText();
@@ -587,11 +568,13 @@ TEST_F(StreamingRpcTest,
auto_close_if_host_socket_closed) {
ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" <<
request_stream;
{
- brpc::SocketUniquePtr ptr;
- ASSERT_EQ(0, brpc::Socket::Address(request_stream, &ptr));
- brpc::Stream* s = (brpc::Stream*)ptr->conn();
- ASSERT_TRUE(s->_host_socket != NULL);
- s->_host_socket->SetFailed();
+ brpc::StreamUniquePtr ptr;
+ ASSERT_EQ(0, brpc::Stream::Address(request_stream, &ptr));
+ brpc::Stream* s = ptr.get();
+ ASSERT_TRUE(s->_connected.load(butil::memory_order_acquire));
+ brpc::Socket* host_socket = s->_host_socket;
+ ASSERT_TRUE(host_socket != NULL);
+ host_socket->SetFailed();
}
usleep(100);
@@ -638,9 +621,10 @@ TEST_F(StreamingRpcTest, failed_when_rst) {
usleep(100);
}
{
- brpc::SocketUniquePtr ptr;
- ASSERT_EQ(0, brpc::Socket::Address(request_stream, &ptr));
- brpc::Stream* s = (brpc::Stream*)ptr->conn();
+ brpc::StreamUniquePtr ptr;
+ ASSERT_EQ(0, brpc::Stream::Address(request_stream, &ptr));
+ brpc::Stream* s = ptr.get();
+ ASSERT_TRUE(s->_connected.load(butil::memory_order_acquire));
ASSERT_TRUE(s->_host_socket != NULL);
brpc::policy::SendStreamRst(s->_host_socket,
s->_remote_settings.stream_id());
@@ -866,11 +850,12 @@ TEST_F(StreamingRpcTest,
segment_stream_data_automatically) {
brpc::SocketUniquePtr host_socket_ptr;
{
- brpc::SocketUniquePtr ptr;
- ASSERT_EQ(0, brpc::Socket::Address(request_stream, &ptr));
- brpc::Stream *s = (brpc::Stream *)ptr->conn();
- ASSERT_TRUE(s->_host_socket != NULL);
- s->_host_socket->ReAddress(&host_socket_ptr);
+ brpc::StreamUniquePtr ptr;
+ ASSERT_EQ(0, brpc::Stream::Address(request_stream, &ptr));
+ ASSERT_TRUE(ptr->_connected.load(butil::memory_order_acquire));
+ brpc::Socket* host_socket = ptr->_host_socket;
+ ASSERT_TRUE(host_socket != NULL);
+ host_socket->ReAddress(&host_socket_ptr);
}
ASSERT_EQ(0, brpc::StreamClose(request_stream));
@@ -883,7 +868,12 @@ TEST_F(StreamingRpcTest,
segment_stream_data_automatically) {
host_socket_ptr->UpdateStatsEverySecond(now_ms);
brpc::SocketStat stat;
host_socket_ptr->GetStat(&stat);
- ASSERT_LT(N * sizeof(N), stat.out_num_messages_m);
+ // A whole message (with all its segments) is now written to the host
socket
+ // in a single wait-free Socket::Write, so the number of host-socket
messages
+ // no longer reflects the number of stream frames. Heavy segmentation still
+ // shows up as extra on-wire bytes: each 1-byte segment carries a full STRM
+ // frame header + meta, so out_size_m is far larger than the raw payload.
+ ASSERT_LT(N * sizeof(N), stat.out_size_m);
ASSERT_FALSE(handler.failed());
ASSERT_EQ(0, handler.idle_times());
ASSERT_EQ(N, handler._expected_next_value);
@@ -1051,11 +1041,11 @@ TEST_F(StreamingRpcTest, batch_create_extra_stream) {
for (size_t i = 0; i < request_streams.size(); ++i) {
const brpc::StreamId sid = request_streams[i];
ASSERT_TRUE(WaitForTrue([sid]() {
- brpc::SocketUniquePtr ptr;
- if (brpc::Socket::Address(sid, &ptr) != 0) {
+ brpc::StreamUniquePtr ptr;
+ if (brpc::Stream::Address(sid, &ptr) != 0) {
return false;
}
- brpc::Stream* s = static_cast<brpc::Stream*>(ptr->conn());
+ brpc::Stream* s = ptr.get();
return s->_host_socket != NULL &&
s->_connected.load(butil::memory_order_acquire);
}, 5000)) << "stream_index=" << i;
@@ -1136,11 +1126,11 @@ TEST_F(StreamingRpcTest,
batch_create_extra_stream_upstream_only) {
for (size_t i = 0; i < request_streams.size(); ++i) {
const brpc::StreamId sid = request_streams[i];
ASSERT_TRUE(WaitForTrue([sid]() {
- brpc::SocketUniquePtr ptr;
- if (brpc::Socket::Address(sid, &ptr) != 0) {
+ brpc::StreamUniquePtr ptr;
+ if (brpc::Stream::Address(sid, &ptr) != 0) {
return false;
}
- brpc::Stream* s = static_cast<brpc::Stream*>(ptr->conn());
+ brpc::Stream* s = ptr.get();
return s->_host_socket != NULL &&
s->_connected.load(butil::memory_order_acquire);
}, 5000)) << "stream_index=" << i;
@@ -1175,3 +1165,82 @@ TEST_F(StreamingRpcTest,
batch_create_extra_stream_upstream_only) {
server.Stop(0);
server.Join();
}
+
+TEST_F(StreamingRpcTest, unconsumed_bytes_reclaimed_on_stream_close) {
+ GFLAGS_NAMESPACE::SetCommandLineOption(
+ "socket_max_streams_unconsumed_bytes", "10485760");
+ BRPC_SCOPE_EXIT {
+ GFLAGS_NAMESPACE::SetCommandLineOption(
+ "socket_max_streams_unconsumed_bytes", "0");
+ };
+
+ class BlockingHandler : public brpc::StreamInputHandler {
+ public:
+ BlockingHandler() : blocked(true) {}
+
+ int on_received_messages(brpc::StreamId,
+ butil::IOBuf* const[], size_t) override {
+ while (blocked.load(std::memory_order_acquire)) {
+ usleep(100);
+ }
+ return 0;
+ }
+ void on_idle_timeout(brpc::StreamId) override {}
+ void on_closed(brpc::StreamId) override {}
+
+ std::atomic<bool> blocked;
+ } handler;
+
+ brpc::StreamOptions opt;
+ opt.handler = &handler;
+ opt.max_buf_size = 1024 * 1024;
+
+ brpc::Server server;
+ MyServiceWithStream service(opt);
+ ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE));
+ ASSERT_EQ(0, server.Start(9007, NULL));
+
+ brpc::Channel channel;
+ ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL));
+
+ brpc::Controller cntl;
+ brpc::StreamId request_stream;
+ brpc::StreamOptions request_stream_options;
+ request_stream_options.max_buf_size = 1024 * 1024;
+ ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options));
+ brpc::ScopedStream stream_guard(request_stream);
+
+ test::EchoService_Stub stub(&channel);
+ stub.Echo(&cntl, &request, &response, NULL);
+ ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText();
+
+ brpc::SocketUniquePtr host_socket;
+ {
+ brpc::StreamUniquePtr ptr;
+ ASSERT_EQ(0, brpc::Stream::Address(request_stream, &ptr));
+ ASSERT_TRUE(ptr->_connected.load(butil::memory_order_acquire));
+ ASSERT_TRUE(ptr->_host_socket != NULL);
+ ptr->_host_socket->ReAddress(&host_socket);
+ }
+ int64_t baseline = host_socket->_total_streams_unconsumed_size.load(
+ butil::memory_order_relaxed);
+
+ size_t write_size = 100 * 1024;
+ butil::IOBuf out;
+ out.append(std::string(write_size, 'x'));
+ ASSERT_EQ(0, brpc::StreamWrite(request_stream, out));
+ ASSERT_TRUE(WaitForTrue([&]() {
+ return host_socket->_total_streams_unconsumed_size.load(
+ butil::memory_order_relaxed) >= baseline +
static_cast<int64_t>(write_size);
+ }, 2000));
+
+ ASSERT_EQ(0, brpc::StreamClose(request_stream));
+ ASSERT_TRUE(WaitForTrue([&]() {
+ return host_socket->_total_streams_unconsumed_size.load(
+ butil::memory_order_relaxed) == baseline;
+ }, 2000));
+
+ handler.blocked.store(false, std::memory_order_release);
+ server.Stop(0);
+ server.Join();
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]