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 e0abb100 Progressive timeout dev (#3409)
e0abb100 is described below
commit e0abb1001ef76d400dddc2b2c3a1ff42f5d3afac
Author: Chuang Zhang <[email protected]>
AuthorDate: Sat Aug 15 16:56:56 2026 +0800
Progressive timeout dev (#3409)
* add the progressive timeout reader
* optimize the code format
* WIP: progressive read timeout review
* test: strengthen progressive read timeout coverage
---
example/http_c++/http_client.cpp | 35 +++++
example/http_c++/http_server.cpp | 7 +
src/brpc/controller.cpp | 259 ++++++++++++++++++++++++++++++-
src/brpc/controller.h | 8 +-
src/brpc/errno.proto | 1 +
src/brpc/policy/http_rpc_protocol.cpp | 1 +
src/brpc/policy/http_rpc_protocol.h | 12 +-
src/brpc/progressive_reader.h | 2 +
test/brpc_http_rpc_protocol_unittest.cpp | 237 +++++++++++++++++++++++++++-
9 files changed, 557 insertions(+), 5 deletions(-)
diff --git a/example/http_c++/http_client.cpp b/example/http_c++/http_client.cpp
index 23222dee..3a09186f 100644
--- a/example/http_c++/http_client.cpp
+++ b/example/http_c++/http_client.cpp
@@ -22,11 +22,15 @@
// - Access www.foo.com
// ./http_client www.foo.com
+#include <string>
#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/channel.h>
+#include "bthread/countdown_event.h"
DEFINE_string(d, "", "POST this data to the http server");
+DEFINE_bool(progressive, false, "whether or not progressive read data from
server");
+DEFINE_int32(progressive_read_timeout_ms, 5000, "progressive read data idle
timeout in milliseconds");
DEFINE_string(load_balancer, "", "The algorithm for load balancing");
DEFINE_int32(timeout_ms, 2000, "RPC timeout in milliseconds");
DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)");
@@ -36,6 +40,25 @@ namespace brpc {
DECLARE_bool(http_verbose);
}
+class PartDataReader: public brpc::ProgressiveReader {
+public:
+ explicit PartDataReader(bthread::CountdownEvent* done): _done(done){}
+
+ butil::Status OnReadOnePart(const void* data, size_t length) {
+ const std::string part(static_cast<const char*>(data), length);
+ LOG(INFO) << "data: " << part << " size: " << length;
+ return butil::Status::OK();
+ }
+
+ void OnEndOfMessage(const butil::Status& status) {
+ LOG(INFO) << "progressive read data final status : " << status;
+ _done->signal();
+ delete this;
+ }
+private:
+ bthread::CountdownEvent* _done;
+};
+
int main(int argc, char* argv[]) {
// Parse gflags. We recommend you to use gflags as well.
GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
@@ -71,6 +94,11 @@ int main(int argc, char* argv[]) {
cntl.request_attachment().append(FLAGS_d);
}
+ if (FLAGS_progressive) {
+
cntl.set_progressive_read_timeout_ms(FLAGS_progressive_read_timeout_ms);
+ cntl.response_will_be_read_progressively();
+ }
+
// Because `done'(last parameter) is NULL, this function waits until
// the response comes back or error occurs(including timedout).
channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
@@ -78,6 +106,13 @@ int main(int argc, char* argv[]) {
std::cerr << cntl.ErrorText() << std::endl;
return -1;
}
+
+ if (FLAGS_progressive) {
+ bthread::CountdownEvent done(1);
+ cntl.ReadProgressiveAttachmentBy(new PartDataReader(&done));
+ done.wait();
+ LOG(INFO) << "wait client progressive read done safely";
+ }
// If -http_verbose is on, brpc already prints the response to stderr.
if (!brpc::FLAGS_http_verbose) {
std::cout << cntl.response_attachment() << std::endl;
diff --git a/example/http_c++/http_server.cpp b/example/http_c++/http_server.cpp
index 05c9a0ee..4c3c8722 100644
--- a/example/http_c++/http_server.cpp
+++ b/example/http_c++/http_server.cpp
@@ -31,6 +31,7 @@ DEFINE_int32(idle_timeout_s, -1, "Connection will be closed
if there is no "
DEFINE_string(certificate, "cert.pem", "Certificate file path to enable SSL");
DEFINE_string(private_key, "key.pem", "Private key file path to enable SSL");
DEFINE_string(ciphers, "", "Cipher suite used for SSL connections");
+DEFINE_bool(enable_progressive_timeout, false, "whether or not trigger
progressive write attachment data timeout");
namespace example {
@@ -104,6 +105,9 @@ public:
// sleep a while to send another part.
bthread_usleep(10000);
+ if (FLAGS_enable_progressive_timeout && i > 50) {
+ bthread_usleep(100000000UL);
+ }
}
return NULL;
}
@@ -194,6 +198,9 @@ public:
// sleep a while to send another part.
bthread_usleep(10000 * 10);
+ if (FLAGS_enable_progressive_timeout && i > 50) {
+ bthread_usleep(100000000UL);
+ }
}
return NULL;
}
diff --git a/src/brpc/controller.cpp b/src/brpc/controller.cpp
index 8a8410be..66a9a3a5 100644
--- a/src/brpc/controller.cpp
+++ b/src/brpc/controller.cpp
@@ -73,6 +73,7 @@ BAIDU_REGISTER_ERRNO(brpc::EEOF, "Got EOF");
BAIDU_REGISTER_ERRNO(brpc::EUNUSED, "The socket was not needed");
BAIDU_REGISTER_ERRNO(brpc::ESSL, "SSL related operation failed");
BAIDU_REGISTER_ERRNO(brpc::EH2RUNOUTSTREAMS, "The H2 socket was run out of
streams");
+BAIDU_REGISTER_ERRNO(brpc::EPROGREADTIMEOUT, "Progressive read timed out");
BAIDU_REGISTER_ERRNO(brpc::EINTERNAL, "General internal error");
BAIDU_REGISTER_ERRNO(brpc::ERESPONSE, "Bad response");
@@ -94,8 +95,9 @@ namespace brpc {
DEFINE_bool(graceful_quit_on_sigterm, false,
"Register SIGTERM handle func to quit graceful");
DEFINE_bool(graceful_quit_on_sighup, false,
- "Register SIGHUP handle func to quit graceful");
-
+ "Register SIGHUP handle func to quit graceful");
+DEFINE_bool(log_idle_progressive_read_close, false,
+ "Print log when an idle progressive read is closed");
const IdlNames idl_single_req_single_res = { "req", "res" };
const IdlNames idl_single_req_multi_res = { "req", "" };
const IdlNames idl_multi_req_single_res = { "", "res" };
@@ -174,6 +176,226 @@ public:
void OnEndOfMessage(const butil::Status&) {}
};
+struct ProgressiveReadTimeoutTask;
+
+struct ProgressiveReadTimeoutState {
+ ProgressiveReadTimeoutState(SocketId id, int32_t timeout_ms)
+ : socket_id(id)
+ , read_timeout_ms(timeout_ms)
+ , deadline_us(butil::cpuwide_time_us() + timeout_ms * 1000L)
+ , timer_id(0)
+ , timer_task(NULL)
+ , user_callback_running(false)
+ , reader_failed(false)
+ , timeout_triggered(false)
+ , end_delivered(false) {}
+
+ butil::Mutex mutex;
+ const SocketId socket_id;
+ const int32_t read_timeout_ms;
+ int64_t deadline_us;
+ bthread_timer_t timer_id;
+ ProgressiveReadTimeoutTask* timer_task;
+ bool user_callback_running;
+ bool reader_failed;
+ bool timeout_triggered;
+ bool end_delivered;
+ butil::Status timer_error;
+};
+
+struct ProgressiveReadTimeoutTask {
+ explicit ProgressiveReadTimeoutTask(
+ const std::shared_ptr<ProgressiveReadTimeoutState>& state_in)
+ : state(state_in) {}
+
+ std::shared_ptr<ProgressiveReadTimeoutState> state;
+};
+
+class ProgressiveTimeoutReader : public ProgressiveReader {
+public:
+ ProgressiveTimeoutReader(SocketId id, int32_t read_timeout_ms,
+ ProgressiveReader* reader)
+ : _reader(reader)
+ , _state(new ProgressiveReadTimeoutState(id, read_timeout_ms)) {}
+
+ int Start() {
+ std::unique_lock<butil::Mutex> mu(_state->mutex);
+ return AddWatchdogLocked(_state, _state->read_timeout_ms * 1000L);
+ }
+
+ butil::Status OnReadOnePart(const void* data, size_t length) override {
+ {
+ std::unique_lock<butil::Mutex> mu(_state->mutex);
+ if (_state->timeout_triggered) {
+ return MakeTimeoutStatus(_state->read_timeout_ms);
+ }
+ if (!_state->timer_error.ok()) {
+ return _state->timer_error;
+ }
+ _state->user_callback_running = true;
+ }
+
+ butil::Status status = _reader->OnReadOnePart(data, length);
+ {
+ std::unique_lock<butil::Mutex> mu(_state->mutex);
+ _state->user_callback_running = false;
+ if (_state->timeout_triggered) {
+ status = MakeTimeoutStatus(_state->read_timeout_ms);
+ } else if (!_state->timer_error.ok()) {
+ status = _state->timer_error;
+ } else if (status.ok() && !_state->end_delivered) {
+ _state->deadline_us = butil::cpuwide_time_us() +
+ _state->read_timeout_ms * 1000L;
+ } else if (!status.ok()) {
+ _state->reader_failed = true;
+ }
+ }
+ return status;
+ }
+
+ void OnEndOfMessage(const butil::Status& status) override {
+ bthread_timer_t timer_id = 0;
+ ProgressiveReadTimeoutTask* timer_task = NULL;
+ butil::Status final_status = status;
+ ProgressiveReader* reader = NULL;
+ {
+ std::unique_lock<butil::Mutex> mu(_state->mutex);
+ if (_state->end_delivered) {
+ LOG(ERROR) << "ProgressiveReader::OnEndOfMessage was called
more than once";
+ return;
+ }
+ _state->end_delivered = true;
+ timer_id = _state->timer_id;
+ timer_task = _state->timer_task;
+ _state->timer_id = 0;
+ _state->timer_task = NULL;
+ if (_state->timeout_triggered) {
+ final_status = MakeTimeoutStatus(_state->read_timeout_ms);
+ } else if (!_state->timer_error.ok()) {
+ final_status = _state->timer_error;
+ }
+ reader = _reader;
+ _reader = NULL;
+ }
+
+ CancelWatchdog(timer_id, timer_task);
+ reader->OnEndOfMessage(final_status);
+ delete this;
+ }
+
+private:
+ ~ProgressiveTimeoutReader() override {}
+
+ static butil::Status MakeTimeoutStatus(int32_t timeout_ms) {
+ return butil::Status(
+ EPROGREADTIMEOUT,
+ "Progressive read timed out after %d ms", timeout_ms);
+ }
+
+ static butil::Status MakeTimerErrorStatus(int error_code) {
+ return butil::Status(
+ error_code, "Fail to add progressive read timeout timer: %s",
+ berror(error_code));
+ }
+
+ static void CancelWatchdog(
+ bthread_timer_t timer_id, ProgressiveReadTimeoutTask* timer_task) {
+ if (timer_id == 0) {
+ return;
+ }
+ const int rc = bthread_timer_del(timer_id);
+ if (rc == 0) {
+ delete timer_task;
+ } else if (rc == 1 || rc == EINVAL) {
+ // The callback owns timer_task once it starts running. EINVAL
means
+ // that the callback has already finished and released the task.
+ } else {
+ LOG(ERROR) << "Unexpected bthread_timer_del error=" << rc;
+ }
+ }
+
+ static int AddWatchdogLocked(
+ const std::shared_ptr<ProgressiveReadTimeoutState>& state,
+ int64_t delay_us) {
+ if (state->end_delivered || state->reader_failed) {
+ return ECANCELED;
+ }
+ if (delay_us <= 0) {
+ delay_us = 1;
+ }
+ ProgressiveReadTimeoutTask* task =
+ new (std::nothrow) ProgressiveReadTimeoutTask(state);
+ if (task == NULL) {
+ return ENOMEM;
+ }
+ bthread_timer_t timer_id = 0;
+ const int rc = bthread_timer_add(
+ &timer_id, butil::microseconds_from_now(delay_us),
+ HandleIdleProgressiveReader, task);
+ if (rc != 0) {
+ delete task;
+ return rc;
+ }
+ state->timer_id = timer_id;
+ state->timer_task = task;
+ return 0;
+ }
+
+ static void HandleIdleProgressiveReader(void* arg) {
+ std::unique_ptr<ProgressiveReadTimeoutTask> task(
+ static_cast<ProgressiveReadTimeoutTask*>(arg));
+ const std::shared_ptr<ProgressiveReadTimeoutState> state = task->state;
+ bool fail_socket = false;
+ int error_code = 0;
+ std::string error_text;
+ {
+ std::unique_lock<butil::Mutex> mu(state->mutex);
+ if (state->timer_task == task.get()) {
+ state->timer_id = 0;
+ state->timer_task = NULL;
+ }
+ if (state->end_delivered || state->reader_failed) {
+ return;
+ }
+
+ const int64_t now_us = butil::cpuwide_time_us();
+ if (state->user_callback_running || now_us < state->deadline_us) {
+ const int64_t delay_us = state->user_callback_running
+ ? state->read_timeout_ms * 1000L
+ : state->deadline_us - now_us;
+ const int rc = AddWatchdogLocked(state, delay_us);
+ if (rc != 0) {
+ state->timer_error = MakeTimerErrorStatus(rc);
+ fail_socket = true;
+ error_code = rc;
+ error_text = state->timer_error.error_str();
+ }
+ } else {
+ state->timeout_triggered = true;
+ fail_socket = true;
+ error_code = EPROGREADTIMEOUT;
+ error_text =
MakeTimeoutStatus(state->read_timeout_ms).error_str();
+ }
+ }
+
+ if (!fail_socket) {
+ return;
+ }
+ SocketUniquePtr socket;
+ if (Socket::Address(state->socket_id, &socket) != 0) {
+ LOG(ERROR) << "Fail to address socket_id=" << state->socket_id
+ << " after progressive read timeout";
+ } else {
+ LOG_IF(INFO, FLAGS_log_idle_progressive_read_close)
+ << error_text << ", socket_id=" << state->socket_id;
+ socket->SetFailed(error_code, "%s", error_text.c_str());
+ }
+ }
+
+ ProgressiveReader* _reader;
+ const std::shared_ptr<ProgressiveReadTimeoutState> _state;
+};
+
static IgnoreAllRead* s_ignore_all_read = NULL;
static pthread_once_t s_ignore_all_read_once = PTHREAD_ONCE_INIT;
static void CreateIgnoreAllRead() { s_ignore_all_read = new IgnoreAllRead; }
@@ -261,6 +483,7 @@ void Controller::ResetPods() {
_backup_request_ms = UNSET_MAGIC_NUM;
_backup_request_policy = NULL;
_connect_timeout_ms = UNSET_MAGIC_NUM;
+ _progressive_read_timeout_ms = UNSET_MAGIC_NUM;
_real_timeout_ms = UNSET_MAGIC_NUM;
_deadline_us = -1;
_timeout_id = 0;
@@ -336,6 +559,11 @@ void Controller::Call::Reset() {
stream_user_data = NULL;
}
+void Controller::set_progressive_read_timeout_ms(
+ int32_t progressive_read_timeout_ms) {
+ _progressive_read_timeout_ms = progressive_read_timeout_ms;
+}
+
void Controller::set_timeout_ms(int64_t timeout_ms) {
if (timeout_ms <= 0x7fffffff) {
_timeout_ms = timeout_ms;
@@ -1613,6 +1841,33 @@ void
Controller::ReadProgressiveAttachmentBy(ProgressiveReader* r) {
__FUNCTION__));
}
add_flag(FLAGS_PROGRESSIVE_READER);
+ if (progressive_read_timeout_ms() > 0) {
+ const SocketId socket_id = _rpa->GetSocketId();
+ if (socket_id == INVALID_SOCKET_ID) {
+ pthread_once(&s_ignore_all_read_once, CreateIgnoreAllRead);
+ _rpa->ReadProgressiveAttachmentBy(s_ignore_all_read);
+ return r->OnEndOfMessage(butil::Status(
+ ENOTSUP,
+ "Progressive read timeout is only supported for HTTP/1.x"));
+ }
+ ProgressiveTimeoutReader* reader = new (std::nothrow)
+ ProgressiveTimeoutReader(
+ socket_id, _progressive_read_timeout_ms, r);
+ if (reader == NULL) {
+ pthread_once(&s_ignore_all_read_once, CreateIgnoreAllRead);
+ _rpa->ReadProgressiveAttachmentBy(s_ignore_all_read);
+ return r->OnEndOfMessage(
+ butil::Status(ENOMEM, "Fail to create progressive timeout
reader"));
+ }
+ const int rc = reader->Start();
+ if (rc != 0) {
+ pthread_once(&s_ignore_all_read_once, CreateIgnoreAllRead);
+ _rpa->ReadProgressiveAttachmentBy(s_ignore_all_read);
+ return reader->OnEndOfMessage(butil::Status(
+ rc, "Fail to add progressive read timeout timer: %s",
berror(rc)));
+ }
+ return _rpa->ReadProgressiveAttachmentBy(reader);
+ }
return _rpa->ReadProgressiveAttachmentBy(r);
}
diff --git a/src/brpc/controller.h b/src/brpc/controller.h
index 564c0875..41d42ae7 100644
--- a/src/brpc/controller.h
+++ b/src/brpc/controller.h
@@ -199,6 +199,9 @@ public:
// Set/get timeout in milliseconds for the RPC call. Use
// ChannelOptions.timeout_ms on unset.
+ void set_progressive_read_timeout_ms(int32_t progressive_read_timeout_ms);
+ int32_t progressive_read_timeout_ms() const { return
_progressive_read_timeout_ms; }
+
void set_timeout_ms(int64_t timeout_ms);
int64_t timeout_ms() const { return _timeout_ms; }
@@ -361,7 +364,9 @@ public:
// Make the RPC end when the HTTP response has complete headers and let
// user read the remaining body by using ReadProgressiveAttachmentBy().
- void response_will_be_read_progressively() {
add_flag(FLAGS_READ_PROGRESSIVELY); }
+ void response_will_be_read_progressively() {
+ add_flag(FLAGS_READ_PROGRESSIVELY);
+ }
// Make the RPC end when the HTTP request has complete headers and let
// user read the remaining body by using ReadProgressiveAttachmentBy().
void request_will_be_read_progressively() {
add_flag(FLAGS_READ_PROGRESSIVELY); }
@@ -911,6 +916,7 @@ private:
int32_t _timeout_ms;
int32_t _connect_timeout_ms;
int32_t _backup_request_ms;
+ int32_t _progressive_read_timeout_ms;
// Priority: `_backup_request_policy' > `_backup_request_ms'.
BackupRequestPolicy* _backup_request_policy;
// If this rpc call has retry/backup request,this var save the real
timeout for current call
diff --git a/src/brpc/errno.proto b/src/brpc/errno.proto
index 26ffadc2..166d82dc 100644
--- a/src/brpc/errno.proto
+++ b/src/brpc/errno.proto
@@ -41,6 +41,7 @@ enum Errno {
ESSL = 1016; // SSL related error
EH2RUNOUTSTREAMS = 1017; // The H2 socket was run out of streams
EREJECT = 1018; // The Request is rejected
+ EPROGREADTIMEOUT = 1019; // The Progressive read timeout
// Errno caused by server
EINTERNAL = 2001; // Internal Server Error
diff --git a/src/brpc/policy/http_rpc_protocol.cpp
b/src/brpc/policy/http_rpc_protocol.cpp
index 8cbe0698..3fb94088 100644
--- a/src/brpc/policy/http_rpc_protocol.cpp
+++ b/src/brpc/policy/http_rpc_protocol.cpp
@@ -1201,6 +1201,7 @@ ParseResult ParseHttpMessage(butil::IOBuf *source, Socket
*socket,
LOG(FATAL) << "Fail to new HttpContext";
return MakeParseError(PARSE_ERROR_NO_RESOURCE);
}
+ http_imsg->SetSocketId(socket->id());
// Parsing http is costly, parsing an incomplete http message from the
// beginning repeatedly should be avoided, otherwise the cost may reach
// O(n^2) in the worst case. Save incomplete http messages in sockets
diff --git a/src/brpc/policy/http_rpc_protocol.h
b/src/brpc/policy/http_rpc_protocol.h
index bc8bd065..cd41798e 100644
--- a/src/brpc/policy/http_rpc_protocol.h
+++ b/src/brpc/policy/http_rpc_protocol.h
@@ -87,11 +87,20 @@ class HttpContext : public ReadableProgressiveAttachment
, public InputMessageBase
, public HttpMessage {
public:
+ SocketId GetSocketId() override {
+ return _socket_id;
+ }
+
+ void SetSocketId(SocketId id) {
+ _socket_id = id;
+ }
+
explicit HttpContext(bool read_body_progressively,
HttpMethod request_method = HTTP_METHOD_GET)
: InputMessageBase()
, HttpMessage(read_body_progressively, request_method)
- , _is_stage2(false) {
+ , _is_stage2(false)
+ , _socket_id(INVALID_SOCKET_ID) {
// add one ref for Destroy
butil::intrusive_ptr<HttpContext>(this).detach();
}
@@ -122,6 +131,7 @@ public:
private:
bool _is_stage2;
+ SocketId _socket_id;
};
// Implement functions required in protocol.h
diff --git a/src/brpc/progressive_reader.h b/src/brpc/progressive_reader.h
index 6f54ae68..c84be8b7 100644
--- a/src/brpc/progressive_reader.h
+++ b/src/brpc/progressive_reader.h
@@ -20,6 +20,7 @@
#define BRPC_PROGRESSIVE_READER_H
#include "brpc/shared_object.h"
+#include "brpc/socket_id.h"
namespace brpc {
@@ -84,6 +85,7 @@ public:
// Any error occurred should destroy the reader by calling r->Destroy().
// r->Destroy() should be guaranteed to be called once and only once.
virtual void ReadProgressiveAttachmentBy(ProgressiveReader* r) = 0;
+ virtual SocketId GetSocketId() = 0;
};
} // namespace brpc
diff --git a/test/brpc_http_rpc_protocol_unittest.cpp
b/test/brpc_http_rpc_protocol_unittest.cpp
index 87837abf..6735a171 100644
--- a/test/brpc_http_rpc_protocol_unittest.cpp
+++ b/test/brpc_http_rpc_protocol_unittest.cpp
@@ -19,6 +19,7 @@
// Date: Sun Jul 13 15:04:18 CST 2014
+#include <atomic>
#include <cstddef>
#include <string>
#include <sys/ioctl.h>
@@ -736,9 +737,13 @@ static void CopyPAPrefixedWithSeqNo(char* buf, uint64_t
seq_no) {
class DownloadServiceImpl : public ::test::DownloadService {
public:
DownloadServiceImpl(DonePlace done_place = DONE_BEFORE_CREATE_PA,
- size_t num_repeat = 1)
+ size_t num_repeat = 1,
+ int write_interval_us = 0,
+ int initial_write_delay_us = 0)
: _done_place(done_place)
, _nrep(num_repeat)
+ , _write_interval_us(write_interval_us)
+ , _initial_write_delay_us(initial_write_delay_us)
, _nwritten(0)
, _ever_full(false)
, _last_errno(0) {}
@@ -762,6 +767,9 @@ public:
if (_done_place == DONE_BEFORE_CREATE_PA) {
done_guard.reset(NULL);
}
+ if (_initial_write_delay_us > 0) {
+ bthread_usleep(_initial_write_delay_us);
+ }
ASSERT_GT(PA_DATA_LEN, 8u); // long enough to hold a 64-bit decimal.
char buf[PA_DATA_LEN];
for (size_t c = 0; c < _nrep;) {
@@ -778,6 +786,9 @@ public:
}
} else {
_nwritten += PA_DATA_LEN;
+ if (_write_interval_us > 0) {
+ bthread_usleep(_write_interval_us);
+ }
}
++c;
}
@@ -840,6 +851,8 @@ public:
private:
DonePlace _done_place;
size_t _nrep;
+ int _write_interval_us;
+ int _initial_write_delay_us;
size_t _nwritten;
bool _ever_full;
int _last_errno;
@@ -941,6 +954,47 @@ private:
butil::Status _destroying_st;
};
+class TimeoutReadBody : public brpc::ProgressiveReader,
+ public brpc::SharedObject {
+public:
+ explicit TimeoutReadBody(int read_delay_us = 0, int read_error = 0)
+ : _read_delay_us(read_delay_us)
+ , _read_error(read_error)
+ , _nread(0)
+ , _nend(0)
+ , _end_error(0) {
+ butil::intrusive_ptr<TimeoutReadBody>(this).detach();
+ }
+
+ butil::Status OnReadOnePart(const void*, size_t length) override {
+ if (_read_delay_us > 0) {
+ bthread_usleep(_read_delay_us);
+ }
+ _nread.fetch_add(length);
+ if (_read_error != 0) {
+ return butil::Status(_read_error, "intended progressive read
failure");
+ }
+ return butil::Status::OK();
+ }
+
+ void OnEndOfMessage(const butil::Status& status) override {
+ _end_error.store(status.error_code());
+ _nend.fetch_add(1);
+ butil::intrusive_ptr<TimeoutReadBody>(this, false);
+ }
+
+ size_t read_bytes() const { return _nread.load(); }
+ int end_count() const { return _nend.load(); }
+ int end_error() const { return _end_error.load(); }
+
+private:
+ const int _read_delay_us;
+ const int _read_error;
+ std::atomic<size_t> _nread;
+ std::atomic<int> _nend;
+ std::atomic<int> _end_error;
+};
+
#ifdef BUTIL_USE_ASAN
static const int GENERAL_DELAY_US = 1000000; // 1s
#else
@@ -1034,6 +1088,187 @@ TEST_F(HttpTest, read_short_body_progressively) {
}
}
+TEST_F(HttpTest, progressive_read_timeout_keeps_active_reader_alive) {
+ const int port = 8923;
+ brpc::Server server;
+ DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, 8, 100000);
+ ASSERT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE));
+ ASSERT_EQ(0, server.Start(port, NULL));
+
+ brpc::Channel channel;
+ brpc::ChannelOptions options;
+ options.protocol = brpc::PROTOCOL_HTTP;
+ ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port),
&options));
+
+ brpc::Controller cntl;
+ cntl.response_will_be_read_progressively();
+ cntl.set_progressive_read_timeout_ms(500);
+ cntl.http_request().uri() = "/DownloadService/Download";
+ channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
+ ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText();
+
+ butil::intrusive_ptr<TimeoutReadBody> reader(new TimeoutReadBody);
+ cntl.ReadProgressiveAttachmentBy(reader.get());
+ for (int i = 0; i < 200 && reader->end_count() == 0; ++i) {
+ bthread_usleep(10000);
+ }
+ ASSERT_EQ(1, reader->end_count());
+ EXPECT_EQ(0, reader->end_error());
+ EXPECT_EQ(8 * PA_DATA_LEN, reader->read_bytes());
+}
+
+TEST_F(HttpTest, progressive_read_timeout_closes_idle_http1_reader_once) {
+ const int port = 8923;
+ brpc::Server server;
+ DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, 2, 300000);
+ ASSERT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE));
+ ASSERT_EQ(0, server.Start(port, NULL));
+
+ butil::intrusive_ptr<TimeoutReadBody> reader(new TimeoutReadBody);
+ {
+ brpc::Channel channel;
+ brpc::ChannelOptions options;
+ options.protocol = brpc::PROTOCOL_HTTP;
+ ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port),
&options));
+ {
+ brpc::Controller cntl;
+ cntl.response_will_be_read_progressively();
+ cntl.set_progressive_read_timeout_ms(50);
+ cntl.http_request().uri() = "/DownloadService/Download";
+ channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
+ ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText();
+ cntl.ReadProgressiveAttachmentBy(reader.get());
+ bthread_usleep(400000);
+ ASSERT_NE(0, svc.last_errno());
+ EXPECT_EQ(0, reader->end_count());
+ }
+ }
+ for (int i = 0; i < 100 && reader->end_count() == 0; ++i) {
+ bthread_usleep(10000);
+ }
+ ASSERT_EQ(1, reader->end_count());
+ EXPECT_EQ(brpc::EPROGREADTIMEOUT, reader->end_error());
+ bthread_usleep(400000);
+ EXPECT_EQ(1, reader->end_count());
+}
+
+TEST_F(HttpTest, progressive_read_timeout_before_first_body_part) {
+ const int port = 8923;
+ brpc::Server server;
+ DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, 1, 0, 300000);
+ ASSERT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE));
+ ASSERT_EQ(0, server.Start(port, NULL));
+
+ butil::intrusive_ptr<TimeoutReadBody> reader(new TimeoutReadBody);
+ {
+ brpc::Channel channel;
+ brpc::ChannelOptions options;
+ options.protocol = brpc::PROTOCOL_HTTP;
+ ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port),
&options));
+ {
+ brpc::Controller cntl;
+ cntl.response_will_be_read_progressively();
+ cntl.set_progressive_read_timeout_ms(50);
+ cntl.http_request().uri() = "/DownloadService/Download";
+ channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
+ ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText();
+ cntl.ReadProgressiveAttachmentBy(reader.get());
+ bthread_usleep(400000);
+ ASSERT_NE(0, svc.last_errno());
+ EXPECT_EQ(size_t(0), reader->read_bytes());
+ EXPECT_EQ(0, reader->end_count());
+ }
+ }
+ for (int i = 0; i < 100 && reader->end_count() == 0; ++i) {
+ bthread_usleep(10000);
+ }
+ ASSERT_EQ(1, reader->end_count());
+ EXPECT_EQ(brpc::EPROGREADTIMEOUT, reader->end_error());
+}
+
+TEST_F(HttpTest, progressive_read_timeout_ignores_slow_user_callback) {
+ const int port = 8923;
+ brpc::Server server;
+ DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, 3, 50000);
+ ASSERT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE));
+ ASSERT_EQ(0, server.Start(port, NULL));
+
+ brpc::Channel channel;
+ brpc::ChannelOptions options;
+ options.protocol = brpc::PROTOCOL_HTTP;
+ ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port),
&options));
+
+ brpc::Controller cntl;
+ cntl.response_will_be_read_progressively();
+ cntl.set_progressive_read_timeout_ms(50);
+ cntl.http_request().uri() = "/DownloadService/Download";
+ channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
+ ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText();
+
+ butil::intrusive_ptr<TimeoutReadBody> reader(
+ new TimeoutReadBody(200000));
+ cntl.ReadProgressiveAttachmentBy(reader.get());
+ for (int i = 0; i < 100 && reader->end_count() == 0; ++i) {
+ bthread_usleep(10000);
+ }
+ ASSERT_EQ(1, reader->end_count());
+ EXPECT_EQ(0, reader->end_error());
+ EXPECT_EQ(3 * PA_DATA_LEN, reader->read_bytes());
+}
+
+TEST_F(HttpTest, progressive_read_timeout_preserves_reader_error) {
+ const int port = 8923;
+ brpc::Server server;
+ DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, 10);
+ ASSERT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE));
+ ASSERT_EQ(0, server.Start(port, NULL));
+
+ brpc::Channel channel;
+ brpc::ChannelOptions options;
+ options.protocol = brpc::PROTOCOL_HTTP;
+ ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port),
&options));
+
+ brpc::Controller cntl;
+ cntl.response_will_be_read_progressively();
+ cntl.set_progressive_read_timeout_ms(1000);
+ cntl.http_request().uri() = "/DownloadService/Download";
+ channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
+ ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText();
+
+ butil::intrusive_ptr<TimeoutReadBody> reader(
+ new TimeoutReadBody(0, EIO));
+ cntl.ReadProgressiveAttachmentBy(reader.get());
+ ASSERT_EQ(1, reader->end_count());
+ EXPECT_EQ(EIO, reader->end_error());
+}
+
+TEST_F(HttpTest, progressive_read_timeout_rejects_http2) {
+ const int port = 8923;
+ brpc::Server server;
+ ASSERT_EQ(0, server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE));
+ ASSERT_EQ(0, server.Start(port, NULL));
+
+ brpc::Channel channel;
+ brpc::ChannelOptions options;
+ options.protocol = brpc::PROTOCOL_H2;
+ ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port),
&options));
+
+ brpc::Controller cntl;
+ cntl.response_will_be_read_progressively();
+ cntl.set_progressive_read_timeout_ms(1000);
+ cntl.http_request().uri() = "/EchoService/Echo";
+ test::EchoRequest req;
+ req.set_message(EXP_REQUEST);
+ channel.CallMethod(NULL, &cntl, &req, NULL, NULL);
+ ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText();
+
+ butil::intrusive_ptr<TimeoutReadBody> reader(new TimeoutReadBody);
+ cntl.ReadProgressiveAttachmentBy(reader.get());
+ ASSERT_EQ(1, reader->end_count());
+ EXPECT_EQ(ENOTSUP, reader->end_error());
+ EXPECT_EQ(size_t(0), reader->read_bytes());
+}
+
TEST_F(HttpTest, read_progressively_after_cntl_destroys) {
DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA,
std::numeric_limits<size_t>::max());
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]