#include "h2_private.h"
#include "h2_bucket_eos.h"
#include "h2_config.h"
+#include "h2_c1.h"
#include "h2_c1_io.h"
#include "h2_protocol.h"
#include "h2_session.h"
}
-apr_status_t h2_c1_io_init(h2_c1_io *io, conn_rec *c, server_rec *s)
+apr_status_t h2_c1_io_init(h2_c1_io *io, h2_session *session)
{
- io->c = c;
- io->output = apr_brigade_create(c->pool, c->bucket_alloc);
- io->is_tls = ap_ssl_conn_is_ssl(c);
+ conn_rec *c = session->c1;
+
+ io->session = session;
+ io->output = apr_brigade_create(c->pool, c->bucket_alloc);
+ io->is_tls = ap_ssl_conn_is_ssl(session->c1);
io->buffer_output = io->is_tls;
- io->flush_threshold = 4 * (apr_size_t)h2_config_sgeti64(s, H2_CONF_STREAM_MAX_MEM);
+ io->flush_threshold = 4 * (apr_size_t)h2_config_sgeti64(session->s, H2_CONF_STREAM_MAX_MEM);
if (io->buffer_output) {
/* This is what we start with,
* see https://issues.apache.org/jira/browse/TS-2503
*/
- io->warmup_size = h2_config_sgeti64(s, H2_CONF_TLS_WARMUP_SIZE);
- io->cooldown_usecs = (h2_config_sgeti(s, H2_CONF_TLS_COOLDOWN_SECS)
+ io->warmup_size = h2_config_sgeti64(session->s, H2_CONF_TLS_WARMUP_SIZE);
+ io->cooldown_usecs = (h2_config_sgeti(session->s, H2_CONF_TLS_COOLDOWN_SECS)
* APR_USEC_PER_SEC);
io->cooldown_usecs = 0;
- io->write_size = (io->cooldown_usecs > 0?
- WRITE_SIZE_INITIAL : WRITE_SIZE_MAX);
+ io->write_size = (io->cooldown_usecs > 0?
+ WRITE_SIZE_INITIAL : WRITE_SIZE_MAX);
}
else {
- io->warmup_size = 0;
+ io->warmup_size = 0;
io->cooldown_usecs = 0;
- io->write_size = 0;
+ io->write_size = 0;
}
if (APLOGctrace1(c)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c,
"h2_c1_io(%ld): init, buffering=%d, warmup_size=%ld, "
- "cd_secs=%f", io->c->id, io->buffer_output,
+ "cd_secs=%f", c->id, io->buffer_output,
(long)io->warmup_size,
((double)io->cooldown_usecs/APR_USEC_PER_SEC));
}
if (io->scratch && io->slen > 0) {
apr_bucket *b = apr_bucket_heap_create(io->scratch, io->slen,
apr_bucket_free,
- io->c->bucket_alloc);
+ io->session->c1->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(io->output, b);
io->buffered_len += io->slen;
io->scratch = NULL;
if (!io->scratch) {
/* we control the size and it is larger than what buckets usually
* allocate. */
- io->scratch = apr_bucket_alloc(io->write_size, io->c->bucket_alloc);
+ io->scratch = apr_bucket_alloc(io->write_size, io->session->c1->bucket_alloc);
io->ssize = io->write_size;
io->slen = 0;
remain = io->ssize;
io->slen += len;
}
else if (APR_BUCKET_IS_MMAP(b)) {
- ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, io->c,
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, io->session->c1,
"h2_c1_io(%ld): seeing mmap bucket of size %ld, scratch remain=%ld",
- io->c->id, (long)b->length, (long)(io->ssize - io->slen));
+ io->session->c1->id, (long)b->length, (long)(io->ssize - io->slen));
status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
if (status == APR_SUCCESS) {
memcpy(io->scratch+io->slen, data, len);
static apr_status_t pass_output(h2_c1_io *io, int flush)
{
- conn_rec *c = io->c;
+ conn_rec *c = io->session->c1;
apr_off_t bblen;
apr_status_t rv;
append_scratch(io);
if (flush) {
if (!APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output))) {
- apr_bucket *b = apr_bucket_flush_create(io->c->bucket_alloc);
+ apr_bucket *b = apr_bucket_flush_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(io->output, b);
}
}
return APR_SUCCESS;
}
- ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, NULL);
io->unflushed = !APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output));
apr_brigade_length(io->output, 0, &bblen);
C1_IO_BB_LOG(c, 0, APLOG_TRACE2, "out", io->output);
io->buffered_len = 0;
io->bytes_written += (apr_size_t)bblen;
+
if (io->write_size < WRITE_SIZE_MAX
&& io->bytes_written >= io->warmup_size) {
/* connection is hot, use max size */
apr_status_t status = APR_SUCCESS;
apr_size_t remain;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->session->c1,
"h2_c1_io(%ld): adding %ld data bytes",
- io->c->id, (long)length);
+ io->session->c1->id, (long)length);
if (io->buffer_output) {
while (length > 0) {
remain = assure_scratch_space(io);
}
else {
/* no buffering, forward buckets setaside on flush */
- apr_bucket_setaside(b, io->c->pool);
+ apr_bucket_setaside(b, io->session->c1->pool);
APR_BUCKET_REMOVE(b);
APR_BRIGADE_INSERT_TAIL(io->output, b);
io->buffered_len += b->length;
/* A stream reset on a request it sent us. Could happen in a browser
* when the user navigates away or cancels loading - maybe. */
h2_mplx_c1_client_rst(session->mplx, frame->hd.stream_id);
- ++session->streams_reset;
}
+ ++session->streams_reset;
break;
case NGHTTP2_GOAWAY:
if (frame->goaway.error_code == 0
return APR_SUCCESS;
}
+static void update_child_status(h2_session *session, int status,
+ const char *msg, const h2_stream *stream)
+{
+ /* Assume that we also change code/msg when something really happened and
+ * avoid updating the scoreboard in between */
+ if (session->last_status_code != status
+ || session->last_status_msg != msg) {
+ char sbuffer[1024];
+ sbuffer[0] = '\0';
+ if (stream) {
+ apr_snprintf(sbuffer, sizeof(sbuffer),
+ ": stream %d, %s %s",
+ stream->id,
+ stream->request? stream->request->method : "",
+ stream->request? stream->request->path : "");
+ }
+ apr_snprintf(session->status, sizeof(session->status),
+ "[%d/%d] %s%s",
+ (int)(session->remote.emitted_count + session->pushes_submitted),
+ (int)session->streams_done,
+ msg? msg : "-", sbuffer);
+ ap_update_child_status_from_server(session->c1->sbh, status,
+ session->c1, session->s);
+ ap_update_child_status_descr(session->c1->sbh, status, session->status);
+ }
+}
+
static apr_status_t h2_session_shutdown_notice(h2_session *session)
{
apr_status_t status;
if (session->local.shutdown) {
return APR_SUCCESS;
}
- if (!msg && error) {
- msg = nghttp2_strerror(error);
+
+ if (error && !msg) {
+ if (APR_STATUS_IS_EPIPE(error)) {
+ msg = "remote close";
+ }
}
-
+
if (error || force_close) {
/* not a graceful shutdown, we want to leave...
* Do not start further streams that are waiting to be scheduled.
* we send. */
session->local.accepted_max = h2_mplx_c1_shutdown(session->mplx);
session->local.error = error;
+ session->local.error_msg = msg;
}
else {
/* graceful shutdown. we will continue processing all streams
return APR_ENOTIMPL;
}
- h2_c1_io_init(&session->io, c, s);
+ h2_c1_io_init(&session->io, session);
session->padding_max = h2_config_sgeti(s, H2_CONF_PADDING_BITS);
if (session->padding_max) {
session->padding_max = (0x01 << session->padding_max) - 1;
int ngrv;
apr_status_t rv = APR_SUCCESS;
- ap_update_child_status(session->c1->sbh, SERVER_BUSY_WRITE, NULL);
while (nghttp2_session_want_write(session->ngh2)) {
ngrv = nghttp2_session_send(session->ngh2);
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
}
cleanup:
if (rv != APR_SUCCESS) {
- h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR,
- H2_ERR_INTERNAL_ERROR, "c1 out writing");
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, rv, NULL);
}
return rv;
}
H2_STRM_LOG(APLOGNO(10026), stream, "remote close missing"));
nghttp2_submit_rst_stream(stream->session->ngh2, NGHTTP2_FLAG_NONE,
stream->id, NGHTTP2_NO_ERROR);
+ update_child_status(session, SERVER_BUSY_WRITE, "reset", stream);
goto cleanup;
}
+ update_child_status(session, SERVER_BUSY_READ, "read", stream);
h2_beam_report_consumption(stream->input);
if (stream->state == H2_SS_CLOSED_R) {
/* TODO: remove this stream from input polling */
/* we dont poll output of stream 0, this should not be called */
return APR_SUCCESS;
}
+ update_child_status(session, SERVER_BUSY_WRITE, "write", stream);
return h2_stream_read_output(stream);
}
return StateNames[state];
}
-static void update_child_status(h2_session *session, int status, const char *msg)
-{
- /* Assume that we also change code/msg when something really happened and
- * avoid updating the scoreboard in between */
- if (session->last_status_code != status
- || session->last_status_msg != msg) {
- apr_snprintf(session->status, sizeof(session->status),
- "%s, streams: %d/%d/%d/%d/%d (open/recv/resp/push/rst)",
- msg? msg : "-",
- (int)session->open_streams,
- (int)session->remote.emitted_count,
- (int)session->responses_submitted,
- (int)session->pushes_submitted,
- (int)session->pushes_reset + session->streams_reset);
- ap_update_child_status_descr(session->c1->sbh, status, session->status);
- }
-}
-
static void transit(h2_session *session, const char *action, h2_session_state nstate)
{
int ostate;
if (session->state != nstate) {
ostate = session->state;
- session->state = nstate;
-
+
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_SSSN_LOG(APLOGNO(03078), session,
"transit [%s] -- %s --> [%s]"),
* If we return to mpm right away, this connection has the
* same chance of being cleaned up by the mpm as connections
* that already served requests - not fair. */
- update_child_status(session, SERVER_BUSY_READ, "idle");
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
H2_SSSN_LOG("", session, "enter idle"));
}
else {
/* normal keepalive setup */
- update_child_status(session, SERVER_BUSY_KEEPALIVE, "keepalive");
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
H2_SSSN_LOG("", session, "enter keepalive"));
}
+ session->state = nstate;
break;
case H2_SESSION_ST_DONE:
- update_child_status(session, SERVER_CLOSING, "done");
break;
default:
/* nop */
+ session->state = nstate;
break;
}
}
static void h2_session_ev_local_goaway(h2_session *session, int arg, const char *msg)
{
cleanup_unprocessed_streams(session);
- if (!session->remote.shutdown) {
- update_child_status(session, SERVER_CLOSING, "local goaway");
- }
transit(session, "local goaway", H2_SESSION_ST_DONE);
}
session->remote.accepting = 0;
session->remote.shutdown = 1;
cleanup_unprocessed_streams(session);
- update_child_status(session, SERVER_CLOSING, "remote goaway");
transit(session, "remote goaway", H2_SESSION_ST_DONE);
}
}
/* Stream state OPEN means we have received all request headers
* and can start processing the stream. */
h2_iq_append(session->ready_to_process, stream->id);
+ update_child_status(session, SERVER_BUSY_READ, "schedule", stream);
}
static void ev_stream_closed(h2_session *session, h2_stream *stream)
case H2_SS_CLEANUP:
nghttp2_session_set_stream_user_data(session->ngh2, stream->id, NULL);
h2_mplx_c1_stream_cleanup(session->mplx, stream, &session->open_streams);
+ ++session->streams_done;
+ update_child_status(session, SERVER_BUSY_WRITE, "done", stream);
if (session->open_streams == 0) {
h2_session_dispatch_event(session, H2_SESSION_EV_NO_MORE_STREAMS,
0, "stream done");
}
void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev,
- int arg, const char *msg)
+ apr_status_t arg, const char *msg)
{
switch (ev) {
case H2_SESSION_EV_INIT:
}
if (H2_SESSION_ST_INIT == session->state) {
- ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
if (!h2_protocol_is_acceptable_c1(c, session->r, 1)) {
- update_child_status(session, SERVER_BUSY_READ,
- "inadequate security");
- h2_session_shutdown(session,
- NGHTTP2_INADEQUATE_SECURITY, NULL, 1);
+ const char *msg = nghttp2_strerror(NGHTTP2_INADEQUATE_SECURITY);
+ update_child_status(session, SERVER_BUSY_READ, msg, NULL);
+ h2_session_shutdown(session, APR_EINVAL, msg, 1);
}
else {
- update_child_status(session, SERVER_BUSY_READ, "init");
+ update_child_status(session, SERVER_BUSY_READ, "init", NULL);
status = h2_session_start(session, &rv);
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
H2_SSSN_LOG(APLOGNO(03079), session,
c->local_addr->port);
if (status != APR_SUCCESS) {
h2_session_dispatch_event(session,
- H2_SESSION_EV_CONN_ERROR, 0, NULL);
+ H2_SESSION_EV_CONN_ERROR, status, NULL);
}
else {
h2_session_dispatch_event(session, H2_SESSION_EV_INIT, 0, NULL);
status = h2_c1_io_assure_flushed(&session->io);
if (APR_SUCCESS != status) {
- h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
}
switch (session->state) {
status = h2_mplx_c1_poll(session->mplx, 0,
on_stream_input, on_stream_output, session);
if (APR_SUCCESS != status && !APR_STATUS_IS_TIMEUP(status)) {
- h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
break;
}
h2_c1_read(session);
case H2_SESSION_ST_WAIT:
status = h2_c1_io_assure_flushed(&session->io);
if (APR_SUCCESS != status) {
- h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
break;
}
/* No IO happening and input is exhausted. Make sure we have
status = h2_mplx_c1_poll(session->mplx, session->s->timeout,
on_stream_input, on_stream_output, session);
if (APR_STATUS_IS_TIMEUP(status)) {
- h2_session_dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, "timeout");
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, status, NULL);
break;
}
else if (APR_SUCCESS != status) {
- h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, "error");
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
break;
}
break;
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
H2_SSSN_LOG(APLOGNO(03080), session,
"unknown state"));
- h2_session_dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, 0, NULL);
+ h2_session_dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, APR_EGENERAL, NULL);
break;
}
}
H2_SSSN_MSG(session, "process returns"));
}
- if ((session->state != H2_SESSION_ST_DONE)
- && (APR_STATUS_IS_EOF(status)
+ if (session->state == H2_SESSION_ST_DONE) {
+ if (session->local.error) {
+ char buffer[128];
+ const char *msg;
+ if (session->local.error_msg) {
+ msg = session->local.error_msg;
+ }
+ else {
+ msg = apr_strerror(session->local.error, buffer, sizeof(buffer));
+ }
+ update_child_status(session, SERVER_CLOSING, msg, NULL);
+ }
+ else {
+ update_child_status(session, SERVER_CLOSING, "done", NULL);
+ }
+ }
+ else if (APR_STATUS_IS_EOF(status)
|| APR_STATUS_IS_ECONNRESET(status)
- || APR_STATUS_IS_ECONNABORTED(status))) {
- h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+ || APR_STATUS_IS_ECONNABORTED(status)) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+ update_child_status(session, SERVER_CLOSING, "error", NULL);
}
return (session->state == H2_SESSION_ST_DONE)? APR_EOF : APR_SUCCESS;