diff options
Diffstat (limited to 'modules/http2/h2_session.c')
-rw-r--r-- | modules/http2/h2_session.c | 1816 |
1 files changed, 742 insertions, 1074 deletions
diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index ed96cf0..5724fda 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -17,6 +17,7 @@ #include <assert.h> #include <stddef.h> #include <apr_thread_cond.h> +#include <apr_atomic.h> #include <apr_base64.h> #include <apr_strings.h> @@ -26,33 +27,35 @@ #include <http_core.h> #include <http_config.h> #include <http_log.h> +#include <http_protocol.h> #include <scoreboard.h> #include <mpm_common.h> +#if APR_HAVE_UNISTD_H +#include <unistd.h> /* for getpid() */ +#endif + #include "h2_private.h" #include "h2.h" #include "h2_bucket_beam.h" #include "h2_bucket_eos.h" #include "h2_config.h" -#include "h2_ctx.h" -#include "h2_filter.h" -#include "h2_h2.h" +#include "h2_conn_ctx.h" +#include "h2_protocol.h" #include "h2_mplx.h" #include "h2_push.h" #include "h2_request.h" #include "h2_headers.h" #include "h2_stream.h" -#include "h2_task.h" +#include "h2_c2.h" #include "h2_session.h" #include "h2_util.h" #include "h2_version.h" #include "h2_workers.h" -static apr_status_t dispatch_master(h2_session *session); -static apr_status_t h2_session_read(h2_session *session, int block); -static void transit(h2_session *session, const char *action, +static void transit(h2_session *session, const char *action, h2_session_state nstate); static void on_stream_state_enter(void *ctx, h2_stream *stream); @@ -73,23 +76,20 @@ static int h2_session_status_from_apr_status(apr_status_t rv) return NGHTTP2_ERR_PROTO; } -h2_stream *h2_session_stream_get(h2_session *session, int stream_id) +static h2_stream *get_stream(h2_session *session, int stream_id) { return nghttp2_session_get_stream_user_data(session->ngh2, stream_id); } -static void dispatch_event(h2_session *session, h2_session_event_t ev, - int err, const char *msg); - -void h2_session_event(h2_session *session, h2_session_event_t ev, +void h2_session_event(h2_session *session, h2_session_event_t ev, int err, const char *msg) { - dispatch_event(session, ev, err, msg); + h2_session_dispatch_event(session, ev, err, msg); } static int rst_unprocessed_stream(h2_stream *stream, void *ctx) { - int unprocessed = (!h2_stream_was_closed(stream) + int unprocessed = (!h2_stream_is_at_or_past(stream, H2_SS_CLOSED) && (H2_STREAM_CLIENT_INITIATED(stream->id)? (!stream->session->local.accepting && stream->id > stream->session->local.accepted_max) @@ -106,7 +106,7 @@ static int rst_unprocessed_stream(h2_stream *stream, void *ctx) static void cleanup_unprocessed_streams(h2_session *session) { - h2_mplx_stream_do(session->mplx, rst_unprocessed_stream, session); + h2_mplx_c1_streams_do(session->mplx, rst_unprocessed_stream, session); } static h2_stream *h2_session_open_stream(h2_session *session, int stream_id, @@ -127,7 +127,7 @@ static h2_stream *h2_session_open_stream(h2_session *session, int stream_id, } /** - * Determine the importance of streams when scheduling tasks. + * Determine the priority order of streams. * - if both stream depend on the same one, compare weights * - if one stream is closer to the root, prioritize that one * - if both are on the same level, use the weight of their root @@ -187,20 +187,26 @@ static ssize_t send_cb(nghttp2_session *ngh2, int flags, void *userp) { h2_session *session = (h2_session *)userp; - apr_status_t status; + apr_status_t rv; (void)ngh2; (void)flags; - - status = h2_conn_io_write(&session->io, (const char *)data, length); - if (status == APR_SUCCESS) { + + if (h2_c1_io_needs_flush(&session->io)) { + return NGHTTP2_ERR_WOULDBLOCK; + } + + rv = h2_c1_io_add_data(&session->io, (const char *)data, length); + if (APR_SUCCESS == rv) { return length; } - if (APR_STATUS_IS_EAGAIN(status)) { + else if (APR_STATUS_IS_EAGAIN(rv)) { return NGHTTP2_ERR_WOULDBLOCK; } - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03062) - "h2_session: send error"); - return h2_session_status_from_apr_status(status); + else { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, session->c1, + APLOGNO(03062) "h2_session: send error"); + return h2_session_status_from_apr_status(rv); + } } static int on_invalid_frame_recv_cb(nghttp2_session *ngh2, @@ -210,11 +216,11 @@ static int on_invalid_frame_recv_cb(nghttp2_session *ngh2, h2_session *session = (h2_session *)userp; (void)ngh2; - if (APLOGcdebug(session->c)) { + if (APLOGcdebug(session->c1)) { char buffer[256]; h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, H2_SSSN_LOG(APLOGNO(03063), session, "recv invalid FRAME[%s], frames=%ld/%ld (r/s)"), buffer, (long)session->frames_received, @@ -232,15 +238,17 @@ static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags, h2_stream * stream; int rv = 0; - stream = h2_session_stream_get(session, stream_id); + stream = get_stream(session, stream_id); if (stream) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, + H2_SSSN_STRM_MSG(session, stream_id, "write %ld bytes of DATA"), + (long)len); status = h2_stream_recv_DATA(stream, flags, data, len); - dispatch_event(session, H2_SESSION_EV_STREAM_CHANGE, 0, "stream data rcvd"); } else { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03064) - "h2_stream(%ld-%d): on_data_chunk for unknown stream", - session->id, (int)stream_id); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03064) + H2_SSSN_STRM_MSG(session, stream_id, + "on_data_chunk for unknown stream")); rv = NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -258,13 +266,13 @@ static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id, h2_stream *stream; (void)ngh2; - stream = h2_session_stream_get(session, stream_id); + stream = get_stream(session, stream_id); if (stream) { if (error_code) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, H2_STRM_LOG(APLOGNO(03065), stream, "closing with err=%d %s"), - (int)error_code, h2_h2_err_description(error_code)); + (int)error_code, h2_protocol_err_description(error_code)); h2_stream_rst(stream, error_code); } } @@ -275,16 +283,16 @@ static int on_begin_headers_cb(nghttp2_session *ngh2, const nghttp2_frame *frame, void *userp) { h2_session *session = (h2_session *)userp; - h2_stream *s; + h2_stream *s = NULL; /* We may see HEADERs at the start of a stream or after all DATA * streams to carry trailers. */ (void)ngh2; - s = h2_session_stream_get(session, frame->hd.stream_id); + s = get_stream(session, frame->hd.stream_id); if (s) { /* nop */ } - else { + else if (session->local.accepting) { s = h2_session_open_stream(userp, frame->hd.stream_id, 0); } return s? 0 : NGHTTP2_ERR_START_STREAM_NOT_ALLOWED; @@ -301,17 +309,23 @@ static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame, apr_status_t status; (void)flags; - stream = h2_session_stream_get(session, frame->hd.stream_id); + stream = get_stream(session, frame->hd.stream_id); if (!stream) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(02920) - "h2_stream(%ld-%d): on_header unknown stream", - session->id, (int)frame->hd.stream_id); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(02920) + H2_SSSN_STRM_MSG(session, frame->hd.stream_id, + "on_header unknown stream")); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } status = h2_stream_add_header(stream, (const char *)name, namelen, (const char *)value, valuelen); - if (status != APR_SUCCESS && !h2_stream_is_ready(stream)) { + if (status != APR_SUCCESS && + (!stream->rtmp || + stream->rtmp->http_status == H2_HTTP_STATUS_UNSET || + /* We accept a certain amount of failures in order to reply + * with an informative HTTP error response like 413. But if the + * client is too wrong, we fail the request a RESET of the stream */ + stream->request_headers_failed > 100)) { return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } return 0; @@ -330,15 +344,25 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, h2_stream *stream; apr_status_t rv = APR_SUCCESS; - if (APLOGcdebug(session->c)) { + stream = frame->hd.stream_id? get_stream(session, frame->hd.stream_id) : NULL; + if (APLOGcdebug(session->c1)) { char buffer[256]; - + h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - H2_SSSN_LOG(APLOGNO(03066), session, - "recv FRAME[%s], frames=%ld/%ld (r/s)"), - buffer, (long)session->frames_received, - (long)session->frames_sent); + if (stream) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, + H2_STRM_LOG(APLOGNO(10302), stream, + "recv FRAME[%s], frames=%ld/%ld (r/s)"), + buffer, (long)session->frames_received, + (long)session->frames_sent); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, + H2_SSSN_LOG(APLOGNO(03066), session, + "recv FRAME[%s], frames=%ld/%ld (r/s)"), + buffer, (long)session->frames_received, + (long)session->frames_sent); + } } ++session->frames_received; @@ -347,16 +371,14 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, /* This can be HEADERS for a new stream, defining the request, * or HEADER may come after DATA at the end of a stream as in * trailers */ - stream = h2_session_stream_get(session, frame->hd.stream_id); if (stream) { rv = h2_stream_recv_frame(stream, NGHTTP2_HEADERS, frame->hd.flags, frame->hd.length + H2_FRAME_HDR_LEN); } break; case NGHTTP2_DATA: - stream = h2_session_stream_get(session, frame->hd.stream_id); if (stream) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, H2_STRM_LOG(APLOGNO(02923), stream, "DATA, len=%ld, flags=%d"), (long)frame->hd.length, frame->hd.flags); @@ -366,35 +388,40 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, break; case NGHTTP2_PRIORITY: session->reprioritize = 1; - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, - "h2_stream(%ld-%d): PRIORITY frame " - " weight=%d, dependsOn=%d, exclusive=%d", - session->id, (int)frame->hd.stream_id, + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, + H2_SSSN_STRM_MSG(session, frame->hd.stream_id, "PRIORITY frame " + " weight=%d, dependsOn=%d, exclusive=%d"), frame->priority.pri_spec.weight, frame->priority.pri_spec.stream_id, frame->priority.pri_spec.exclusive); break; case NGHTTP2_WINDOW_UPDATE: - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, - "h2_stream(%ld-%d): WINDOW_UPDATE incr=%d", - session->id, (int)frame->hd.stream_id, + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, + H2_SSSN_STRM_MSG(session, frame->hd.stream_id, + "WINDOW_UPDATE incr=%d"), frame->window_update.window_size_increment); - if (nghttp2_session_want_write(session->ngh2)) { - dispatch_event(session, H2_SESSION_EV_FRAME_RCVD, 0, "window update"); - } break; case NGHTTP2_RST_STREAM: - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03067) - "h2_stream(%ld-%d): RST_STREAM by client, errror=%d", - session->id, (int)frame->hd.stream_id, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03067) + H2_SSSN_STRM_MSG(session, frame->hd.stream_id, + "RST_STREAM by client, error=%d"), (int)frame->rst_stream.error_code); - stream = h2_session_stream_get(session, frame->hd.stream_id); + if (stream) { + rv = h2_stream_recv_frame(stream, NGHTTP2_RST_STREAM, frame->hd.flags, + frame->hd.length + H2_FRAME_HDR_LEN); + } if (stream && stream->initiated_on) { + /* A stream reset on a request we sent it. Normal, when the + * client does not want it. */ ++session->pushes_reset; } else { - ++session->streams_reset; + /* 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, + stream); } + ++session->streams_reset; break; case NGHTTP2_GOAWAY: if (frame->goaway.error_code == 0 @@ -404,23 +431,21 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, } else { session->remote.accepted_max = frame->goaway.last_stream_id; - dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY, + h2_session_dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY, frame->goaway.error_code, NULL); } break; case NGHTTP2_SETTINGS: - if (APLOGctrace2(session->c)) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, - H2_SSSN_MSG(session, "SETTINGS, len=%ld"), (long)frame->hd.length); - } + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, + H2_SSSN_MSG(session, "SETTINGS, len=%ld"), (long)frame->hd.length); break; default: - if (APLOGctrace2(session->c)) { + if (APLOGctrace2(session->c1)) { char buffer[256]; h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, H2_SSSN_MSG(session, "on_frame_rcv %s"), buffer); } break; @@ -436,7 +461,7 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, * become in serving this connection. This is expressed in increasing "idle_delays". * Eventually, the connection will timeout and we'll close it. */ session->idle_frames = H2MIN(session->idle_frames + 1, session->frames_received); - ap_log_cerror( APLOG_MARK, APLOG_TRACE2, 0, session->c, + ap_log_cerror( APLOG_MARK, APLOG_TRACE2, 0, session->c1, H2_SSSN_MSG(session, "session has %ld idle frames"), (long)session->idle_frames); if (session->idle_frames > 10) { @@ -461,16 +486,6 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, return 0; } -static int h2_session_continue_data(h2_session *session) { - if (h2_mplx_has_master_events(session->mplx)) { - return 0; - } - if (h2_conn_io_needs_flush(&session->io)) { - return 0; - } - return 1; -} - static char immortal_zeros[H2_MAX_PADLEN]; static int on_send_data_cb(nghttp2_session *ngh2, @@ -491,48 +506,42 @@ static int on_send_data_cb(nghttp2_session *ngh2, (void)ngh2; (void)source; - if (!h2_session_continue_data(session)) { - return NGHTTP2_ERR_WOULDBLOCK; - } - - if (frame->data.padlen > H2_MAX_PADLEN) { - return NGHTTP2_ERR_PROTO; - } + ap_assert(frame->data.padlen <= (H2_MAX_PADLEN+1)); padlen = (unsigned char)frame->data.padlen; - stream = h2_session_stream_get(session, stream_id); + stream = get_stream(session, stream_id); if (!stream) { - ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c, + ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c1, APLOGNO(02924) - "h2_stream(%ld-%d): send_data, stream not found", - session->id, (int)stream_id); + H2_SSSN_STRM_MSG(session, stream_id, "send_data, stream not found")); return NGHTTP2_ERR_CALLBACK_FAILURE; } - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, H2_STRM_MSG(stream, "send_data_cb for %ld bytes"), (long)length); - status = h2_conn_io_write(&session->io, (const char *)framehd, 9); + status = h2_c1_io_add_data(&session->io, (const char *)framehd, H2_FRAME_HDR_LEN); if (padlen && status == APR_SUCCESS) { - status = h2_conn_io_write(&session->io, (const char *)&padlen, 1); + --padlen; + status = h2_c1_io_add_data(&session->io, (const char *)&padlen, 1); } if (status != APR_SUCCESS) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c, + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1, H2_STRM_MSG(stream, "writing frame header")); return NGHTTP2_ERR_CALLBACK_FAILURE; } status = h2_stream_read_to(stream, session->bbtmp, &len, &eos); if (status != APR_SUCCESS) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c, + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1, H2_STRM_MSG(stream, "send_data_cb, reading stream")); apr_brigade_cleanup(session->bbtmp); return NGHTTP2_ERR_CALLBACK_FAILURE; } - else if (len != length) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c, + else if (len != (apr_off_t)length) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1, H2_STRM_MSG(stream, "send_data_cb, wanted %ld bytes, " "got %ld from stream"), (long)length, (long)len); apr_brigade_cleanup(session->bbtmp); @@ -541,20 +550,23 @@ static int on_send_data_cb(nghttp2_session *ngh2, if (padlen) { b = apr_bucket_immortal_create(immortal_zeros, padlen, - session->c->bucket_alloc); + session->c1->bucket_alloc); APR_BRIGADE_INSERT_TAIL(session->bbtmp, b); } - status = h2_conn_io_pass(&session->io, session->bbtmp); + status = h2_c1_io_append(&session->io, session->bbtmp); apr_brigade_cleanup(session->bbtmp); if (status == APR_SUCCESS) { stream->out_data_frames++; stream->out_data_octets += length; + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, + H2_STRM_MSG(stream, "sent data length=%ld, total=%ld"), + (long)length, (long)stream->out_data_octets); return 0; } else { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1, H2_STRM_LOG(APLOGNO(02925), stream, "failed send_data_cb")); return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -578,18 +590,27 @@ static int on_frame_send_cb(nghttp2_session *ngh2, break; } - if (APLOGcdebug(session->c)) { + stream = get_stream(session, stream_id); + if (APLOGcdebug(session->c1)) { char buffer[256]; h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - H2_SSSN_LOG(APLOGNO(03068), session, - "sent FRAME[%s], frames=%ld/%ld (r/s)"), - buffer, (long)session->frames_received, - (long)session->frames_sent); + if (stream) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, + H2_STRM_LOG(APLOGNO(10303), stream, + "sent FRAME[%s], frames=%ld/%ld (r/s)"), + buffer, (long)session->frames_received, + (long)session->frames_sent); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, + H2_SSSN_LOG(APLOGNO(03068), session, + "sent FRAME[%s], frames=%ld/%ld (r/s)"), + buffer, (long)session->frames_received, + (long)session->frames_sent); + } } - stream = h2_session_stream_get(session, stream_id); if (stream) { h2_stream_send_frame(stream, frame->hd.type, frame->hd.flags, frame->hd.length + H2_FRAME_HDR_LEN); @@ -598,7 +619,7 @@ static int on_frame_send_cb(nghttp2_session *ngh2, } #ifdef H2_NG2_INVALID_HEADER_CB -static int on_invalid_header_cb(nghttp2_session *ngh2, +static int on_invalid_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, @@ -607,14 +628,11 @@ static int on_invalid_header_cb(nghttp2_session *ngh2, h2_session *session = user_data; h2_stream *stream; - if (APLOGcdebug(session->c)) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03456) - "h2_stream(%ld-%d): invalid header '%s: %s'", - session->id, (int)frame->hd.stream_id, - apr_pstrndup(session->pool, (const char *)name, namelen), - apr_pstrndup(session->pool, (const char *)value, valuelen)); - } - stream = h2_session_stream_get(session, frame->hd.stream_id); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03456) + H2_SSSN_STRM_MSG(session, frame->hd.stream_id, + "invalid header '%.*s: %.*s'"), + (int)namelen, name, (int)valuelen, value); + stream = get_stream(session, frame->hd.stream_id); if (stream) { h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR); } @@ -622,6 +640,37 @@ static int on_invalid_header_cb(nghttp2_session *ngh2, } #endif +static ssize_t select_padding_cb(nghttp2_session *ngh2, + const nghttp2_frame *frame, + size_t max_payloadlen, void *user_data) +{ + h2_session *session = user_data; + size_t frame_len = frame->hd.length + H2_FRAME_HDR_LEN; /* the total length without padding */ + size_t padded_len = frame_len; + + /* Determine # of padding bytes to append to frame. Unless session->padding_always + * the number my be capped by the ui.write_size that currently applies. + */ + if (session->padding_max) { + int n = ap_random_pick(0, session->padding_max); + padded_len = H2MIN(max_payloadlen + H2_FRAME_HDR_LEN, frame_len + n); + } + + if (padded_len != frame_len) { + if (!session->padding_always && session->io.write_size + && (padded_len > session->io.write_size) + && (frame_len <= session->io.write_size)) { + padded_len = session->io.write_size; + } + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, + "select padding from [%d, %d]: %d (frame length: 0x%04x, write size: %d)", + (int)frame_len, (int)max_payloadlen+H2_FRAME_HDR_LEN, + (int)(padded_len - frame_len), (int)padded_len, (int)session->io.write_size); + return padded_len - H2_FRAME_HDR_LEN; + } + return frame->hd.length; +} + #define NGH2_SET_CALLBACK(callbacks, name, fn)\ nghttp2_session_callbacks_set_##name##_callback(callbacks, fn) @@ -647,9 +696,37 @@ static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb) #ifdef H2_NG2_INVALID_HEADER_CB NGH2_SET_CALLBACK(*pcb, on_invalid_header, on_invalid_header_cb); #endif + NGH2_SET_CALLBACK(*pcb, select_padding, select_padding_cb); 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; @@ -663,9 +740,9 @@ static apr_status_t h2_session_shutdown_notice(h2_session *session) session->local.accepting = 0; status = nghttp2_session_send(session->ngh2); if (status == APR_SUCCESS) { - status = h2_conn_io_flush(&session->io); + status = h2_c1_io_assure_flushed(&session->io); } - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, H2_SSSN_LOG(APLOGNO(03457), session, "sent shutdown notice")); return status; } @@ -679,10 +756,13 @@ static apr_status_t h2_session_shutdown(h2_session *session, int error, 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. @@ -691,8 +771,9 @@ static apr_status_t h2_session_shutdown(h2_session *session, int error, * Remove all streams greater than this number without submitting * a RST_STREAM frame, since that should be clear from the GOAWAY * we send. */ - session->local.accepted_max = h2_mplx_shutdown(session->mplx); + 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 @@ -702,25 +783,25 @@ static apr_status_t h2_session_shutdown(h2_session *session, int error, session->local.accepting = 0; session->local.shutdown = 1; - if (!session->c->aborted) { + if (!session->c1->aborted) { nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, session->local.accepted_max, error, (uint8_t*)msg, msg? strlen(msg):0); status = nghttp2_session_send(session->ngh2); if (status == APR_SUCCESS) { - status = h2_conn_io_flush(&session->io); + status = h2_c1_io_assure_flushed(&session->io); } - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, H2_SSSN_LOG(APLOGNO(03069), session, "sent GOAWAY, err=%d, msg=%s"), error, msg? msg : ""); } - dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg); + h2_session_dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg); return status; } static apr_status_t session_cleanup(h2_session *session, const char *trigger) { - conn_rec *c = session->c; + conn_rec *c = session->c1; ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, H2_SSSN_MSG(session, "pool_cleanup")); @@ -734,40 +815,54 @@ static apr_status_t session_cleanup(h2_session *session, const char *trigger) * connection when sending the next request, this has the effect * that at least this one request will fail. */ - ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, H2_SSSN_LOG(APLOGNO(03199), session, "connection disappeared without proper " "goodbye, clients will be confused, should not happen")); } + if (!h2_iq_empty(session->ready_to_process)) { + int sid; + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, + H2_SSSN_LOG(APLOGNO(10485), session, + "cleanup, resetting %d streams in ready-to-process"), + h2_iq_count(session->ready_to_process)); + while ((sid = h2_iq_shift(session->ready_to_process)) > 0) { + h2_mplx_c1_client_rst(session->mplx, sid, get_stream(session, sid)); + } + } + transit(session, trigger, H2_SESSION_ST_CLEANUP); - h2_mplx_release_and_join(session->mplx, session->iowait); + h2_mplx_c1_destroy(session->mplx); session->mplx = NULL; ap_assert(session->ngh2); nghttp2_session_del(session->ngh2); session->ngh2 = NULL; - h2_ctx_clear(c); - - + h2_conn_ctx_detach(c); + return APR_SUCCESS; } static apr_status_t session_pool_cleanup(void *data) { conn_rec *c = data; - h2_session *session; - h2_ctx *ctx = h2_ctx_get(c, 0); - - if (ctx && (session = h2_ctx_session_get(ctx))) { + h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c); + h2_session *session = conn_ctx? conn_ctx->session : NULL; + + if (session) { + int mpm_state = 0; + int level; + + ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state); + level = (AP_MPMQ_STOPPING == mpm_state)? APLOG_DEBUG : APLOG_WARNING; /* if the session is still there, now is the last chance * to perform cleanup. Normally, cleanup should have happened - * earlier in the connection pre_close. Main reason is that - * any ongoing requests on slave connections might still access - * data which has, at this time, already been freed. An example - * is mod_ssl that uses request hooks. */ - ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, - H2_SSSN_LOG(APLOGNO(10020), session, + * earlier in the connection pre_close. + * However, when the server is stopping, it may shutdown connections + * without running the pre_close hooks. Do not want about that. */ + ap_log_cerror(APLOG_MARK, level, 0, c, + H2_SSSN_LOG(APLOGNO(10020), session, "session cleanup triggered by pool cleanup. " "this should have happened earlier already.")); return session_cleanup(session, "pool cleanup"); @@ -775,101 +870,83 @@ static apr_status_t session_pool_cleanup(void *data) return APR_SUCCESS; } -static apr_status_t h2_session_create_int(h2_session **psession, - conn_rec *c, - request_rec *r, - h2_ctx *ctx, - h2_workers *workers) +static /* atomic */ apr_uint32_t next_id; + +apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *r, + server_rec *s, h2_workers *workers) { nghttp2_session_callbacks *callbacks = NULL; nghttp2_option *options = NULL; - apr_allocator_t *allocator; - apr_thread_mutex_t *mutex; uint32_t n; + int thread_num; apr_pool_t *pool = NULL; h2_session *session; + h2_stream *stream0; apr_status_t status; int rv; *psession = NULL; - status = apr_allocator_create(&allocator); - if (status != APR_SUCCESS) { - return status; - } - apr_allocator_max_free_set(allocator, ap_max_mem_free); - apr_pool_create_ex(&pool, c->pool, NULL, allocator); - if (!pool) { - apr_allocator_destroy(allocator); - return APR_ENOMEM; - } + apr_pool_create(&pool, c->pool); apr_pool_tag(pool, "h2_session"); - apr_allocator_owner_set(allocator, pool); - status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pool); - if (status != APR_SUCCESS) { - apr_pool_destroy(pool); - return APR_ENOMEM; - } - apr_allocator_mutex_set(allocator, mutex); - session = apr_pcalloc(pool, sizeof(h2_session)); if (!session) { return APR_ENOMEM; } *psession = session; - session->id = c->id; - session->c = c; + /* c->id does not give a unique id for the lifetime of the session. + * mpms like event change c->id when re-activating a keepalive + * connection based on the child_num+thread_num of the worker + * processing it. + * We'd like to have an id that remains constant and unique bc + * h2 streams can live through keepalive periods. While double id + * will not lead to processing failures, it will confuse log analysis. + */ +#if AP_MODULE_MAGIC_AT_LEAST(20211221, 8) + ap_sb_get_child_thread(c->sbh, &session->child_num, &thread_num); +#else + (void)thread_num; + session->child_num = (int)getpid(); +#endif + session->id = apr_atomic_inc32(&next_id); + session->c1 = c; session->r = r; - session->s = h2_ctx_server_get(ctx); + session->s = s; session->pool = pool; - session->config = h2_config_sget(session->s); session->workers = workers; session->state = H2_SESSION_ST_INIT; session->local.accepting = 1; session->remote.accepting = 1; - session->max_stream_count = h2_config_geti(session->config, - H2_CONF_MAX_STREAMS); - session->max_stream_mem = h2_config_geti(session->config, - H2_CONF_STREAM_MAX_MEM); - - status = apr_thread_cond_create(&session->iowait, session->pool); - if (status != APR_SUCCESS) { - apr_pool_destroy(pool); - return status; - } - - session->in_pending = h2_iq_create(session->pool, (int)session->max_stream_count); - if (session->in_pending == NULL) { - apr_pool_destroy(pool); - return APR_ENOMEM; - } + session->max_stream_count = h2_config_sgeti(s, H2_CONF_MAX_STREAMS); + session->max_stream_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM); + session->max_data_frame_len = h2_config_sgeti(s, H2_CONF_MAX_DATA_FRAME_LEN); + + session->out_c1_blocked = h2_iq_create(session->pool, (int)session->max_stream_count); + session->ready_to_process = h2_iq_create(session->pool, (int)session->max_stream_count); - session->in_process = h2_iq_create(session->pool, (int)session->max_stream_count); - if (session->in_process == NULL) { - apr_pool_destroy(pool); - return APR_ENOMEM; - } - session->monitor = apr_pcalloc(pool, sizeof(h2_stream_monitor)); - if (session->monitor == NULL) { - apr_pool_destroy(pool); - return APR_ENOMEM; - } session->monitor->ctx = session; session->monitor->on_state_enter = on_stream_state_enter; session->monitor->on_state_event = on_stream_state_event; session->monitor->on_event = on_stream_event; - - session->mplx = h2_mplx_create(c, session->pool, session->config, - workers); - - /* connection input filter that feeds the session */ - session->cin = h2_filter_cin_create(session); - ap_add_input_filter("H2_IN", session->cin, r, c); - - h2_conn_io_init(&session->io, c, session->config); + + stream0 = h2_stream_create(0, session->pool, session, NULL, 0); + stream0->c2 = session->c1; /* stream0's connection is the main connection */ + session->mplx = h2_mplx_c1_create(session->child_num, session->id, + stream0, s, session->pool, workers); + if (!session->mplx) { + apr_pool_destroy(pool); + return APR_ENOTIMPL; + } + + 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; + } + session->padding_always = h2_config_sgeti(s, H2_CONF_PADDING_ALWAYS); session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc); status = init_callbacks(c, &callbacks); @@ -888,12 +965,23 @@ static apr_status_t h2_session_create_int(h2_session **psession, apr_pool_destroy(pool); return status; } - nghttp2_option_set_peer_max_concurrent_streams( - options, (uint32_t)session->max_stream_count); + nghttp2_option_set_peer_max_concurrent_streams(options, (uint32_t)session->max_stream_count); /* We need to handle window updates ourself, otherwise we * get flooded by nghttp2. */ nghttp2_option_set_no_auto_window_update(options, 1); - +#ifdef H2_NG2_NO_CLOSED_STREAMS + /* We do not want nghttp2 to keep information about closed streams as + * that accumulates memory on long connections. This makes PRIORITY + * setting in relation to older streams non-working. */ + nghttp2_option_set_no_closed_streams(options, 1); +#endif +#ifdef H2_NG2_RFC9113_STRICTNESS + /* nghttp2 v1.50.0 introduces the strictness checks on leading/trailing + * whitespace of RFC 9113 for fields. But, by default, it RST streams + * carrying such. We do not want that. We want to strip the ws and + * handle them, just like the HTTP/1.1 parser does. */ + nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(options, 1); +#endif rv = nghttp2_session_server_new2(&session->ngh2, callbacks, session, options); nghttp2_session_callbacks_del(callbacks); @@ -907,7 +995,7 @@ static apr_status_t h2_session_create_int(h2_session **psession, return APR_ENOMEM; } - n = h2_config_geti(session->config, H2_CONF_PUSH_DIARY_SIZE); + n = h2_config_sgeti(s, H2_CONF_PUSH_DIARY_SIZE); session->push_diary = h2_push_diary_create(session->pool, n); if (APLOGcdebug(c)) { @@ -915,35 +1003,26 @@ static apr_status_t h2_session_create_int(h2_session **psession, H2_SSSN_LOG(APLOGNO(03200), session, "created, max_streams=%d, stream_mem=%d, " "workers_limit=%d, workers_max=%d, " - "push_diary(type=%d,N=%d)"), + "push_diary(type=%d,N=%d), " + "max_data_frame_len=%d"), (int)session->max_stream_count, (int)session->max_stream_mem, - session->mplx->limit_active, - session->mplx->max_active, + session->mplx->processing_limit, + session->mplx->processing_max, session->push_diary->dtype, - (int)session->push_diary->N); + (int)session->push_diary->N, + (int)session->max_data_frame_len); } - apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup); + apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup); + return APR_SUCCESS; } -apr_status_t h2_session_create(h2_session **psession, - conn_rec *c, h2_ctx *ctx, h2_workers *workers) -{ - return h2_session_create_int(psession, c, NULL, ctx, workers); -} - -apr_status_t h2_session_rcreate(h2_session **psession, - request_rec *r, h2_ctx *ctx, h2_workers *workers) -{ - return h2_session_create_int(psession, r->connection, r, ctx, workers); -} - static apr_status_t h2_session_start(h2_session *session, int *rv) { apr_status_t status = APR_SUCCESS; - nghttp2_settings_entry settings[3]; + nghttp2_settings_entry settings[4]; size_t slen; int win_size; @@ -1004,14 +1083,21 @@ static apr_status_t h2_session_start(h2_session *session, int *rv) settings[slen].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; settings[slen].value = (uint32_t)session->max_stream_count; ++slen; - win_size = h2_config_geti(session->config, H2_CONF_WIN_SIZE); + win_size = h2_config_sgeti(session->s, H2_CONF_WIN_SIZE); if (win_size != H2_INITIAL_WINDOW_SIZE) { settings[slen].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; settings[slen].value = win_size; ++slen; } - - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, +#if H2_USE_WEBSOCKETS + if (h2_config_sgeti(session->s, H2_CONF_WEBSOCKETS)) { + settings[slen].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL; + settings[slen].value = 1; + ++slen; + } +#endif + + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1, H2_SSSN_LOG(APLOGNO(03201), session, "start, INITIAL_WINDOW_SIZE=%ld, MAX_CONCURRENT_STREAMS=%d"), (long)win_size, (int)session->max_stream_count); @@ -1019,7 +1105,7 @@ static apr_status_t h2_session_start(h2_session *session, int *rv) settings, slen); if (*rv != 0) { status = APR_EGENERAL; - ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, + ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c1, H2_SSSN_LOG(APLOGNO(02935), session, "nghttp2_submit_settings: %s"), nghttp2_strerror(*rv)); } @@ -1037,7 +1123,7 @@ static apr_status_t h2_session_start(h2_session *session, int *rv) 0, NGHTTP2_MAX_WINDOW_SIZE - win_size); if (*rv != 0) { status = APR_EGENERAL; - ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, + ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c1, H2_SSSN_LOG(APLOGNO(02970), session, "nghttp2_submit_window_update: %s"), nghttp2_strerror(*rv)); @@ -1047,87 +1133,6 @@ static apr_status_t h2_session_start(h2_session *session, int *rv) return status; } -static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, - h2_headers *headers, apr_off_t len, - int eos); - -static ssize_t stream_data_cb(nghttp2_session *ng2s, - int32_t stream_id, - uint8_t *buf, - size_t length, - uint32_t *data_flags, - nghttp2_data_source *source, - void *puser) -{ - h2_session *session = (h2_session *)puser; - apr_off_t nread = length; - int eos = 0; - apr_status_t status; - h2_stream *stream; - ap_assert(session); - - /* The session wants to send more DATA for the stream. We need - * to find out how much of the requested length we can send without - * blocking. - * Indicate EOS when we encounter it or DEFERRED if the stream - * should be suspended. Beware of trailers. - */ - - (void)ng2s; - (void)buf; - (void)source; - stream = h2_session_stream_get(session, stream_id); - if (!stream) { - ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, - APLOGNO(02937) - "h2_stream(%ld-%d): data_cb, stream not found", - session->id, (int)stream_id); - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - - status = h2_stream_out_prepare(stream, &nread, &eos, NULL); - if (nread) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, - H2_STRM_MSG(stream, "prepared no_copy, len=%ld, eos=%d"), - (long)nread, eos); - *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; - } - - switch (status) { - case APR_SUCCESS: - break; - - case APR_EOF: - eos = 1; - break; - - case APR_ECONNRESET: - case APR_ECONNABORTED: - return NGHTTP2_ERR_CALLBACK_FAILURE; - - case APR_EAGAIN: - /* If there is no data available, our session will automatically - * suspend this stream and not ask for more data until we resume - * it. Remember at our h2_stream that we need to do this. - */ - nread = 0; - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - H2_STRM_LOG(APLOGNO(03071), stream, "suspending")); - return NGHTTP2_ERR_DEFERRED; - - default: - nread = 0; - ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, - H2_STRM_LOG(APLOGNO(02938), stream, "reading data")); - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - - if (eos) { - *data_flags |= NGHTTP2_DATA_FLAG_EOF; - } - return (ssize_t)nread; -} - struct h2_stream *h2_session_push(h2_session *session, h2_stream *is, h2_push *push) { @@ -1142,21 +1147,21 @@ struct h2_stream *h2_session_push(h2_session *session, h2_stream *is, ngh->nv, ngh->nvlen, NULL); } if (status != APR_SUCCESS || nid <= 0) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1, H2_STRM_LOG(APLOGNO(03075), is, "submitting push promise fail: %s"), nghttp2_strerror(nid)); return NULL; } ++session->pushes_promised; - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, H2_STRM_LOG(APLOGNO(03076), is, "SERVER_PUSH %d for %s %s on %d"), nid, push->req->method, push->req->path, is->id); stream = h2_session_open_stream(session, nid, is->id); if (!stream) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - H2_STRM_LOG(APLOGNO(03077), stream, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, + H2_STRM_LOG(APLOGNO(03077), is, "failed to create stream obj %d"), nid); /* kill the push_promise */ nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, nid, @@ -1166,7 +1171,6 @@ struct h2_stream *h2_session_push(h2_session *session, h2_stream *is, h2_session_set_prio(session, stream, push->priority); h2_stream_set_request(stream, push->req); - ++session->unsent_promises; return stream; } @@ -1181,7 +1185,6 @@ apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream, const h2_priority *prio) { apr_status_t status = APR_SUCCESS; -#ifdef H2_NG2_CHANGE_PRIO nghttp2_stream *s_grandpa, *s_parent, *s; if (prio == NULL) { @@ -1190,7 +1193,7 @@ apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream, } s = nghttp2_session_find_stream(session->ngh2, stream->id); if (!s) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1, H2_STRM_MSG(stream, "lookup of nghttp2_stream failed")); return APR_EINVAL; } @@ -1239,10 +1242,10 @@ apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream, id_grandpa = nghttp2_stream_get_stream_id(s_grandpa); rv = nghttp2_session_change_stream_priority(session->ngh2, id_parent, &ps); if (rv < 0) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03202) - "h2_stream(%ld-%d): PUSH BEFORE, weight=%d, " - "depends=%d, returned=%d", - session->id, id_parent, ps.weight, ps.stream_id, rv); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03202) + H2_SSSN_STRM_MSG(session, id_parent, + "PUSH BEFORE, weight=%d, depends=%d, returned=%d"), + ps.weight, ps.stream_id, rv); return APR_EGENERAL; } nghttp2_priority_spec_init(&ps, id_grandpa, w, 0); @@ -1261,18 +1264,13 @@ apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream, rv = nghttp2_session_change_stream_priority(session->ngh2, stream->id, &ps); - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - ""H2_STRM_LOG(APLOGNO(03203), stream, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, + H2_STRM_LOG(APLOGNO(03203), stream, "PUSH %s, weight=%d, depends=%d, returned=%d"), ptype, ps.weight, ps.stream_id, rv); status = (rv < 0)? APR_EGENERAL : APR_SUCCESS; } -#else - (void)session; - (void)stream; - (void)prio; - (void)valid_weight; -#endif + return status; } @@ -1280,338 +1278,89 @@ int h2_session_push_enabled(h2_session *session) { /* iff we can and they can and want */ return (session->remote.accepting /* remote GOAWAY received */ - && h2_config_geti(session->config, H2_CONF_PUSH) + && h2_config_sgeti(session->s, H2_CONF_PUSH) && nghttp2_session_get_remote_settings(session->ngh2, NGHTTP2_SETTINGS_ENABLE_PUSH)); } -static apr_status_t h2_session_send(h2_session *session) +static int h2_session_want_send(h2_session *session) { - apr_interval_time_t saved_timeout; - int rv; - apr_socket_t *socket; - - socket = ap_get_conn_socket(session->c); - if (socket) { - apr_socket_timeout_get(socket, &saved_timeout); - apr_socket_timeout_set(socket, session->s->timeout); - } - - rv = nghttp2_session_send(session->ngh2); - - if (socket) { - apr_socket_timeout_set(socket, saved_timeout); - } - session->have_written = 1; - if (rv != 0 && rv != NGHTTP2_ERR_WOULDBLOCK) { - if (nghttp2_is_fatal(rv)) { - dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv)); - return APR_EGENERAL; - } - } - - session->unsent_promises = 0; - session->unsent_submits = 0; - - return APR_SUCCESS; + return nghttp2_session_want_write(session->ngh2) + || h2_c1_io_pending(&session->io); } -/** - * headers for the stream are ready. - */ -static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, - h2_headers *headers, apr_off_t len, - int eos) +static apr_status_t h2_session_send(h2_session *session) { - apr_status_t status = APR_SUCCESS; - int rv = 0; + int ngrv, pending = 0; + apr_status_t rv = APR_SUCCESS; - ap_assert(session); - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, - H2_STRM_MSG(stream, "on_headers")); - if (headers->status < 100) { - h2_stream_rst(stream, headers->status); - goto leave; - } - else if (stream->has_response) { - h2_ngheader *nh; - - status = h2_res_create_ngtrailer(&nh, stream->pool, headers); - - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, - H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"), - (int)nh->nvlen); - if (status == APR_SUCCESS) { - rv = nghttp2_submit_trailer(session->ngh2, stream->id, - nh->nv, nh->nvlen); - } - else { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, - H2_STRM_LOG(APLOGNO(10024), stream, "invalid trailers")); - h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR); - } - goto leave; - } - else { - nghttp2_data_provider provider, *pprovider = NULL; - h2_ngheader *ngh; - const char *note; - - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - H2_STRM_LOG(APLOGNO(03073), stream, "submit response %d, REMOTE_WINDOW_SIZE=%u"), - headers->status, - (unsigned int)nghttp2_session_get_stream_remote_window_size(session->ngh2, stream->id)); - - if (!eos || len > 0) { - memset(&provider, 0, sizeof(provider)); - provider.source.fd = stream->id; - provider.read_callback = stream_data_cb; - pprovider = &provider; - } - - /* If this stream is not a pushed one itself, - * and HTTP/2 server push is enabled here, - * and the response HTTP status is not sth >= 400, - * and the remote side has pushing enabled, - * -> find and perform any pushes on this stream - * *before* we submit the stream response itself. - * This helps clients avoid opening new streams on Link - * headers that get pushed right afterwards. - * - * *) the response code is relevant, as we do not want to - * make pushes on 401 or 403 codes and friends. - * And if we see a 304, we do not push either - * as the client, having this resource in its cache, might - * also have the pushed ones as well. - */ - if (!stream->initiated_on - && !stream->has_response - && stream->request && stream->request->method - && !strcmp("GET", stream->request->method) - && (headers->status < 400) - && (headers->status != 304) - && h2_session_push_enabled(session)) { - - h2_stream_submit_pushes(stream, headers); - } - - if (!stream->pref_priority) { - stream->pref_priority = h2_stream_get_priority(stream, headers); - } - h2_session_set_prio(session, stream, stream->pref_priority); - - note = apr_table_get(headers->notes, H2_FILTER_DEBUG_NOTE); - if (note && !strcmp("on", note)) { - int32_t connFlowIn, connFlowOut; - - connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2); - connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2); - headers = h2_headers_copy(stream->pool, headers); - apr_table_setn(headers->headers, "conn-flow-in", - apr_itoa(stream->pool, connFlowIn)); - apr_table_setn(headers->headers, "conn-flow-out", - apr_itoa(stream->pool, connFlowOut)); - } - - if (headers->status == 103 - && !h2_config_geti(session->config, H2_CONF_EARLY_HINTS)) { - /* suppress sending this to the client, it might have triggered - * pushes and served its purpose nevertheless */ - rv = 0; - goto leave; - } - - status = h2_res_create_ngheader(&ngh, stream->pool, headers); - if (status == APR_SUCCESS) { - rv = nghttp2_submit_response(session->ngh2, stream->id, - ngh->nv, ngh->nvlen, pprovider); - stream->has_response = h2_headers_are_response(headers); - session->have_written = 1; - - if (stream->initiated_on) { - ++session->pushes_submitted; - } - else { - ++session->responses_submitted; + while (nghttp2_session_want_write(session->ngh2)) { + ngrv = nghttp2_session_send(session->ngh2); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, + "nghttp2_session_send: %d", (int)ngrv); + pending = 1; + if (ngrv != 0 && ngrv != NGHTTP2_ERR_WOULDBLOCK) { + if (nghttp2_is_fatal(ngrv)) { + h2_session_dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, + ngrv, nghttp2_strerror(ngrv)); + rv = APR_EGENERAL; + goto cleanup; } } - else { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, - H2_STRM_LOG(APLOGNO(10025), stream, "invalid response")); - h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR); + if (h2_c1_io_needs_flush(&session->io) || + ngrv == NGHTTP2_ERR_WOULDBLOCK) { + rv = h2_c1_io_assure_flushed(&session->io); + if (rv != APR_SUCCESS) + goto cleanup; + pending = 0; } } - -leave: - if (nghttp2_is_fatal(rv)) { - status = APR_EGENERAL; - dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv)); - ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, - APLOGNO(02940) "submit_response: %s", - nghttp2_strerror(rv)); + if (pending) { + rv = h2_c1_io_pass(&session->io); } - - ++session->unsent_submits; - - /* Unsent push promises are written immediately, as nghttp2 - * 1.5.0 realizes internal stream data structures only on - * send and we might need them for other submits. - * Also, to conserve memory, we send at least every 10 submits - * so that nghttp2 does not buffer all outbound items too - * long. - */ - if (status == APR_SUCCESS - && (session->unsent_promises || session->unsent_submits > 10)) { - status = h2_session_send(session); +cleanup: + if (rv != APR_SUCCESS) { + h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, rv, NULL); } - return status; + return rv; } /** - * A stream was resumed as new response/output data arrived. + * A streams input state has changed. */ -static apr_status_t on_stream_resume(void *ctx, h2_stream *stream) +static void on_stream_input(void *ctx, h2_stream *stream) { h2_session *session = ctx; - apr_status_t status = APR_EAGAIN; - int rv; - apr_off_t len = 0; - int eos = 0; - h2_headers *headers; - - ap_assert(stream); - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, - H2_STRM_MSG(stream, "on_resume")); - -send_headers: - headers = NULL; - status = h2_stream_out_prepare(stream, &len, &eos, &headers); - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, - H2_STRM_MSG(stream, "prepared len=%ld, eos=%d"), - (long)len, eos); - if (headers) { - status = on_stream_headers(session, stream, headers, len, eos); - if (status != APR_SUCCESS || stream->rst_error) { - return status; - } - goto send_headers; - } - else if (status != APR_EAGAIN) { - /* we have DATA to send */ - if (!stream->has_response) { - /* but no response */ - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - H2_STRM_LOG(APLOGNO(03466), stream, - "no response, RST_STREAM")); - h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR); - return APR_SUCCESS; - } - rv = nghttp2_session_resume_data(session->ngh2, stream->id); - session->have_written = 1; - ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)? - APLOG_ERR : APLOG_DEBUG, 0, session->c, - H2_STRM_LOG(APLOGNO(02936), stream, "resumed")); - } - return status; -} -static void h2_session_in_flush(h2_session *session) -{ - int id; - - while ((id = h2_iq_shift(session->in_process)) > 0) { - h2_stream *stream = h2_session_stream_get(session, id); - if (stream) { - ap_assert(!stream->scheduled); - if (h2_stream_prep_processing(stream) == APR_SUCCESS) { - h2_mplx_process(session->mplx, stream, stream_pri_cmp, session); - } - else { - h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR); - } - } + ap_assert(stream); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, + H2_STRM_MSG(stream, "on_input change")); + update_child_status(session, SERVER_BUSY_READ, "read", stream); + if (stream->id == 0) { + /* input on primary connection available? read */ + h2_c1_read(session); } - - while ((id = h2_iq_shift(session->in_pending)) > 0) { - h2_stream *stream = h2_session_stream_get(session, id); - if (stream) { - h2_stream_flush_input(stream); - } + else { + h2_stream_on_input_change(stream); } } -static apr_status_t session_read(h2_session *session, apr_size_t readlen, int block) +/** + * A streams output state has changed. + */ +static void on_stream_output(void *ctx, h2_stream *stream) { - apr_status_t status, rstatus = APR_EAGAIN; - conn_rec *c = session->c; - apr_off_t read_start = session->io.bytes_read; - - while (1) { - /* H2_IN filter handles all incoming data against the session. - * We just pull at the filter chain to make it happen */ - status = ap_get_brigade(c->input_filters, - session->bbtmp, AP_MODE_READBYTES, - block? APR_BLOCK_READ : APR_NONBLOCK_READ, - H2MAX(APR_BUCKET_BUFF_SIZE, readlen)); - /* get rid of any possible data we do not expect to get */ - apr_brigade_cleanup(session->bbtmp); - - switch (status) { - case APR_SUCCESS: - /* successful read, reset our idle timers */ - rstatus = APR_SUCCESS; - if (block) { - /* successful blocked read, try unblocked to - * get more. */ - block = 0; - } - break; - case APR_EAGAIN: - return rstatus; - case APR_TIMEUP: - return status; - default: - if (session->io.bytes_read == read_start) { - /* first attempt failed */ - if (APR_STATUS_IS_ETIMEDOUT(status) - || APR_STATUS_IS_ECONNABORTED(status) - || APR_STATUS_IS_ECONNRESET(status) - || APR_STATUS_IS_EOF(status) - || APR_STATUS_IS_EBADF(status)) { - /* common status for a client that has left */ - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, - H2_SSSN_MSG(session, "input gone")); - } - else { - /* uncommon status, log on INFO so that we see this */ - ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, c, - H2_SSSN_LOG(APLOGNO(02950), session, - "error reading, terminating")); - } - return status; - } - /* subsequent failure after success(es), return initial - * status. */ - return rstatus; - } - if ((session->io.bytes_read - read_start) > readlen) { - /* read enough in one go, give write a chance */ - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c, - H2_SSSN_MSG(session, "read enough, returning")); - break; - } + h2_session *session = ctx; + + ap_assert(stream); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, + H2_STRM_MSG(stream, "on_output change")); + if (stream->id != 0) { + update_child_status(session, SERVER_BUSY_WRITE, "write", stream); + h2_stream_on_output_change(stream); } - return rstatus; } -static apr_status_t h2_session_read(h2_session *session, int block) -{ - apr_status_t status = session_read(session, session->max_stream_mem - * H2MAX(2, session->open_streams), - block); - h2_session_in_flush(session); - return status; -} static const char *StateNames[] = { "INIT", /* H2_SESSION_ST_INIT */ @@ -1630,40 +1379,14 @@ const char *h2_session_state_str(h2_session_state state) 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->c->sbh, status, session->status); - } -} - static void transit(h2_session *session, const char *action, h2_session_state nstate) { - apr_time_t timeout; - int ostate, loglvl; - const char *s; - + int ostate; + if (session->state != nstate) { ostate = session->state; - session->state = nstate; - - loglvl = APLOG_DEBUG; - if ((ostate == H2_SESSION_ST_BUSY && nstate == H2_SESSION_ST_WAIT) - || (ostate == H2_SESSION_ST_WAIT && nstate == H2_SESSION_ST_BUSY)){ - loglvl = APLOG_TRACE1; - } - ap_log_cerror(APLOG_MARK, loglvl, 0, session->c, + + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, H2_SSSN_LOG(APLOGNO(03078), session, "transit [%s] -- %s --> [%s]"), h2_session_state_str(ostate), action, @@ -1678,35 +1401,21 @@ static void transit(h2_session *session, const char *action, h2_session_state ns * 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. */ - session->idle_sync_until = apr_time_now() + apr_time_from_sec(1); - s = "timeout"; - timeout = H2MAX(session->s->timeout, session->s->keep_alive_timeout); - update_child_status(session, SERVER_BUSY_READ, "idle"); - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, - H2_SSSN_LOG("", session, "enter idle, timeout = %d sec"), - (int)apr_time_sec(H2MAX(session->s->timeout, session->s->keep_alive_timeout))); - } - else if (session->open_streams) { - s = "timeout"; - timeout = session->s->keep_alive_timeout; - update_child_status(session, SERVER_BUSY_KEEPALIVE, "idle"); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1, + H2_SSSN_LOG("", session, "enter idle")); } else { /* normal keepalive setup */ - s = "keepalive"; - timeout = session->s->keep_alive_timeout; - update_child_status(session, SERVER_BUSY_KEEPALIVE, "idle"); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1, + H2_SSSN_LOG("", session, "enter keepalive")); } - session->idle_until = apr_time_now() + timeout; - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, - H2_SSSN_LOG("", session, "enter idle, %s = %d sec"), - s, (int)apr_time_sec(timeout)); + session->state = nstate; break; case H2_SESSION_ST_DONE: - update_child_status(session, SERVER_CLOSING, "done"); break; default: /* nop */ + session->state = nstate; break; } } @@ -1724,12 +1433,45 @@ static void h2_session_ev_init(h2_session *session, int arg, const char *msg) } } +static void h2_session_ev_input_pending(h2_session *session, int arg, const char *msg) +{ + switch (session->state) { + case H2_SESSION_ST_INIT: + case H2_SESSION_ST_IDLE: + case H2_SESSION_ST_WAIT: + transit(session, "input read", H2_SESSION_ST_BUSY); + break; + default: + break; + } +} + +static void h2_session_ev_input_exhausted(h2_session *session, int arg, const char *msg) +{ + switch (session->state) { + case H2_SESSION_ST_BUSY: + if (!h2_session_want_send(session)) { + if (session->open_streams == 0) { + transit(session, "input exhausted, no streams", H2_SESSION_ST_IDLE); + } + else { + transit(session, "input exhausted", H2_SESSION_ST_WAIT); + } + } + break; + case H2_SESSION_ST_WAIT: + if (session->open_streams == 0) { + transit(session, "input exhausted, no streams", H2_SESSION_ST_IDLE); + } + break; + default: + 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); } @@ -1740,7 +1482,6 @@ static void h2_session_ev_remote_goaway(h2_session *session, int arg, const char 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); } } @@ -1755,7 +1496,7 @@ static void h2_session_ev_conn_error(h2_session *session, int arg, const char *m break; default: - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, H2_SSSN_LOG(APLOGNO(03401), session, "conn error -> shutdown")); h2_session_shutdown(session, arg, msg, 0); @@ -1766,7 +1507,7 @@ static void h2_session_ev_conn_error(h2_session *session, int arg, const char *m static void h2_session_ev_proto_error(h2_session *session, int arg, const char *msg) { if (!session->local.shutdown) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, H2_SSSN_LOG(APLOGNO(03402), session, "proto error -> shutdown")); h2_session_shutdown(session, arg, msg, 0); @@ -1781,115 +1522,91 @@ static void h2_session_ev_conn_timeout(h2_session *session, int arg, const char } } -static void h2_session_ev_no_io(h2_session *session, int arg, const char *msg) +static void h2_session_ev_ngh2_done(h2_session *session, int arg, const char *msg) { switch (session->state) { - case H2_SESSION_ST_BUSY: - /* Nothing to READ, nothing to WRITE on the master connection. - * Possible causes: - * - we wait for the client to send us sth - * - we wait for started tasks to produce output - * - we have finished all streams and the client has sent GO_AWAY - */ - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, - H2_SSSN_MSG(session, "NO_IO event, %d streams open"), - session->open_streams); - h2_conn_io_flush(&session->io); - if (session->open_streams > 0) { - if (h2_mplx_awaits_data(session->mplx)) { - /* waiting for at least one stream to produce data */ - transit(session, "no io", H2_SESSION_ST_WAIT); - } - else { - /* we have streams open, and all are submitted and none - * is suspended. The only thing keeping us from WRITEing - * more must be the flow control. - * This means we only wait for WINDOW_UPDATE from the - * client and can block on READ. */ - transit(session, "no io (flow wait)", H2_SESSION_ST_IDLE); - /* Make sure we have flushed all previously written output - * so that the client will react. */ - if (h2_conn_io_flush(&session->io) != APR_SUCCESS) { - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL); - return; - } - } - } - else if (session->local.accepting) { - /* When we have no streams, but accept new, switch to idle */ - transit(session, "no io (keepalive)", H2_SESSION_ST_IDLE); - } - else { - /* We are no longer accepting new streams and there are - * none left. Time to leave. */ - h2_session_shutdown(session, arg, msg, 0); - transit(session, "no io", H2_SESSION_ST_DONE); - } - break; - default: + case H2_SESSION_ST_DONE: /* nop */ break; - } -} - -static void h2_session_ev_frame_rcvd(h2_session *session, int arg, const char *msg) -{ - switch (session->state) { - case H2_SESSION_ST_IDLE: - case H2_SESSION_ST_WAIT: - transit(session, "frame received", H2_SESSION_ST_BUSY); - break; default: - /* nop */ + transit(session, "nghttp2 done", H2_SESSION_ST_DONE); break; } } -static void h2_session_ev_stream_change(h2_session *session, int arg, const char *msg) +static void h2_session_ev_mpm_stopping(h2_session *session, int arg, const char *msg) { switch (session->state) { - case H2_SESSION_ST_IDLE: - case H2_SESSION_ST_WAIT: - transit(session, "stream change", H2_SESSION_ST_BUSY); + case H2_SESSION_ST_DONE: + /* nop */ break; default: - /* nop */ + h2_session_shutdown_notice(session); +#if !AP_MODULE_MAGIC_AT_LEAST(20120211, 110) + h2_workers_graceful_shutdown(session->workers); +#endif break; } } -static void h2_session_ev_ngh2_done(h2_session *session, int arg, const char *msg) +static void h2_session_ev_pre_close(h2_session *session, int arg, const char *msg) { - switch (session->state) { - case H2_SESSION_ST_DONE: - /* nop */ - break; - default: - transit(session, "nghttp2 done", H2_SESSION_ST_DONE); - break; - } + h2_session_shutdown(session, arg, msg, 1); } -static void h2_session_ev_mpm_stopping(h2_session *session, int arg, const char *msg) +static void h2_session_ev_no_more_streams(h2_session *session) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, + H2_SSSN_LOG(APLOGNO(10304), session, "no more streams")); switch (session->state) { - case H2_SESSION_ST_DONE: - /* nop */ + case H2_SESSION_ST_BUSY: + case H2_SESSION_ST_WAIT: + if (!h2_session_want_send(session)) { + if (session->local.accepting) { + /* We wait for new frames on c1 only. */ + transit(session, "all streams done", H2_SESSION_ST_IDLE); + } + else { + /* We are no longer accepting new streams. + * Time to leave. */ + h2_session_shutdown(session, 0, "done", 0); + transit(session, "c1 done after goaway", H2_SESSION_ST_DONE); + } + } + else { + transit(session, "no more streams", H2_SESSION_ST_WAIT); + } break; default: - h2_session_shutdown_notice(session); + /* nop */ break; } } -static void h2_session_ev_pre_close(h2_session *session, int arg, const char *msg) +static void ev_stream_created(h2_session *session, h2_stream *stream) { - h2_session_shutdown(session, arg, msg, 1); + /* nop */ } static void ev_stream_open(h2_session *session, h2_stream *stream) { - h2_iq_append(session->in_process, stream->id); + if (H2_STREAM_CLIENT_INITIATED(stream->id)) { + ++session->remote.emitted_count; + if (stream->id > session->remote.emitted_max) { + session->remote.emitted_max = stream->id; + session->local.accepted_max = stream->id; + } + } + else { + if (stream->id > session->local.emitted_max) { + ++session->local.emitted_count; + session->remote.emitted_max = stream->id; + } + } + /* 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) @@ -1900,75 +1617,72 @@ static void ev_stream_closed(h2_session *session, h2_stream *stream) && (stream->id > session->local.completed_max)) { session->local.completed_max = stream->id; } - switch (session->state) { - case H2_SESSION_ST_IDLE: - break; - default: - break; - } - /* The stream might have data in the buffers of the main connection. * We can only free the allocated resources once all had been written. * Send a special buckets on the connection that gets destroyed when * all preceding data has been handled. On its destruction, it is safe * to purge all resources of the stream. */ - b = h2_bucket_eos_create(session->c->bucket_alloc, stream); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, + H2_STRM_MSG(stream, "adding h2_eos to c1 out")); + b = h2_bucket_eos_create(session->c1->bucket_alloc, stream); APR_BRIGADE_INSERT_TAIL(session->bbtmp, b); - h2_conn_io_pass(&session->io, session->bbtmp); + h2_c1_io_append(&session->io, session->bbtmp); apr_brigade_cleanup(session->bbtmp); } static void on_stream_state_enter(void *ctx, h2_stream *stream) { h2_session *session = ctx; - /* stream entered a new state */ - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1, H2_STRM_MSG(stream, "entered state")); switch (stream->state) { case H2_SS_IDLE: /* stream was created */ - ++session->open_streams; - if (H2_STREAM_CLIENT_INITIATED(stream->id)) { - ++session->remote.emitted_count; - if (stream->id > session->remote.emitted_max) { - session->remote.emitted_max = stream->id; - session->local.accepted_max = stream->id; - } - } - else { - if (stream->id > session->local.emitted_max) { - ++session->local.emitted_count; - session->remote.emitted_max = stream->id; - } - } + ev_stream_created(session, stream); break; case H2_SS_OPEN: /* stream has request headers */ - case H2_SS_RSVD_L: /* stream has request headers */ + case H2_SS_RSVD_L: ev_stream_open(session, stream); break; - case H2_SS_CLOSED_L: /* stream output was closed */ + case H2_SS_CLOSED_L: /* stream output was closed, but remote end is not */ + /* If the stream is still being processed, it could still be reading + * its input (theoretically, http request hangling does not normally). + * But when processing is done, we need to cancel the stream as no + * one is consuming the input any longer. + * This happens, for example, on a large POST when the response + * is ready early due to the POST being denied. */ + if (!h2_mplx_c1_stream_is_running(session->mplx, stream)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, + H2_STRM_LOG(APLOGNO(10305), stream, "remote close missing")); + nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, + stream->id, H2_ERR_NO_ERROR); + } break; case H2_SS_CLOSED_R: /* stream input was closed */ break; case H2_SS_CLOSED: /* stream in+out were closed */ - --session->open_streams; ev_stream_closed(session, stream); break; case H2_SS_CLEANUP: - h2_mplx_stream_cleanup(session->mplx, stream); + 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); break; default: break; } - dispatch_event(session, H2_SESSION_EV_STREAM_CHANGE, 0, "stream state change"); } -static void on_stream_event(void *ctx, h2_stream *stream, - h2_stream_event_t ev) +static void on_stream_event(void *ctx, h2_stream *stream, h2_stream_event_t ev) { h2_session *session = ctx; switch (ev) { case H2_SEV_IN_DATA_PENDING: - h2_iq_append(session->in_pending, stream->id); + session->input_flushed = 1; + break; + case H2_SEV_OUT_C1_BLOCK: + h2_iq_append(session->out_c1_blocked, stream->id); break; default: /* NOP */ @@ -1993,13 +1707,19 @@ static void on_stream_state_event(void *ctx, h2_stream *stream, } } -static void dispatch_event(h2_session *session, h2_session_event_t ev, - int arg, const char *msg) +void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev, + apr_status_t arg, const char *msg) { switch (ev) { case H2_SESSION_EV_INIT: h2_session_ev_init(session, arg, msg); break; + case H2_SESSION_EV_INPUT_PENDING: + h2_session_ev_input_pending(session, arg, msg); + break; + case H2_SESSION_EV_INPUT_EXHAUSTED: + h2_session_ev_input_exhausted(session, arg, msg); + break; case H2_SESSION_EV_LOCAL_GOAWAY: h2_session_ev_local_goaway(session, arg, msg); break; @@ -2015,12 +1735,6 @@ static void dispatch_event(h2_session *session, h2_session_event_t ev, case H2_SESSION_EV_CONN_TIMEOUT: h2_session_ev_conn_timeout(session, arg, msg); break; - case H2_SESSION_EV_NO_IO: - h2_session_ev_no_io(session, arg, msg); - break; - case H2_SESSION_EV_FRAME_RCVD: - h2_session_ev_frame_rcvd(session, arg, msg); - break; case H2_SESSION_EV_NGH2_DONE: h2_session_ev_ngh2_done(session, arg, msg); break; @@ -2030,311 +1744,265 @@ static void dispatch_event(h2_session *session, h2_session_event_t ev, case H2_SESSION_EV_PRE_CLOSE: h2_session_ev_pre_close(session, arg, msg); break; - case H2_SESSION_EV_STREAM_CHANGE: - h2_session_ev_stream_change(session, arg, msg); + case H2_SESSION_EV_NO_MORE_STREAMS: + h2_session_ev_no_more_streams(session); break; default: - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1, H2_SSSN_MSG(session, "unknown event %d"), ev); break; } } -/* trigger window updates, stream resumes and submits */ -static apr_status_t dispatch_master(h2_session *session) { - apr_status_t status; - - status = h2_mplx_dispatch_master_events(session->mplx, - on_stream_resume, session); - if (status == APR_EAGAIN) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c, - H2_SSSN_MSG(session, "no master event available")); - } - else if (status != APR_SUCCESS) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c, - H2_SSSN_MSG(session, "dispatch error")); - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, - H2_ERR_INTERNAL_ERROR, "dispatch error"); +static void unblock_c1_out(h2_session *session) { + int sid; + + while ((sid = h2_iq_shift(session->out_c1_blocked)) > 0) { + nghttp2_session_resume_data(session->ngh2, sid); } - return status; } -static const int MAX_WAIT_MICROS = 200 * 1000; - apr_status_t h2_session_process(h2_session *session, int async) { apr_status_t status = APR_SUCCESS; - conn_rec *c = session->c; + conn_rec *c = session->c1; int rv, mpm_state, trace = APLOGctrace3(c); - apr_time_t now; - + if (trace) { ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c, H2_SSSN_MSG(session, "process start, async=%d"), async); } - + + if (H2_SESSION_ST_INIT == session->state) { + if (!h2_protocol_is_acceptable_c1(c, session->r, 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", NULL); + status = h2_session_start(session, &rv); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, + H2_SSSN_LOG(APLOGNO(03079), session, + "started on %s:%d"), + session->s->server_hostname, + c->local_addr->port); + if (status != APR_SUCCESS) { + h2_session_dispatch_event(session, + H2_SESSION_EV_CONN_ERROR, status, NULL); + } + else { + h2_session_dispatch_event(session, H2_SESSION_EV_INIT, 0, NULL); + } + } + } + while (session->state != H2_SESSION_ST_DONE) { - now = apr_time_now(); - session->have_read = session->have_written = 0; - if (session->local.accepting + /* PR65731: we may get a new connection to process while the + * MPM already is stopping. For example due to having reached + * MaxRequestsPerChild limit. + * Since this is supposed to handle things gracefully, we need to: + * a) fully initialize the session before GOAWAYing + * b) give the client the chance to submit at least one request + */ + if (session->state != H2_SESSION_ST_INIT /* no longer intializing */ + && session->local.accepted_max > 0 /* have gotten at least one stream */ + && session->local.accepting /* have not already locally shut down */ && !ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) { if (mpm_state == AP_MPMQ_STOPPING) { - dispatch_event(session, H2_SESSION_EV_MPM_STOPPING, 0, NULL); + h2_session_dispatch_event(session, H2_SESSION_EV_MPM_STOPPING, 0, NULL); } } - + session->status[0] = '\0'; + if (h2_session_want_send(session)) { + h2_session_send(session); + } + else if (!nghttp2_session_want_read(session->ngh2)) { + h2_session_dispatch_event(session, H2_SESSION_EV_NGH2_DONE, 0, NULL); + } + + if (!h2_iq_empty(session->ready_to_process)) { + h2_mplx_c1_process(session->mplx, session->ready_to_process, + get_stream, stream_pri_cmp, session, + &session->open_streams); + transit(session, "scheduled stream", H2_SESSION_ST_BUSY); + } + + if (session->input_flushed) { + transit(session, "forwarded input", H2_SESSION_ST_BUSY); + session->input_flushed = 0; + } + + if (!h2_iq_empty(session->out_c1_blocked)) { + unblock_c1_out(session); + transit(session, "unblocked output", H2_SESSION_ST_BUSY); + } + + if (session->reprioritize) { + h2_mplx_c1_reprioritize(session->mplx, stream_pri_cmp, session); + session->reprioritize = 0; + } + + if (h2_session_want_send(session)) { + h2_session_send(session); + } + + status = h2_c1_io_assure_flushed(&session->io); + if (APR_SUCCESS != status) { + h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL); + } + switch (session->state) { - case H2_SESSION_ST_INIT: - ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c); - if (!h2_is_acceptable_connection(c, 1)) { - update_child_status(session, SERVER_BUSY_READ, - "inadequate security"); - h2_session_shutdown(session, - NGHTTP2_INADEQUATE_SECURITY, NULL, 1); - } - else { - update_child_status(session, SERVER_BUSY_READ, "init"); - status = h2_session_start(session, &rv); - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, - H2_SSSN_LOG(APLOGNO(03079), session, - "started on %s:%d"), - session->s->server_hostname, - c->local_addr->port); - if (status != APR_SUCCESS) { - dispatch_event(session, - H2_SESSION_EV_CONN_ERROR, 0, NULL); - } - dispatch_event(session, H2_SESSION_EV_INIT, 0, NULL); - } - break; - - case H2_SESSION_ST_IDLE: - if (session->idle_until && (apr_time_now() + session->idle_delay) > session->idle_until) { - ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c, - H2_SSSN_MSG(session, "idle, timeout reached, closing")); - if (session->idle_delay) { - apr_table_setn(session->c->notes, "short-lingering-close", "1"); - } - dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, "timeout"); - goto out; - } - - if (session->idle_delay) { - /* we are less interested in spending time on this connection */ - ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, c, - H2_SSSN_MSG(session, "session is idle (%ld ms), idle wait %ld sec left"), - (long)apr_time_as_msec(session->idle_delay), - (long)apr_time_sec(session->idle_until - now)); - apr_sleep(session->idle_delay); - session->idle_delay = 0; - } + case H2_SESSION_ST_INIT: + ap_assert(0); + h2_c1_read(session); + break; - h2_conn_io_flush(&session->io); - if (async && !session->r && (now > session->idle_sync_until)) { - if (trace) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c, - H2_SSSN_MSG(session, - "nonblock read, %d streams open"), - session->open_streams); - } - status = h2_session_read(session, 0); - - if (status == APR_SUCCESS) { - session->have_read = 1; - } - else if (APR_STATUS_IS_EAGAIN(status) || APR_STATUS_IS_TIMEUP(status)) { - status = APR_EAGAIN; - goto out; - } - else { + case H2_SESSION_ST_IDLE: + ap_assert(session->open_streams == 0); + ap_assert(nghttp2_session_want_read(session->ngh2)); + if (!h2_session_want_send(session)) { + /* Give any new incoming request a short grace period to + * arrive while we are still hot and return to the mpm + * connection handling when nothing really happened. */ + h2_c1_read(session); + if (H2_SESSION_ST_IDLE == session->state) { + if (async) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, - H2_SSSN_LOG(APLOGNO(03403), session, - "no data, error")); - dispatch_event(session, - H2_SESSION_EV_CONN_ERROR, 0, "timeout"); - } - } - else { - /* make certain, we send everything before we idle */ - if (trace) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c, - H2_SSSN_MSG(session, - "sync, stutter 1-sec, %d streams open"), - session->open_streams); - } - /* We wait in smaller increments, using a 1 second timeout. - * That gives us the chance to check for MPMQ_STOPPING often. - */ - status = h2_mplx_idle(session->mplx); - if (status == APR_EAGAIN) { - break; - } - else if (status != APR_SUCCESS) { - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, - H2_ERR_ENHANCE_YOUR_CALM, "less is more"); - } - h2_filter_cin_timeout_set(session->cin, apr_time_from_sec(1)); - status = h2_session_read(session, 1); - if (status == APR_SUCCESS) { - session->have_read = 1; - } - else if (status == APR_EAGAIN) { - /* nothing to read */ - } - else if (APR_STATUS_IS_TIMEUP(status)) { - /* continue reading handling */ - } - else if (APR_STATUS_IS_ECONNABORTED(status) - || APR_STATUS_IS_ECONNRESET(status) - || APR_STATUS_IS_EOF(status) - || APR_STATUS_IS_EBADF(status)) { - ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c, - H2_SSSN_MSG(session, "input gone")); - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL); + H2_SSSN_LOG(APLOGNO(10306), session, + "returning to mpm c1 monitoring")); + goto leaving; } else { - ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c, - H2_SSSN_MSG(session, - "(1 sec timeout) read failed")); - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, "error"); - } - } - if (nghttp2_session_want_write(session->ngh2)) { - ap_update_child_status(session->c->sbh, SERVER_BUSY_WRITE, NULL); - status = h2_session_send(session); - if (status == APR_SUCCESS) { - status = h2_conn_io_flush(&session->io); - } - if (status != APR_SUCCESS) { - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, - H2_ERR_INTERNAL_ERROR, "writing"); - break; + /* Not an async mpm, we must continue waiting + * for client data to arrive until the configured + * server Timeout/KeepAliveTimeout happens */ + apr_time_t timeout = (session->open_streams == 0)? + session->s->keep_alive_timeout : + session->s->timeout; + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c, + H2_SSSN_MSG(session, "polling timeout=%d"), + (int)apr_time_sec(timeout)); + status = h2_mplx_c1_poll(session->mplx, timeout, + on_stream_input, + on_stream_output, session); + if (APR_STATUS_IS_TIMEUP(status)) { + if (session->open_streams == 0) { + 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, status, NULL); + break; + } } } + } + else { + transit(session, "c1 io pending", H2_SESSION_ST_BUSY); + } + break; + + case H2_SESSION_ST_BUSY: + /* IO happening in and out. Make sure we react to c2 events + * inbetween send and receive. */ + 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, status, NULL); break; - - case H2_SESSION_ST_BUSY: - if (nghttp2_session_want_read(session->ngh2)) { - ap_update_child_status(session->c->sbh, SERVER_BUSY_READ, NULL); - h2_filter_cin_timeout_set(session->cin, session->s->timeout); - status = h2_session_read(session, 0); - if (status == APR_SUCCESS) { - session->have_read = 1; - } - else if (status == APR_EAGAIN) { - /* nothing to read */ - } - else if (APR_STATUS_IS_TIMEUP(status)) { - dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, NULL); - break; - } - else { - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL); - } - } + } + h2_c1_read(session); + break; - status = dispatch_master(session); - if (status != APR_SUCCESS && status != APR_EAGAIN) { + 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, status, NULL); + break; + } + if (session->open_streams == 0) { + h2_session_dispatch_event(session, H2_SESSION_EV_NO_MORE_STREAMS, + 0, "streams really done"); + if (session->state != H2_SESSION_ST_WAIT) { break; } - - if (nghttp2_session_want_write(session->ngh2)) { - ap_update_child_status(session->c->sbh, SERVER_BUSY_WRITE, NULL); - status = h2_session_send(session); - if (status == APR_SUCCESS) { - status = h2_conn_io_flush(&session->io); - } - if (status != APR_SUCCESS) { - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, - H2_ERR_INTERNAL_ERROR, "writing"); - break; - } - } - - if (session->have_read || session->have_written) { - if (session->wait_us) { - session->wait_us = 0; - } - } - else if (!nghttp2_session_want_write(session->ngh2)) { - dispatch_event(session, H2_SESSION_EV_NO_IO, 0, NULL); + } + /* No IO happening and input is exhausted. Make sure we have + * flushed any possibly pending output and then wait with + * the c1 connection timeout for sth to happen in our c1/c2 sockets/pipes */ + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c, + H2_SSSN_MSG(session, "polling timeout=%d, open_streams=%d"), + (int)apr_time_sec(session->s->timeout), session->open_streams); + status = h2_mplx_c1_poll(session->mplx, session->s->timeout, + on_stream_input, on_stream_output, session); + if (APR_STATUS_IS_TIMEUP(status)) { + /* If we timeout without streams open, no new request from client + * arrived. + * If we timeout without nghttp2 wanting to write something, but + * all open streams have something to send, it means we are + * blocked on HTTP/2 flow control and the client did not send + * WINDOW_UPDATEs to us. */ + if (session->open_streams == 0 || + (!h2_session_want_send(session) && + h2_mplx_c1_all_streams_want_send_data(session->mplx))) { + 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, status, NULL); break; - - case H2_SESSION_ST_WAIT: - if (session->wait_us <= 0) { - session->wait_us = 10; - if (h2_conn_io_flush(&session->io) != APR_SUCCESS) { - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL); - break; - } - } - else { - /* repeating, increase timer for graceful backoff */ - session->wait_us = H2MIN(session->wait_us*2, MAX_WAIT_MICROS); - } + } + break; - if (trace) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c, - "h2_session: wait for data, %ld micros", - (long)session->wait_us); - } - status = h2_mplx_out_trywait(session->mplx, session->wait_us, - session->iowait); - if (status == APR_SUCCESS) { - session->wait_us = 0; - dispatch_event(session, H2_SESSION_EV_STREAM_CHANGE, 0, NULL); - } - else if (APR_STATUS_IS_TIMEUP(status)) { - /* go back to checking all inputs again */ - transit(session, "wait cycle", session->local.shutdown? - H2_SESSION_ST_DONE : H2_SESSION_ST_BUSY); - } - else if (APR_STATUS_IS_ECONNRESET(status) - || APR_STATUS_IS_ECONNABORTED(status)) { - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL); - } - else { - ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, c, - H2_SSSN_LOG(APLOGNO(03404), session, - "waiting on conditional")); - h2_session_shutdown(session, H2_ERR_INTERNAL_ERROR, - "cond wait error", 0); - } - break; - - default: - ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c, - H2_SSSN_LOG(APLOGNO(03080), session, - "unknown state")); - dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, 0, NULL); - break; - } + case H2_SESSION_ST_DONE: + h2_c1_read(session); + break; - if (!nghttp2_session_want_read(session->ngh2) - && !nghttp2_session_want_write(session->ngh2)) { - dispatch_event(session, H2_SESSION_EV_NGH2_DONE, 0, NULL); - } - if (session->reprioritize) { - h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session); - session->reprioritize = 0; + default: + 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, APR_EGENERAL, NULL); + break; } } - -out: + +leaving: if (trace) { ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c, H2_SSSN_MSG(session, "process returns")); } - - if ((session->state != H2_SESSION_ST_DONE) - && (APR_STATUS_IS_EOF(status) + h2_mplx_c1_going_keepalive(session->mplx); + + 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))) { - 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; @@ -2344,14 +2012,14 @@ apr_status_t h2_session_pre_close(h2_session *session, int async) { apr_status_t status; - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1, H2_SSSN_MSG(session, "pre_close")); - dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0, + h2_session_dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0, (session->state == H2_SESSION_ST_IDLE)? "timeout" : NULL); status = session_cleanup(session, "pre_close"); if (status == APR_SUCCESS) { /* no one should hold a reference to this session any longer and - * the h2_ctx was removed from the connection. + * the h2_conn_ctx_twas removed from the connection. * Take the pool (and thus all subpools etc. down now, instead of * during cleanup of main connection pool. */ apr_pool_destroy(session->pool); |