src: Avoid copy in evbuffer_pullup()

Previously, we use evbuffer_pullup(buf, -1) to linearize the memory
region and it may cause buffer copy.  To avoid this, we use the return
value of evbuffer_get_contiguous_space() as 2nd parameter.  According
to the libevent manual, by doing so evbuffer_pullup() will not copy or
modify any data in evbuffer.
This commit is contained in:
Tatsuhiro Tsujikawa 2014-06-01 21:01:01 +09:00
parent cc250386df
commit 8c67bbe3a8
7 changed files with 350 additions and 177 deletions

View File

@ -153,16 +153,33 @@ void Http2Session::submit_request()
ssize_t Http2Session::on_read()
{
int rv;
auto input = bufferevent_get_input(client_->bev);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
size_t nread = 0;
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if(rv < 0) {
return -1;
auto input = bufferevent_get_input(client_->bev);
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return nread;
}
auto mem = evbuffer_pullup(input, inputlen);
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if(rv < 0) {
return -1;
}
nread += rv;
if(evbuffer_drain(input, rv) != 0) {
return -1;
}
}
evbuffer_drain(input, rv);
return rv;
}
int Http2Session::on_write()

View File

@ -24,6 +24,8 @@
*/
#include "h2load_spdy_session.h"
#include <cassert>
#include "h2load.h"
namespace h2load {
@ -163,16 +165,32 @@ void SpdySession::submit_request()
ssize_t SpdySession::on_read()
{
int rv;
size_t nread = 0;
auto input = bufferevent_get_input(client_->bev);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
rv = spdylay_session_mem_recv(session_, mem, inputlen);
if(rv < 0) {
return -1;
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return nread;
}
auto mem = evbuffer_pullup(input, inputlen);
rv = spdylay_session_mem_recv(session_, mem, inputlen);
if(rv < 0) {
return -1;
}
nread += rv;
if(evbuffer_drain(input, rv) != 0) {
return -1;
}
}
evbuffer_drain(input, rv);
return rv;
}
int SpdySession::on_write()

View File

@ -651,53 +651,75 @@ struct HttpClient {
{
int rv;
auto input = bufferevent_get_input(bev);
auto inputlen = evbuffer_get_length(input);
if(inputlen == 0) {
return 0;
}
auto mem = evbuffer_pullup(input, -1);
auto nread = http_parser_execute(htp.get(), &htp_hooks,
reinterpret_cast<const char*>(mem),
inputlen);
if(config.verbose) {
std::cout.write(reinterpret_cast<const char*>(mem), nread);
}
evbuffer_drain(input, nread);
auto htperr = HTTP_PARSER_ERRNO(htp.get());
if(htperr == HPE_OK) {
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return 0;
}
auto mem = evbuffer_pullup(input, inputlen);
auto nread = http_parser_execute(htp.get(), &htp_hooks,
reinterpret_cast<const char*>(mem),
inputlen);
if(config.verbose) {
std::cout.write(reinterpret_cast<const char*>(mem), nread);
}
if(evbuffer_drain(input, nread) != 0) {
return -1;
}
auto htperr = HTTP_PARSER_ERRNO(htp.get());
if(htperr != HPE_OK) {
std::cerr << "Failed to parse HTTP Upgrade response header: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr) << std::endl;
return -1;
}
if(upgrade_response_complete) {
if(config.verbose) {
std::cout << std::endl;
}
if(upgrade_response_status_code == 101) {
if(config.verbose) {
print_timer();
std::cout << " HTTP Upgrade success" << std::endl;
}
bufferevent_setcb(bev, readcb, writecb, eventcb, this);
rv = on_connect();
if(rv != 0) {
return rv;
}
// Read remaining data in the buffer because it is not
// notified callback anymore.
rv = on_read();
if(rv != 0) {
return rv;
}
} else {
std::cerr << "HTTP Upgrade failed" << std::endl;
return -1;
return 0;
}
std::cerr << "HTTP Upgrade failed" << std::endl;
return -1;
}
} else {
std::cerr << "Failed to parse HTTP Upgrade response header: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr) << std::endl;
return -1;
}
return 0;
}
int on_connect()
@ -776,18 +798,30 @@ struct HttpClient {
{
int rv;
auto input = bufferevent_get_input(bev);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
rv = nghttp2_session_mem_recv(session, mem, inputlen);
if(rv < 0) {
std::cerr << "nghttp2_session_mem_recv() returned error: "
<< nghttp2_strerror(rv) << std::endl;
return -1;
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return on_write();
}
auto mem = evbuffer_pullup(input, inputlen);
rv = nghttp2_session_mem_recv(session, mem, inputlen);
if(rv < 0) {
std::cerr << "nghttp2_session_mem_recv() returned error: "
<< nghttp2_strerror(rv) << std::endl;
return -1;
}
if(evbuffer_drain(input, rv) != 0) {
return -1;
}
}
evbuffer_drain(input, rv);
return on_write();
}
int on_write()

