903 lines
32 KiB
C++
903 lines
32 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.
|
|
|
|
// brpc - A framework to host and access services throughout Baidu.
|
|
|
|
// Date: 2015/10/22 16:28:44
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <atomic>
|
|
#include "brpc/server.h"
|
|
|
|
#include "brpc/controller.h"
|
|
#include "brpc/channel.h"
|
|
#include "brpc/callback.h"
|
|
#include "brpc/socket.h"
|
|
#include "brpc/stream_impl.h"
|
|
#include "brpc/policy/streaming_rpc_protocol.h"
|
|
#include "echo.pb.h"
|
|
|
|
class AfterAcceptStream {
|
|
public:
|
|
virtual void action(brpc::StreamId) = 0;
|
|
};
|
|
|
|
class MyServiceWithStream : public test::EchoService {
|
|
public:
|
|
MyServiceWithStream(const brpc::StreamOptions& options)
|
|
: _options(options)
|
|
, _after_accept_stream(NULL)
|
|
{}
|
|
MyServiceWithStream(const brpc::StreamOptions& options,
|
|
AfterAcceptStream* after_accept_stream)
|
|
: _options(options)
|
|
, _after_accept_stream(after_accept_stream)
|
|
{}
|
|
MyServiceWithStream()
|
|
: _options()
|
|
, _after_accept_stream(NULL)
|
|
{}
|
|
|
|
void Echo(::google::protobuf::RpcController* controller,
|
|
const ::test::EchoRequest* request,
|
|
::test::EchoResponse* response,
|
|
::google::protobuf::Closure* done) {
|
|
brpc::ClosureGuard done_guard(done);
|
|
response->set_message(request->message());
|
|
brpc::Controller* cntl = (brpc::Controller*)controller;
|
|
brpc::StreamId response_stream;
|
|
ASSERT_EQ(0, StreamAccept(&response_stream, *cntl, &_options));
|
|
LOG(INFO) << "Created response_stream=" << response_stream;
|
|
if (_after_accept_stream) {
|
|
_after_accept_stream->action(response_stream);
|
|
}
|
|
}
|
|
private:
|
|
brpc::StreamOptions _options;
|
|
AfterAcceptStream* _after_accept_stream;
|
|
};
|
|
|
|
class StreamingRpcTest : public testing::Test {
|
|
protected:
|
|
void SetUp() { request.set_message("hello world"); }
|
|
void TearDown() {}
|
|
|
|
test::EchoRequest request;
|
|
test::EchoResponse response;
|
|
};
|
|
|
|
struct BatchStreamFeedbackRaceState {
|
|
brpc::StreamId server_first_stream_id{brpc::INVALID_STREAM_ID};
|
|
brpc::StreamId server_extra_stream_id{brpc::INVALID_STREAM_ID};
|
|
brpc::StreamId client_extra_stream_id{brpc::INVALID_STREAM_ID};
|
|
|
|
std::atomic<int> server_first_write_rc{-1};
|
|
std::atomic<int> server_second_write_rc{-1};
|
|
std::atomic<bool> client_got_first_msg{false};
|
|
std::atomic<bool> client_got_second_msg{false};
|
|
std::atomic<bool> server_write_done{false};
|
|
std::atomic<bool> rpc_done{false};
|
|
std::atomic<int> client_closed_count{0};
|
|
|
|
bthread_t server_send_tid{0};
|
|
std::atomic<bool> server_send_started{false};
|
|
};
|
|
|
|
class BatchStreamClientHandler : public brpc::StreamInputHandler {
|
|
public:
|
|
explicit BatchStreamClientHandler(BatchStreamFeedbackRaceState* state)
|
|
: _state(state) {}
|
|
|
|
int on_received_messages(brpc::StreamId id,
|
|
butil::IOBuf* const messages[],
|
|
size_t size) override {
|
|
if (id != _state->client_extra_stream_id) {
|
|
// This test only cares about extra stream in batch creation.
|
|
return 0;
|
|
}
|
|
for (size_t i = 0; i < size; ++i) {
|
|
const size_t len = messages[i]->length();
|
|
messages[i]->clear();
|
|
// First payload: 64 bytes. Second payload: 1 byte.
|
|
if (len == 64) {
|
|
_state->client_got_first_msg.store(true, std::memory_order_release);
|
|
} else if (len == 1) {
|
|
_state->client_got_second_msg.store(true, std::memory_order_release);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void on_idle_timeout(brpc::StreamId /*id*/) override {}
|
|
|
|
void on_closed(brpc::StreamId /*id*/) override {
|
|
_state->client_closed_count.fetch_add(1, std::memory_order_release);
|
|
}
|
|
|
|
void on_failed(brpc::StreamId /*id*/, int /*error_code*/, const std::string& /*error_text*/) override {}
|
|
|
|
private:
|
|
BatchStreamFeedbackRaceState* _state;
|
|
};
|
|
|
|
static void* SendTwoMessagesOnServerExtraStream(void* arg) {
|
|
auto* state = static_cast<BatchStreamFeedbackRaceState*>(arg);
|
|
const brpc::StreamId sid = state->server_extra_stream_id;
|
|
|
|
// Wait until server-side stream is connected.
|
|
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) {
|
|
connected = true;
|
|
break;
|
|
}
|
|
}
|
|
usleep(1000);
|
|
}
|
|
|
|
if (!connected) {
|
|
state->server_first_write_rc.store(ETIMEDOUT, std::memory_order_relaxed);
|
|
state->server_second_write_rc.store(ETIMEDOUT, std::memory_order_relaxed);
|
|
state->server_write_done.store(true, std::memory_order_release);
|
|
return NULL;
|
|
}
|
|
|
|
// 1) Send a payload exactly equal to max_buf_size(64).
|
|
{
|
|
std::string payload(64, 'a');
|
|
butil::IOBuf out;
|
|
out.append(payload);
|
|
state->server_first_write_rc.store(brpc::StreamWrite(sid, out), std::memory_order_relaxed);
|
|
}
|
|
|
|
// 2) Then send another byte. This write should become writable only after
|
|
// client sends FEEDBACK with consumed_size >= 64.
|
|
const int64_t write_deadline_us = butil::gettimeofday_us() + 2 * 1000 * 1000L;
|
|
int rc = -1;
|
|
while (butil::gettimeofday_us() < write_deadline_us) {
|
|
butil::IOBuf out;
|
|
out.append("b", 1);
|
|
rc = brpc::StreamWrite(sid, out);
|
|
if (rc == 0) {
|
|
break;
|
|
}
|
|
if (rc != EAGAIN) {
|
|
break;
|
|
}
|
|
const timespec duetime = butil::milliseconds_from_now(100);
|
|
(void)brpc::StreamWait(sid, &duetime);
|
|
}
|
|
state->server_second_write_rc.store(rc, std::memory_order_relaxed);
|
|
state->server_write_done.store(true, std::memory_order_release);
|
|
return NULL;
|
|
}
|
|
|
|
class MyServiceWithBatchStream : public test::EchoService {
|
|
public:
|
|
MyServiceWithBatchStream(const brpc::StreamOptions& options,
|
|
BatchStreamFeedbackRaceState* state)
|
|
: _options(options), _state(state) {}
|
|
|
|
void Echo(::google::protobuf::RpcController* controller,
|
|
const ::test::EchoRequest* request,
|
|
::test::EchoResponse* response,
|
|
::google::protobuf::Closure* done) override {
|
|
brpc::ClosureGuard done_guard(done);
|
|
response->set_message(request->message());
|
|
brpc::Controller* cntl = static_cast<brpc::Controller*>(controller);
|
|
|
|
brpc::StreamIds response_streams;
|
|
ASSERT_EQ(0, brpc::StreamAccept(response_streams, *cntl, &_options));
|
|
ASSERT_EQ(2u, response_streams.size());
|
|
_state->server_first_stream_id = response_streams[0];
|
|
_state->server_extra_stream_id = response_streams[1];
|
|
|
|
bthread_t tid;
|
|
ASSERT_EQ(0, bthread_start_background(
|
|
&tid, &BTHREAD_ATTR_NORMAL,
|
|
SendTwoMessagesOnServerExtraStream, _state));
|
|
_state->server_send_tid = tid;
|
|
_state->server_send_started.store(true, std::memory_order_release);
|
|
}
|
|
|
|
private:
|
|
brpc::StreamOptions _options;
|
|
BatchStreamFeedbackRaceState* _state;
|
|
};
|
|
|
|
static void SetAtomicTrue(std::atomic<bool>* f) {
|
|
f->store(true, std::memory_order_release);
|
|
}
|
|
|
|
template <typename Pred>
|
|
static bool WaitForTrue(Pred pred, int timeout_ms) {
|
|
const int64_t deadline_us = butil::gettimeofday_us() + (int64_t)timeout_ms * 1000L;
|
|
while (!pred() && butil::gettimeofday_us() < deadline_us) {
|
|
usleep(1000);
|
|
}
|
|
return pred();
|
|
}
|
|
|
|
static bool WaitForTrue(const std::atomic<bool>& f, int timeout_ms) {
|
|
return WaitForTrue([&f]() { return f.load(std::memory_order_acquire); }, timeout_ms);
|
|
}
|
|
|
|
TEST_F(StreamingRpcTest, sanity) {
|
|
brpc::Server server;
|
|
MyServiceWithStream service;
|
|
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;
|
|
ASSERT_EQ(0, StreamCreate(&request_stream, cntl, NULL));
|
|
brpc::ScopedStream stream_guard(request_stream);
|
|
test::EchoService_Stub stub(&channel);
|
|
stub.Echo(&cntl, &request, &response, NULL);
|
|
ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" << request_stream;
|
|
usleep(10);
|
|
brpc::StreamClose(request_stream);
|
|
server.Stop(0);
|
|
server.Join();
|
|
}
|
|
|
|
TEST_F(StreamingRpcTest, batch_create_stream_feedback_race) {
|
|
BatchStreamFeedbackRaceState state;
|
|
BatchStreamClientHandler client_handler(&state);
|
|
|
|
brpc::StreamOptions server_stream_opt;
|
|
// Make server-side sender sensitive to FEEDBACK quickly.
|
|
server_stream_opt.max_buf_size = 16;
|
|
|
|
brpc::Server server;
|
|
MyServiceWithBatchStream service(server_stream_opt, &state);
|
|
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::StreamIds request_streams;
|
|
brpc::StreamOptions client_stream_opt;
|
|
client_stream_opt.handler = &client_handler;
|
|
client_stream_opt.max_buf_size = 0;
|
|
ASSERT_EQ(0, brpc::StreamCreate(request_streams, 2, cntl, &client_stream_opt));
|
|
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);
|
|
}
|
|
if (state.server_first_stream_id != brpc::INVALID_STREAM_ID) {
|
|
brpc::StreamClose(state.server_first_stream_id);
|
|
}
|
|
for (auto sid : request_streams) {
|
|
brpc::StreamClose(sid);
|
|
}
|
|
|
|
if (state.server_send_tid) {
|
|
bthread_join(state.server_send_tid, NULL);
|
|
}
|
|
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.
|
|
int expected_closed = request_streams.size();
|
|
WaitForTrue([&state, expected_closed]() {
|
|
return state.client_closed_count.load(std::memory_order_acquire)
|
|
>= expected_closed;
|
|
}, 2000);
|
|
};
|
|
|
|
test::EchoService_Stub stub(&channel);
|
|
stub.Echo(&cntl, &request, &response, brpc::NewCallback(SetAtomicTrue, &state.rpc_done));
|
|
|
|
// Wait until client consumes the first 64B payload on extra stream.
|
|
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();
|
|
|
|
// Wait for server-side send thread to be started.
|
|
ASSERT_TRUE(WaitForTrue(state.server_send_started, 2000));
|
|
|
|
ASSERT_TRUE(WaitForTrue(state.server_write_done, 2000));
|
|
ASSERT_EQ(0, state.server_first_write_rc.load(std::memory_order_relaxed));
|
|
ASSERT_EQ(0, state.server_second_write_rc.load(std::memory_order_relaxed));
|
|
ASSERT_TRUE(WaitForTrue(state.client_got_second_msg, 2000));
|
|
}
|
|
|
|
struct HandlerControl {
|
|
HandlerControl()
|
|
: block(false)
|
|
{}
|
|
bool block;
|
|
};
|
|
|
|
class OrderedInputHandler : public brpc::StreamInputHandler {
|
|
public:
|
|
explicit OrderedInputHandler(HandlerControl *cntl = NULL)
|
|
: _expected_next_value(0)
|
|
, _failed(false)
|
|
, _stopped(false)
|
|
, _idle_times(0)
|
|
, _cntl(cntl)
|
|
{}
|
|
|
|
int on_received_messages(brpc::StreamId /*id*/,
|
|
butil::IOBuf *const messages[],
|
|
size_t size) override {
|
|
if (_cntl && _cntl->block) {
|
|
while (_cntl->block) {
|
|
usleep(100);
|
|
}
|
|
}
|
|
for (size_t i = 0; i < size; ++i) {
|
|
CHECK(messages[i]->length() == sizeof(int));
|
|
int network = 0;
|
|
messages[i]->cutn(&network, sizeof(int));
|
|
EXPECT_EQ((int)ntohl(network), _expected_next_value++);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void on_idle_timeout(brpc::StreamId /*id*/) override {
|
|
++_idle_times;
|
|
}
|
|
|
|
void on_closed(brpc::StreamId /*id*/) override {
|
|
ASSERT_FALSE(_stopped);
|
|
_stopped = true;
|
|
}
|
|
|
|
void on_failed(brpc::StreamId id, int error_code,
|
|
const std::string& /*error_text*/) override {
|
|
ASSERT_FALSE(_failed);
|
|
ASSERT_NE(0, error_code);
|
|
_failed = true;
|
|
}
|
|
|
|
bool failed() const { return _failed; }
|
|
bool stopped() const { return _stopped; }
|
|
int idle_times() const { return _idle_times; }
|
|
private:
|
|
int _expected_next_value;
|
|
bool _failed;
|
|
bool _stopped;
|
|
int _idle_times;
|
|
HandlerControl* _cntl;
|
|
};
|
|
|
|
TEST_F(StreamingRpcTest, received_in_order) {
|
|
OrderedInputHandler handler;
|
|
brpc::StreamOptions opt;
|
|
opt.handler = &handler;
|
|
opt.messages_in_batch = 100;
|
|
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 = 0;
|
|
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() << " request_stream=" << request_stream;
|
|
const int N = 10000;
|
|
for (int i = 0; i < N; ++i) {
|
|
int network = htonl(i);
|
|
butil::IOBuf out;
|
|
out.append(&network, sizeof(network));
|
|
ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i;
|
|
}
|
|
ASSERT_EQ(0, brpc::StreamClose(request_stream));
|
|
server.Stop(0);
|
|
server.Join();
|
|
while (!handler.stopped()) {
|
|
usleep(100);
|
|
}
|
|
ASSERT_FALSE(handler.failed());
|
|
ASSERT_EQ(0, handler.idle_times());
|
|
ASSERT_EQ(N, handler._expected_next_value);
|
|
}
|
|
|
|
void on_writable(brpc::StreamId, void* arg, int error_code) {
|
|
std::pair<bool, int>* p = (std::pair<bool, int>*)arg;
|
|
p->first = true;
|
|
p->second = error_code;
|
|
LOG(INFO) << "error_code=" << error_code;
|
|
}
|
|
|
|
TEST_F(StreamingRpcTest, block) {
|
|
HandlerControl hc;
|
|
hc.block = true;
|
|
OrderedInputHandler handler(&hc);
|
|
brpc::StreamOptions opt;
|
|
opt.handler = &handler;
|
|
const int N = 10000;
|
|
opt.max_buf_size = sizeof(uint32_t) * N;
|
|
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::ScopedStream stream_guard(request_stream);
|
|
brpc::StreamOptions request_stream_options;
|
|
request_stream_options.max_buf_size = sizeof(uint32_t) * N;
|
|
ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options));
|
|
test::EchoService_Stub stub(&channel);
|
|
stub.Echo(&cntl, &request, &response, NULL);
|
|
ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream="
|
|
<< request_stream;
|
|
for (int i = 0; i < N; ++i) {
|
|
int network = htonl(i);
|
|
butil::IOBuf out;
|
|
out.append(&network, sizeof(network));
|
|
ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i;
|
|
}
|
|
// sync wait
|
|
int dummy = 102030123;
|
|
butil::IOBuf out;
|
|
out.append(&dummy, sizeof(dummy));
|
|
ASSERT_EQ(EAGAIN, brpc::StreamWrite(request_stream, out));
|
|
hc.block = false;
|
|
// wait flushing all the pending messages
|
|
while (handler._expected_next_value != N) {
|
|
usleep(100);
|
|
}
|
|
// block hanlder again to test async wait
|
|
hc.block = true;
|
|
// async wait
|
|
for (int i = N; i < N + N; ++i) {
|
|
ASSERT_EQ(0, brpc::StreamWait(request_stream, NULL));
|
|
int network = htonl(i);
|
|
butil::IOBuf out;
|
|
out.append(&network, sizeof(network));
|
|
ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i;
|
|
}
|
|
out.clear();
|
|
out.append(&dummy, sizeof(dummy));
|
|
ASSERT_EQ(EAGAIN, brpc::StreamWrite(request_stream, out));
|
|
hc.block = false;
|
|
std::pair<bool, int> p = std::make_pair(false, 0);
|
|
usleep(10);
|
|
brpc::StreamWait(request_stream, NULL, on_writable, &p);
|
|
while (!p.first) {
|
|
usleep(100);
|
|
}
|
|
ASSERT_EQ(0, p.second);
|
|
|
|
// wait flushing all the pending messages
|
|
while (handler._expected_next_value != N + N) {
|
|
usleep(100);
|
|
}
|
|
usleep(1000);
|
|
|
|
LOG(INFO) << "Starting block";
|
|
hc.block = true;
|
|
for (int i = N + N; i < N + N + N; ++i) {
|
|
int network = htonl(i);
|
|
butil::IOBuf out;
|
|
out.append(&network, sizeof(network));
|
|
ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i - N - N;
|
|
}
|
|
out.clear();
|
|
out.append(&dummy, sizeof(dummy));
|
|
ASSERT_EQ(EAGAIN, brpc::StreamWrite(request_stream, out));
|
|
timespec duetime = butil::microseconds_from_now(1);
|
|
p.first = false;
|
|
LOG(INFO) << "Start wait";
|
|
brpc::StreamWait(request_stream, &duetime, on_writable, &p);
|
|
while (!p.first) {
|
|
usleep(100);
|
|
}
|
|
ASSERT_TRUE(p.first);
|
|
EXPECT_EQ(ETIMEDOUT, p.second);
|
|
hc.block = false;
|
|
ASSERT_EQ(0, brpc::StreamClose(request_stream));
|
|
while (!handler.stopped()) {
|
|
usleep(100);
|
|
}
|
|
|
|
ASSERT_FALSE(handler.failed());
|
|
ASSERT_EQ(0, handler.idle_times());
|
|
ASSERT_EQ(N + N + N, handler._expected_next_value);
|
|
}
|
|
|
|
TEST_F(StreamingRpcTest, auto_close_if_host_socket_closed) {
|
|
HandlerControl hc;
|
|
hc.block = true;
|
|
OrderedInputHandler handler(&hc);
|
|
brpc::StreamOptions opt;
|
|
opt.handler = &handler;
|
|
const int N = 10000;
|
|
opt.max_buf_size = sizeof(uint32_t) * N;
|
|
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 = sizeof(uint32_t) * N;
|
|
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() << " 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();
|
|
}
|
|
|
|
usleep(100);
|
|
butil::IOBuf out;
|
|
out.append("test");
|
|
ASSERT_EQ(EINVAL, brpc::StreamWrite(request_stream, out));
|
|
while (!handler.stopped()) {
|
|
usleep(100);
|
|
}
|
|
ASSERT_TRUE(handler.failed());
|
|
ASSERT_EQ(0, handler.idle_times());
|
|
ASSERT_EQ(0, handler._expected_next_value);
|
|
}
|
|
|
|
TEST_F(StreamingRpcTest, failed_when_rst) {
|
|
OrderedInputHandler handler;
|
|
brpc::StreamOptions opt;
|
|
opt.handler = &handler;
|
|
opt.messages_in_batch = 100;
|
|
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 = 0;
|
|
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() << " request_stream=" << request_stream;
|
|
const int N = 10000;
|
|
for (int i = 0; i < N; ++i) {
|
|
int network = htonl(i);
|
|
butil::IOBuf out;
|
|
out.append(&network, sizeof(network));
|
|
ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i;
|
|
}
|
|
|
|
while (handler._expected_next_value != N) {
|
|
usleep(100);
|
|
}
|
|
{
|
|
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);
|
|
brpc::policy::SendStreamRst(s->_host_socket,
|
|
s->_remote_settings.stream_id());
|
|
}
|
|
// ASSERT_EQ(0, brpc::StreamClose(request_stream));
|
|
server.Stop(0);
|
|
server.Join();
|
|
while (!handler.stopped() && !handler.failed()) {
|
|
usleep(100);
|
|
}
|
|
ASSERT_TRUE(handler.failed());
|
|
ASSERT_EQ(0, handler.idle_times());
|
|
ASSERT_EQ(N, handler._expected_next_value);
|
|
}
|
|
|
|
TEST_F(StreamingRpcTest, idle_timeout) {
|
|
HandlerControl hc;
|
|
hc.block = true;
|
|
OrderedInputHandler handler(&hc);
|
|
brpc::StreamOptions opt;
|
|
opt.handler = &handler;
|
|
opt.idle_timeout_ms = 2;
|
|
const int N = 10000;
|
|
opt.max_buf_size = sizeof(uint32_t) * N;
|
|
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 = sizeof(uint32_t) * N;
|
|
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() << " request_stream=" << request_stream;
|
|
usleep(10 * 1000 + 800);
|
|
ASSERT_EQ(0, brpc::StreamClose(request_stream));
|
|
while (!handler.stopped()) {
|
|
usleep(100);
|
|
}
|
|
ASSERT_FALSE(handler.failed());
|
|
// ASSERT_TRUE(handler.idle_times() >= 4 && handler.idle_times() <= 6)
|
|
// << handler.idle_times();
|
|
ASSERT_EQ(0, handler._expected_next_value);
|
|
}
|
|
|
|
class PingPongHandler : public brpc::StreamInputHandler {
|
|
public:
|
|
int on_received_messages(brpc::StreamId id,
|
|
butil::IOBuf *const messages[],
|
|
size_t size) override {
|
|
if (size != 1) {
|
|
LOG(INFO) << "size=" << size;
|
|
_error = true;
|
|
return 0;
|
|
}
|
|
for (size_t i = 0; i < size; ++i) {
|
|
CHECK(messages[i]->length() == sizeof(int));
|
|
int network = 0;
|
|
messages[i]->cutn(&network, sizeof(int));
|
|
if ((int)ntohl(network) != _expected_next_value) {
|
|
_error = true;
|
|
}
|
|
int send_back = ntohl(network) + 1;
|
|
_expected_next_value = send_back + 1;
|
|
butil::IOBuf out;
|
|
network = htonl(send_back);
|
|
out.append(&network, sizeof(network));
|
|
// don't care the return value
|
|
brpc::StreamWrite(id, out);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void on_idle_timeout(brpc::StreamId /*id*/) override {
|
|
++_idle_times;
|
|
}
|
|
|
|
void on_closed(brpc::StreamId /*id*/) override {
|
|
ASSERT_FALSE(_stopped);
|
|
_stopped = true;
|
|
}
|
|
|
|
|
|
void on_failed(brpc::StreamId id, int error_code,
|
|
const std::string& /*error_text*/) override {
|
|
ASSERT_FALSE(_failed);
|
|
ASSERT_NE(0, error_code);
|
|
_failed = true;
|
|
}
|
|
|
|
bool error() const { return _error; }
|
|
bool failed() const { return _failed; }
|
|
bool stopped() const { return _stopped; }
|
|
int idle_times() const { return _idle_times; }
|
|
private:
|
|
int _expected_next_value{0};
|
|
bool _error{false};
|
|
bool _failed{false};
|
|
bool _stopped{false};
|
|
int _idle_times{0};
|
|
};
|
|
|
|
TEST_F(StreamingRpcTest, ping_pong) {
|
|
PingPongHandler resh;
|
|
brpc::StreamOptions opt;
|
|
opt.handler = &resh;
|
|
const int N = 10000;
|
|
opt.max_buf_size = sizeof(uint32_t) * N;
|
|
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;
|
|
PingPongHandler reqh;
|
|
reqh._expected_next_value = 1;
|
|
request_stream_options.handler = &reqh;
|
|
request_stream_options.max_buf_size = sizeof(uint32_t) * N;
|
|
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() << " request_stream=" << request_stream;
|
|
int send = 0;
|
|
butil::IOBuf out;
|
|
out.append(&send, sizeof(send));
|
|
ASSERT_EQ(0, brpc::StreamWrite(request_stream, out));
|
|
usleep(10 * 1000);
|
|
ASSERT_EQ(0, brpc::StreamClose(request_stream));
|
|
while (!resh.stopped() || !reqh.stopped()) {
|
|
usleep(100);
|
|
}
|
|
ASSERT_FALSE(resh.error());
|
|
ASSERT_FALSE(reqh.error());
|
|
ASSERT_EQ(0, resh.idle_times());
|
|
ASSERT_EQ(0, reqh.idle_times());
|
|
}
|
|
|
|
class SendNAfterAcceptStream : public AfterAcceptStream {
|
|
public:
|
|
explicit SendNAfterAcceptStream(int n)
|
|
: _n(n) {}
|
|
void action(brpc::StreamId s) {
|
|
for (int i = 0; i < _n; ++i) {
|
|
int network = htonl(i);
|
|
butil::IOBuf out;
|
|
out.append(&network, sizeof(network));
|
|
ASSERT_EQ(0, brpc::StreamWrite(s, out)) << "i=" << i;
|
|
}
|
|
}
|
|
private:
|
|
int _n;
|
|
};
|
|
|
|
TEST_F(StreamingRpcTest, server_send_data_before_run_done) {
|
|
const int N = 10000;
|
|
SendNAfterAcceptStream after_accept(N);
|
|
brpc::StreamOptions opt;
|
|
opt.max_buf_size = -1;
|
|
brpc::Server server;
|
|
MyServiceWithStream service(opt, &after_accept);
|
|
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));
|
|
OrderedInputHandler handler;
|
|
brpc::StreamOptions request_stream_options;
|
|
request_stream_options.handler = &handler;
|
|
brpc::StreamId request_stream;
|
|
brpc::Controller cntl;
|
|
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() << " request_stream=" << request_stream;
|
|
// wait flushing all the pending messages
|
|
while (handler._expected_next_value != N) {
|
|
usleep(100);
|
|
}
|
|
ASSERT_EQ(0, brpc::StreamClose(request_stream));
|
|
while (!handler.stopped()) {
|
|
usleep(100);
|
|
}
|
|
ASSERT_FALSE(handler.failed());
|
|
ASSERT_EQ(0, handler.idle_times());
|
|
}
|
|
|
|
TEST_F(StreamingRpcTest, segment_stream_data_automatically) {
|
|
GFLAGS_NAMESPACE::SetCommandLineOption("stream_write_max_segment_size", "1");
|
|
OrderedInputHandler handler;
|
|
brpc::StreamOptions opt;
|
|
opt.handler = &handler;
|
|
opt.messages_in_batch = 100;
|
|
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;
|
|
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() << " request_stream=" << request_stream;
|
|
const int N = 1000;
|
|
for (int i = 0; i < N; ++i) {
|
|
int network = htonl(i);
|
|
butil::IOBuf out;
|
|
out.append(&network, sizeof(network));
|
|
ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
ASSERT_EQ(0, brpc::StreamClose(request_stream));
|
|
server.Stop(0);
|
|
server.Join();
|
|
while (!handler.stopped()) {
|
|
usleep(100);
|
|
}
|
|
const int64_t now_ms = butil::cpuwide_time_ms();
|
|
host_socket_ptr->UpdateStatsEverySecond(now_ms);
|
|
brpc::SocketStat stat;
|
|
host_socket_ptr->GetStat(&stat);
|
|
ASSERT_LT(N * sizeof(N), stat.out_num_messages_m);
|
|
ASSERT_FALSE(handler.failed());
|
|
ASSERT_EQ(0, handler.idle_times());
|
|
ASSERT_EQ(N, handler._expected_next_value);
|
|
GFLAGS_NAMESPACE::SetCommandLineOption("stream_write_max_segment_size", "536870912");
|
|
}
|
|
|
|
TEST_F(StreamingRpcTest, create_request_stream_twice_on_same_controller_returns_error) {
|
|
brpc::Controller cntl;
|
|
|
|
brpc::StreamId first_stream = brpc::INVALID_STREAM_ID;
|
|
ASSERT_EQ(0, brpc::StreamCreate(&first_stream, cntl, NULL));
|
|
brpc::ScopedStream stream_guard(first_stream);
|
|
|
|
brpc::StreamId second_stream = brpc::INVALID_STREAM_ID;
|
|
ASSERT_EQ(-1, brpc::StreamCreate(&second_stream, cntl, NULL));
|
|
ASSERT_EQ(brpc::INVALID_STREAM_ID, second_stream);
|
|
}
|