apr_status_t h2_c1_run(conn_rec *c)
{
apr_status_t status;
- int mpm_state = 0;
+ int mpm_state = 0, keepalive = 0;
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
ap_assert(conn_ctx);
c->cs->state = CONN_STATE_HANDLER;
}
- status = h2_session_process(conn_ctx->session, async_mpm);
+ status = h2_session_process(conn_ctx->session, async_mpm, &keepalive);
if (APR_STATUS_IS_EOF(status)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
case H2_SESSION_ST_BUSY:
case H2_SESSION_ST_WAIT:
c->cs->state = CONN_STATE_WRITE_COMPLETION;
- if (c->cs && !conn_ctx->session->remote.emitted_count) {
+ if (!keepalive) {
/* let the MPM know that we are not done and want
* the Timeout behaviour instead of a KeepAliveTimeout
* See PR 63534.
typedef struct {
int stream_count;
int stream_want_send;
+ int stream_send_win_exhausted;
} stream_iter_aws_t;
static int m_stream_want_send_data(void *ctx, void *stream)
return x.stream_count && (x.stream_count == x.stream_want_send);
}
+static int m_stream_send_win_exh(void *ctx, void *s)
+{
+ h2_stream *stream = s;
+ int win;
+ stream_iter_aws_t *x = ctx;
+ ++x->stream_count;
+ win = nghttp2_session_get_stream_remote_window_size(stream->session->ngh2, stream->id);
+ if (win == 0)
+ ++x->stream_send_win_exhausted;
+ return 1;
+}
+
+int h2_mplx_c1_all_streams_send_win_exhausted(h2_mplx *m)
+{
+ stream_iter_aws_t x;
+ x.stream_count = 0;
+ x.stream_send_win_exhausted = 0;
+ H2_MPLX_ENTER(m);
+ h2_ihash_iter(m->streams, m_stream_send_win_exh, &x);
+ H2_MPLX_LEAVE(m);
+ return x.stream_count && (x.stream_count == x.stream_send_win_exhausted);
+}
+
static int m_report_stream_iter(void *ctx, void *val) {
h2_mplx *m = ctx;
h2_stream *stream = val;
(!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 */
+ * with an informative HTTP error response like 413. But of the
+ * client is too wrong, we RESET the stream */
stream->request_headers_failed > 100)) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
}
}
-apr_status_t h2_session_process(h2_session *session, int async)
+static int h2_send_flow_blocked(h2_session *session)
+{
+ /* We are completely send blocked if either the connection window
+ * is 0 or all stream flow windows are 0. */
+ return ((nghttp2_session_get_remote_window_size(session->ngh2) <= 0) ||
+ h2_mplx_c1_all_streams_send_win_exhausted(session->mplx));
+}
+
+apr_status_t h2_session_process(h2_session *session, int async,
+ int *pkeepalive)
{
apr_status_t status = APR_SUCCESS;
conn_rec *c = session->c1;
int rv, mpm_state, trace = APLOGctrace3(c);
+ *pkeepalive = 0;
if (trace) {
ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
H2_SSSN_MSG(session, "process start, async=%d"), async);
break;
case H2_SESSION_ST_WAIT:
+ /* In this state, we might have returned processing to the MPM
+ * before. On a connection socket event, we are invoked again and
+ * need to process any input before proceeding. */
+ h2_c1_read(session);
+ if (session->state != H2_SESSION_ST_WAIT) {
+ break;
+ }
+
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;
}
}
- /* No IO happening and input is exhausted. Make sure we have
- * flushed any possibly pending output and then wait with
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 19)
+ else if (async && h2_send_flow_blocked(session)) {
+ /* On a recent HTTPD, we can return to mpm c1 monitoring,
+ * as it does not treat all connections as having KeepAlive
+ * timing and being purgeable on load.
+ * By returning to the MPM, we do not block a worker
+ * and async wait for the client send window updates. */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+ H2_SSSN_LOG(APLOGNO(10502), session,
+ "BLOCKED, return to mpm c1 monitoring"));
+ goto leaving;
+ }
+#endif
+ /* No IO happening and input is exhausted. 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"),
}
leaving:
+ /* entering KeepAlive timing when we have no more open streams AND
+ * we have processed at least one stream. */
+ *pkeepalive = (session->open_streams == 0 && session->remote.emitted_count);
if (trace) {
- ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
- H2_SSSN_MSG(session, "process returns"));
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
+ H2_SSSN_MSG(session, "process returns, keepalive=%d"),
+ *pkeepalive);
}
h2_mplx_c1_going_keepalive(session->mplx);