View File

@ -541,21 +541,32 @@ http_parser_settings htp_hooks = {
int Http2Session::on_read_proxy()
{
auto input = bufferevent_get_input(bev_);
auto mem = evbuffer_pullup(input, -1);
size_t nread = http_parser_execute(proxy_htp_.get(), &htp_hooks,
reinterpret_cast<const char*>(mem),
evbuffer_get_length(input));
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(evbuffer_drain(input, nread) != 0) {
SSLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
auto htperr = HTTP_PARSER_ERRNO(proxy_htp_.get());
if(htperr == HPE_OK) {
return 0;
} else {
return -1;
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return 0;
}
auto mem = evbuffer_pullup(input, inputlen);
size_t nread = http_parser_execute(proxy_htp_.get(), &htp_hooks,
reinterpret_cast<const char*>(mem),
inputlen);
if(evbuffer_drain(input, nread) != 0) {
SSLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
auto htperr = HTTP_PARSER_ERRNO(proxy_htp_.get());
if(htperr != HPE_OK) {
return -1;
}
}
}
@ -1250,17 +1261,31 @@ int Http2Session::on_read()
{
ssize_t rv = 0;
auto input = bufferevent_get_input(bev_);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if(rv < 0) {
SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: "
<< nghttp2_strerror(rv);
return -1;
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return send();
}
auto mem = evbuffer_pullup(input, inputlen);
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if(rv < 0) {
SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: "
<< nghttp2_strerror(rv);
return -1;
}
if(evbuffer_drain(input, rv) != 0) {
SSLOG(FATAL, this) << "evbuffer_drain() faild";
return -1;
}
}
evbuffer_drain(input, rv);
return send();
}
int Http2Session::on_write()

View File

@ -594,17 +594,30 @@ int Http2Upstream::on_read()
ssize_t rv = 0;
auto bev = handler_->get_bev();
auto input = bufferevent_get_input(bev);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if(rv < 0) {
ULOG(ERROR, this) << "nghttp2_session_recv() returned error: "
<< nghttp2_strerror(rv);
return -1;
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return send();
}
auto mem = evbuffer_pullup(input, inputlen);
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if(rv < 0) {
ULOG(ERROR, this) << "nghttp2_session_recv() returned error: "
<< nghttp2_strerror(rv);
return -1;
}
if(evbuffer_drain(input, rv) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
}
evbuffer_drain(input, rv);
return send();
}
int Http2Upstream::on_write()

View File

@ -502,40 +502,64 @@ http_parser_settings htp_hooks = {
int HttpDownstreamConnection::on_read()
{
auto input = bufferevent_get_input(bev_);
size_t inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
if(downstream_->get_upgraded()) {
// For upgraded connection, just pass data to the upstream.
int rv;
rv = downstream_->get_upstream()->on_downstream_body
(downstream_, reinterpret_cast<const uint8_t*>(mem), inputlen, true);
if(rv != 0) {
return rv;
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return 0;
}
auto mem = evbuffer_pullup(input, inputlen);
int rv;
rv = downstream_->get_upstream()->on_downstream_body
(downstream_, reinterpret_cast<const uint8_t*>(mem), inputlen, true);
if(rv != 0) {
return rv;
}
if(evbuffer_drain(input, inputlen) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
}
if(evbuffer_drain(input, inputlen) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
return 0;
}
size_t nread = http_parser_execute(&response_htp_, &htp_hooks,
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return 0;
}
auto mem = evbuffer_pullup(input, inputlen);
auto nread = http_parser_execute(&response_htp_, &htp_hooks,
reinterpret_cast<const char*>(mem),
inputlen);
if(evbuffer_drain(input, nread) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
if(htperr == HPE_OK) {
return 0;
} else {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "HTTP parser failure: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr);
if(evbuffer_drain(input, nread) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
if(htperr != HPE_OK) {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "HTTP parser failure: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr);
}
return SHRPX_ERR_HTTP_PARSE;
}
return SHRPX_ERR_HTTP_PARSE;
}
}

View File

@ -264,97 +264,139 @@ int HttpsUpstream::on_read()
{
auto bev = handler_->get_bev();
auto input = bufferevent_get_input(bev);
size_t inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
if(inputlen == 0) {
return 0;
}
auto downstream = get_downstream();
// downstream can be nullptr here, because it is initialized in the
// callback chain called by http_parser_execute()
if(downstream && downstream->get_upgraded()) {
int rv = downstream->push_upload_data_chunk
(reinterpret_cast<const uint8_t*>(mem), inputlen);
if(rv != 0) {
return -1;
}
if(evbuffer_drain(input, inputlen) != 0) {
ULOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
if(downstream->get_output_buffer_full()) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Downstream output buffer is full";
}
pause_read(SHRPX_NO_BUFFER);
}
return 0;
}
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
size_t nread = http_parser_execute(&htp_, &htp_hooks,
reinterpret_cast<const char*>(mem),
inputlen);
if(evbuffer_drain(input, nread) != 0) {
ULOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
// Well, actually header length + some body bytes
current_header_length_ += nread;
// Get downstream again because it may be initialized in http parser
// execution
downstream = get_downstream();
auto handler = get_client_handler();
auto htperr = HTTP_PARSER_ERRNO(&htp_);
if(htperr == HPE_PAUSED) {
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
handler->set_should_close_after_write(true);
// Following paues_read is needed to avoid reading next data.
pause_read(SHRPX_MSG_BLOCK);
if(error_reply(503) != 0) {
if(inputlen == 0) {
return 0;
}
auto mem = evbuffer_pullup(input, inputlen);
auto rv = downstream->push_upload_data_chunk
(reinterpret_cast<const uint8_t*>(mem), inputlen);
if(rv != 0) {
return -1;
}
// Downstream gets deleted after response body is read.
} else {
assert(downstream->get_request_state() == Downstream::MSG_COMPLETE);
if(downstream->get_downstream_connection() == 0) {
// Error response has already be sent
assert(downstream->get_response_state() == Downstream::MSG_COMPLETE);
delete_downstream();
} else {
if(handler->get_http2_upgrade_allowed() &&
downstream->http2_upgrade_request()) {
if(handler->perform_http2_upgrade(this) != 0) {
return -1;
}
return 0;
}
pause_read(SHRPX_MSG_BLOCK);
if(evbuffer_drain(input, inputlen) != 0) {
ULOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
}
} else if(htperr == HPE_OK) {
// downstream can be NULL here.
if(downstream) {
if(downstream->get_output_buffer_full()) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Downstream output buffer is full";
}
pause_read(SHRPX_NO_BUFFER);
return 0;
}
}
} else {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "HTTP parse failure: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr);
}
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
return 0;
}
handler->set_should_close_after_write(true);
pause_read(SHRPX_MSG_BLOCK);
if(error_reply(400) != 0) {
auto mem = evbuffer_pullup(input, inputlen);
auto nread = http_parser_execute(&htp_, &htp_hooks,
reinterpret_cast<const char*>(mem),
inputlen);
if(evbuffer_drain(input, nread) != 0) {
ULOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
// Well, actually header length + some body bytes
current_header_length_ += nread;
// Get downstream again because it may be initialized in http parser
// execution
downstream = get_downstream();
auto handler = get_client_handler();
auto htperr = HTTP_PARSER_ERRNO(&htp_);
if(htperr == HPE_PAUSED) {
assert(downstream);
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
handler->set_should_close_after_write(true);
// Following paues_read is needed to avoid reading next data.
pause_read(SHRPX_MSG_BLOCK);
if(error_reply(503) != 0) {
return -1;
}
// Downstream gets deleted after response body is read.
return 0;
}
assert(downstream->get_request_state() == Downstream::MSG_COMPLETE);
if(downstream->get_downstream_connection() == nullptr) {
// Error response has already be sent
assert(downstream->get_response_state() == Downstream::MSG_COMPLETE);
delete_downstream();
return 0;
}
if(handler->get_http2_upgrade_allowed() &&
downstream->http2_upgrade_request()) {
if(handler->perform_http2_upgrade(this) != 0) {
return -1;
}
return 0;
}
pause_read(SHRPX_MSG_BLOCK);
return 0;
}
if(htperr != HPE_OK) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "HTTP parse failure: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr);
}
handler->set_should_close_after_write(true);
pause_read(SHRPX_MSG_BLOCK);
if(error_reply(400) != 0) {
return -1;
}
return 0;
}
// downstream can be NULL here.
if(downstream && downstream->get_output_buffer_full()) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Downstream output buffer is full";
}
pause_read(SHRPX_NO_BUFFER);
return 0;
}
}
return 0;
}
int HttpsUpstream::on_write()