[6.0] c3902d57f H2 Backports

Reza Naghibi reza at naghibi.com
Thu Oct 17 16:33:08 UTC 2019


commit c3902d57ff253eb7bf90f067c98e40f3bc5cad70
Author: Andrew Wiik <andrew at varnish-software.com>
Date:   Mon Oct 7 11:26:32 2019 -0400

    H2 Backports
    
    6233b08842d8ea48e78b9fc5381a26a2c753862e
    4d8e4b9cb960039d08814034d65d3ece1e78d042
    c726746621f7b87a069fdc0ca83bef82b4cefd5b
    cda1921004f10d3a56e6e044426473d99c88fa56
    dee47cc47b52dd8af11cb073661e9a757944aa7c
    e34de5c17515d9975588becde2577db60858fcc9
    a0ad902537bd0d14e663a7208f398ad55aed29e1
    54be487be4863decea34f4cf9600789b0c51f838
    1dc891a5aad0e20f45726ea1c2d923d520917691
    0582753d3708bd8cd546b30e6d6096e91a721b39
    78694521f5b41677e0c7f45907eb20ff48e7bb46
    3c04f5c715a078cec448ffc14a48fec38596d4e7
    f2357c88fa396b3fe911e9f194527e1fbe0dd978
    740ee39c8d6ffd36631a136cb9be23cdb13893d0
    cb1b5899b13f0240631f54794dd7c26660245cb0
    458306fa8ea4391b4567e52a5b869267b0f76ffc
    0e32f1667ea55156fc2a7fa84e3f1080a05bcb2e
    9e1e0972c47c5ea03350c39c76370ccafd678802
    a82d8552fc9f612b2295c357a256f896c6168368
    4bbf6df68722dbe3ea77508d160c1dd258ab1d5e
    d8421dcd59a8cf271e4587a0228a3a24954636f2
    3c8ecb4cc0447e0149715e2fbb773a8fa9f433ce
    a0b8a73441511e3ee90479f11f927d0600e9ae06
    8c5118d17cba2f4d78a672f42f16f1cbff2ca766
    3d170c0d8fd029eb8604854b9a758404f19008c2
    d869640ba8b4978c890610a1a6dcd1bf82a600d3
    1506c5cffc9b7882eb059ce128dfdf824c41445f
    5ecbde3812f9d17b47faf3b6c44567bcb7060fac
    e1a1fdc7688de5f37e35fc528639019d5bd3efbf
    64c0e1aec5145dd1c977f6e53e84e54288447855
    93e6dd706074d5f5815bd03e76d63fa30f6666c9
    f3e9ca6abc4a03e48df4e9894323cad25472793f
    07ad2fb863b7db12e94449e11d47a1eefb2dd359
    
    Co-authored-by: Dag Haavi Finstad <daghf at varnish-software.com>
    Co-authored-by: Dridi Boukelmoune <dridi.boukelmoune at gmail.com>
    Co-authored-by: Federico G. Schwindt <fgsch at lodoss.net>
    Co-authored-by: Nils Goroll <nils.goroll at uplex.de>
    Co-authored-by: Poul-Henning Kamp <phk at FreeBSD.org>
    Co-authored-by: Shohei Tanaka(@xcir) <kokoniimasu+git at gmail.com>

diff --git a/bin/varnishd/cache/cache.h b/bin/varnishd/cache/cache.h
index a6d4f514c..2d6f1ccbc 100644
--- a/bin/varnishd/cache/cache.h
+++ b/bin/varnishd/cache/cache.h
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 2006 Verdens Gang AS
- * Copyright (c) 2006-2015 Varnish Software AS
+ * Copyright (c) 2006-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -567,8 +567,12 @@ struct sess {
 	vtim_real		t_open;		/* fd accepted */
 	vtim_real		t_idle;		/* fd accepted or resp sent */
 
+	vtim_dur		timeout_idle;
 };
 
+#define SESS_TMO(sp, tmo)					\
+	(isnan((sp)->tmo) ? cache_param->tmo : (sp)->tmo)
+
 /* Prototypes etc ----------------------------------------------------*/
 
 
diff --git a/bin/varnishd/cache/cache_esi_deliver.c b/bin/varnishd/cache/cache_esi_deliver.c
index 95bc5f2ed..2f3f8e86f 100644
--- a/bin/varnishd/cache/cache_esi_deliver.c
+++ b/bin/varnishd/cache/cache_esi_deliver.c
@@ -208,7 +208,7 @@ ved_include(struct req *preq, const char *src, const char *host,
 	req->wrk = NULL;
 	THR_SetRequest(preq);
 
-	Req_AcctLogCharge(wrk->stats, req);
+	Req_Cleanup(sp, wrk, req);
 	Req_Release(req);
 	SES_Rel(sp);
 }
diff --git a/bin/varnishd/cache/cache_req.c b/bin/varnishd/cache/cache_req.c
index 5ab13a2cf..eb7bea2a5 100644
--- a/bin/varnishd/cache/cache_req.c
+++ b/bin/varnishd/cache/cache_req.c
@@ -43,8 +43,8 @@
 
 #include "vtim.h"
 
-void
-Req_AcctLogCharge(struct VSC_main_wrk *ds, struct req *req)
+static void
+req_AcctLogCharge(struct VSC_main_wrk *ds, struct req *req)
 {
 	struct acct_req *a;
 
@@ -63,7 +63,11 @@ Req_AcctLogCharge(struct VSC_main_wrk *ds, struct req *req)
 		    (uintmax_t)(a->resp_hdrbytes + a->resp_bodybytes));
 	}
 
-	/* Charge to main byte counters (except for ESI subrequests) */
+	/*
+	 * Charge to main byte counters, except for ESI subrequests
+	 * which are charged as they pass through the topreq.
+	 * XXX: make this test req->top instead
+	 */
 #define ACCT(foo)			\
 	if (req->esi_level == 0)	\
 		ds->s_##foo += a->foo;	\
@@ -166,8 +170,7 @@ Req_Release(struct req *req)
 #include "tbl/acct_fields_req.h"
 
 	AZ(req->vcl);
-	if (req->vsl->wid)
-		VSL_End(req->vsl);
+	AZ(req->vsl->wid);
 	sp = req->sp;
 	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
 	pp = sp->pool;
@@ -212,9 +215,7 @@ Req_Cleanup(struct sess *sp, struct worker *wrk, struct req *req)
 	req->director_hint = NULL;
 	req->restarts = 0;
 
-	AZ(req->esi_level);
 	AZ(req->privs->magic);
-	assert(req->top == req);
 
 	if (req->vcl != NULL) {
 		if (wrk->vcl != NULL)
@@ -224,10 +225,9 @@ Req_Cleanup(struct sess *sp, struct worker *wrk, struct req *req)
 	}
 
 	/* Charge and log byte counters */
-	if (req->vsl->wid) {
-		Req_AcctLogCharge(wrk->stats, req);
+	req_AcctLogCharge(wrk->stats, req);
+	if (req->vsl->wid)
 		VSL_End(req->vsl);
-	}
 	req->req_bodybytes = 0;
 
 	if (!isnan(req->t_prev) && req->t_prev > 0. && req->t_prev > sp->t_idle)
@@ -243,6 +243,7 @@ Req_Cleanup(struct sess *sp, struct worker *wrk, struct req *req)
 	req->hash_always_miss = 0;
 	req->hash_ignore_busy = 0;
 	req->is_hit = 0;
+	req->esi_level = 0;
 
 	if (WS_Overflowed(req->ws))
 		wrk->stats->ws_client_overflow++;
diff --git a/bin/varnishd/cache/cache_session.c b/bin/varnishd/cache/cache_session.c
index 2402b10b5..a8a75b7e7 100644
--- a/bin/varnishd/cache/cache_session.c
+++ b/bin/varnishd/cache/cache_session.c
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 2006 Verdens Gang AS
- * Copyright (c) 2006-2011 Varnish Software AS
+ * Copyright (c) 2006-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -248,12 +248,14 @@ HTC_RxPipeline(struct http_conn *htc, void *p)
  * *t1 becomes time of first non-idle rx
  * *t2 becomes time of complete rx
  * ti is when we return IDLE if nothing has arrived
- * tn is when we timeout on non-complete
+ * tn is when we timeout on non-complete (total timeout)
+ * td is max timeout between reads
  */
 
 enum htc_status_e
 HTC_RxStuff(struct http_conn *htc, htc_complete_f *func,
-    vtim_real *t1, vtim_real *t2, vtim_real ti, vtim_real tn, int maxbytes)
+    vtim_real *t1, vtim_real *t2, vtim_real ti, vtim_real tn, vtim_dur td,
+    int maxbytes)
 {
 	vtim_dur tmo;
 	vtim_real now;
@@ -268,7 +270,7 @@ HTC_RxStuff(struct http_conn *htc, htc_complete_f *func,
 	assert(htc->rxbuf_b <= htc->rxbuf_e);
 	assert(htc->rxbuf_e <= htc->ws->r);
 
-	AZ(isnan(tn));
+	AZ(isnan(tn) && isnan(td));
 	if (t1 != NULL)
 		assert(isnan(*t1));
 
@@ -310,9 +312,18 @@ HTC_RxStuff(struct http_conn *htc, htc_complete_f *func,
 		else
 			WRONG("htc_status_e");
 
-		tmo = tn - now;
-		if (!isnan(ti) && ti < tn && hs == HTC_S_EMPTY)
+		if (hs == HTC_S_EMPTY && !isnan(ti) && (isnan(tn) || ti < tn))
 			tmo = ti - now;
+		else if (isnan(tn))
+			tmo = td;
+		else if (isnan(td))
+			tmo = tn - now;
+		else if (td < tn - now)
+			tmo = td;
+		else
+			tmo = tn - now;
+
+		AZ(isnan(tmo));
 		z = maxbytes - (htc->rxbuf_e - htc->rxbuf_b);
 		if (z <= 0) {
 			/* maxbytes reached but not HTC_S_COMPLETE. Return
@@ -329,14 +340,11 @@ HTC_RxStuff(struct http_conn *htc, htc_complete_f *func,
 		} else if (z > 0)
 			htc->rxbuf_e += z;
 		else if (z == -2) {
-			if (hs == HTC_S_EMPTY && ti <= now) {
-				WS_ReleaseP(htc->ws, htc->rxbuf_b);
+			WS_ReleaseP(htc->ws, htc->rxbuf_b);
+			if (hs == HTC_S_EMPTY)
 				return (HTC_S_IDLE);
-			}
-			if (tn <= now) {
-				WS_ReleaseP(htc->ws, htc->rxbuf_b);
+			else
 				return (HTC_S_TIMEOUT);
-			}
 		}
 	}
 }
@@ -371,6 +379,7 @@ SES_New(struct pool *pp)
 
 	sp->t_open = NAN;
 	sp->t_idle = NAN;
+	sp->timeout_idle = NAN;
 	Lck_New(&sp->mtx, lck_sess);
 	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
 	return (sp);
@@ -458,7 +467,7 @@ SES_Wait(struct sess *sp, const struct transport *xp)
 	wp->priv2 = (uintptr_t)xp;
 	wp->idle = sp->t_idle;
 	wp->func = ses_handle;
-	wp->tmo = &cache_param->timeout_idle;
+	wp->tmo = SESS_TMO(sp, timeout_idle);
 	if (Wait_Enter(pp->waiter, wp))
 		SES_Delete(sp, SC_PIPE_OVERFLOW, NAN);
 }
diff --git a/bin/varnishd/cache/cache_tcp_pool.c b/bin/varnishd/cache/cache_tcp_pool.c
index 9d623ef9c..7ea5942ee 100644
--- a/bin/varnishd/cache/cache_tcp_pool.c
+++ b/bin/varnishd/cache/cache_tcp_pool.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2015 Varnish Software AS
+ * Copyright (c) 2015-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -339,7 +339,7 @@ VCP_Recycle(const struct worker *wrk, struct pfd **pfdp)
 	pfd->waited->idle = VTIM_real();
 	pfd->state = PFD_STATE_AVAIL;
 	pfd->waited->func = vcp_handle;
-	pfd->waited->tmo = &cache_param->backend_idle_timeout;
+	pfd->waited->tmo = cache_param->backend_idle_timeout;
 	if (Wait_Enter(wrk->pool->waiter, pfd->waited)) {
 		cp->methods->close(pfd);
 		memset(pfd, 0x33, sizeof *pfd);
diff --git a/bin/varnishd/cache/cache_varnishd.h b/bin/varnishd/cache/cache_varnishd.h
index ad5e0be6d..5d036f6da 100644
--- a/bin/varnishd/cache/cache_varnishd.h
+++ b/bin/varnishd/cache/cache_varnishd.h
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 2006 Verdens Gang AS
- * Copyright (c) 2006-2015 Varnish Software AS
+ * Copyright (c) 2006-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -318,7 +318,6 @@ void Req_Release(struct req *);
 void Req_Rollback(struct req *req);
 void Req_Cleanup(struct sess *sp, struct worker *wrk, struct req *req);
 void Req_Fail(struct req *req, enum sess_close reason);
-void Req_AcctLogCharge(struct VSC_main_wrk *, struct req *);
 
 /* cache_req_body.c */
 int VRB_Ignore(struct req *);
@@ -347,7 +346,8 @@ const char * HTC_Status(enum htc_status_e);
 void HTC_RxInit(struct http_conn *htc, struct ws *ws);
 void HTC_RxPipeline(struct http_conn *htc, void *);
 enum htc_status_e HTC_RxStuff(struct http_conn *, htc_complete_f *,
-    vtim_real *t1, vtim_real *t2, vtim_real ti, vtim_real tn, int maxbytes);
+    vtim_real *t1, vtim_real *t2, vtim_real ti, vtim_real tn, vtim_dur td,
+    int maxbytes);
 
 #define SESS_ATTR(UP, low, typ, len)					\
 	int SES_Set_##low(const struct sess *sp, const typ *src);	\
diff --git a/bin/varnishd/cache/cache_vrt_var.c b/bin/varnishd/cache/cache_vrt_var.c
index d3787b193..b7557edf5 100644
--- a/bin/varnishd/cache/cache_vrt_var.c
+++ b/bin/varnishd/cache/cache_vrt_var.c
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 2006 Verdens Gang AS
- * Copyright (c) 2006-2015 Varnish Software AS
+ * Copyright (c) 2006-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -923,3 +923,27 @@ HTTP_VAR(req)
 HTTP_VAR(resp)
 HTTP_VAR(bereq)
 HTTP_VAR(beresp)
+
+/*--------------------------------------------------------------------*/
+
+VCL_VOID
+VRT_l_sess_timeout_idle(VRT_CTX, VCL_DURATION d)
+{
+	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+	CHECK_OBJ_NOTNULL(ctx->sp, SESS_MAGIC);
+	ctx->sp->timeout_idle = d > 0.0 ? d : 0.0;
+}
+
+VCL_DURATION
+VRT_r_sess_timeout_idle(VRT_CTX)
+{
+	VCL_DURATION d;
+
+	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+	CHECK_OBJ_NOTNULL(ctx->sp, SESS_MAGIC);
+
+	d = ctx->sp->timeout_idle;
+	if (isnan(d))
+		return (cache_param->timeout_idle);
+	return (d);
+}
diff --git a/bin/varnishd/http1/cache_http1_fetch.c b/bin/varnishd/http1/cache_http1_fetch.c
index c937d741e..879115b63 100644
--- a/bin/varnishd/http1/cache_http1_fetch.c
+++ b/bin/varnishd/http1/cache_http1_fetch.c
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 2006 Verdens Gang AS
- * Copyright (c) 2006-2015 Varnish Software AS
+ * Copyright (c) 2006-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -170,7 +170,7 @@ V1F_FetchRespHdr(struct busyobj *bo)
 
 	t = VTIM_real() + htc->first_byte_timeout;
 	hs = HTC_RxStuff(htc, HTTP1_Complete, NULL, NULL,
-	    t, t + htc->between_bytes_timeout, cache_param->http_resp_size);
+	     t, NAN, htc->between_bytes_timeout, cache_param->http_resp_size);
 	if (hs != HTC_S_COMPLETE) {
 		bo->acct.beresp_hdrbytes +=
 		    htc->rxbuf_e - htc->rxbuf_b;
diff --git a/bin/varnishd/http1/cache_http1_fsm.c b/bin/varnishd/http1/cache_http1_fsm.c
index fad043e84..1c216dc78 100644
--- a/bin/varnishd/http1/cache_http1_fsm.c
+++ b/bin/varnishd/http1/cache_http1_fsm.c
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 2006 Verdens Gang AS
- * Copyright (c) 2006-2015 Varnish Software AS
+ * Copyright (c) 2006-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -337,13 +337,14 @@ HTTP1_Session(struct worker *wrk, struct req *req)
 			hs = HTC_RxStuff(req->htc, HTTP1_Complete,
 			    &req->t_first, &req->t_req,
 			    sp->t_idle + cache_param->timeout_linger,
-			    sp->t_idle + cache_param->timeout_idle,
+			    sp->t_idle + SESS_TMO(sp, timeout_idle),
+			    NAN,
 			    cache_param->http_req_size);
 			AZ(req->htc->ws->r);
 			if (hs < HTC_S_EMPTY) {
 				req->acct.req_hdrbytes +=
 				    req->htc->rxbuf_e - req->htc->rxbuf_b;
-				Req_AcctLogCharge(wrk->stats, req);
+				Req_Cleanup(sp, wrk, req);
 				Req_Release(req);
 				switch (hs) {
 				case HTC_S_CLOSE:
@@ -364,6 +365,7 @@ HTTP1_Session(struct worker *wrk, struct req *req)
 			}
 			if (hs == HTC_S_IDLE) {
 				wrk->stats->sess_herd++;
+				Req_Cleanup(sp, wrk, req);
 				Req_Release(req);
 				SES_Wait(sp, &HTTP1_transport);
 				return;
diff --git a/bin/varnishd/http2/cache_http2.h b/bin/varnishd/http2/cache_http2.h
index 70e31d14e..c377d03aa 100644
--- a/bin/varnishd/http2/cache_http2.h
+++ b/bin/varnishd/http2/cache_http2.h
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2016 Varnish Software AS
+ * Copyright (c) 2016-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -133,6 +133,8 @@ struct h2_req {
 
 	VTAILQ_ENTRY(h2_req)		tx_list;
 	h2_error			error;
+
+	int				counted;
 };
 
 VTAILQ_HEAD(h2_req_s, h2_req);
@@ -178,6 +180,8 @@ struct h2_sess {
 	VTAILQ_HEAD(,h2_req)		txqueue;
 
 	h2_error			error;
+
+	int				open_streams;
 };
 
 #define ASSERT_RXTHR(h2) do {assert(h2->rxthr == pthread_self());} while(0)
@@ -221,15 +225,18 @@ void H2_Send_Frame(struct worker *, struct h2_sess *,
     h2_frame type, uint8_t flags, uint32_t len, uint32_t stream,
     const void *);
 
-void H2_Send(struct worker *, struct h2_req *,
-    h2_frame type, uint8_t flags, uint32_t len, const void *);
+void H2_Send_RST(struct worker *wrk, struct h2_sess *h2,
+    const struct h2_req *r2, uint32_t stream, h2_error h2e);
+
+void H2_Send(struct worker *, struct h2_req *, h2_frame type, uint8_t flags,
+    uint32_t len, const void *, uint64_t *acct);
 
 /* cache_http2_proto.c */
 struct h2_req * h2_new_req(const struct worker *, struct h2_sess *,
     unsigned stream, struct req *);
+int h2_stream_tmo(struct h2_sess *, const struct h2_req *, vtim_real);
 void h2_del_req(struct worker *, const struct h2_req *);
-void h2_kill_req(struct worker *, const struct h2_sess *,
-    struct h2_req *, h2_error);
+void h2_kill_req(struct worker *, struct h2_sess *, struct h2_req *, h2_error);
 int h2_rxframe(struct worker *, struct h2_sess *);
 h2_error h2_set_setting(struct h2_sess *, const uint8_t *);
 void h2_req_body(struct req*);
diff --git a/bin/varnishd/http2/cache_http2_deliver.c b/bin/varnishd/http2/cache_http2_deliver.c
index 1a1de05be..46f7654d9 100644
--- a/bin/varnishd/http2/cache_http2_deliver.c
+++ b/bin/varnishd/http2/cache_http2_deliver.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2016 Varnish Software AS
+ * Copyright (c) 2016-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -87,8 +87,12 @@ h2_fini(struct req *req, void **priv)
 
 	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
 	TAKE_OBJ_NOTNULL(r2, priv, H2_REQ_MAGIC);
+
+	if (r2->error)
+		return (0);
+
 	H2_Send_Get(req->wrk, r2->h2sess, r2);
-	H2_Send(req->wrk, r2, H2_F_DATA, H2FF_DATA_END_STREAM, 0, "");
+	H2_Send(req->wrk, r2, H2_F_DATA, H2FF_DATA_END_STREAM, 0, "", NULL);
 	H2_Send_Rel(r2->h2sess, r2);
 	return (0);
 }
@@ -106,9 +110,9 @@ h2_bytes(struct req *req, enum vdp_action act, void **priv,
 	if ((r2->h2sess->error || r2->error))
 		return (-1);
 	H2_Send_Get(req->wrk, r2->h2sess, r2);
-	H2_Send(req->wrk, r2, H2_F_DATA, H2FF_NONE, len, ptr);
+	H2_Send(req->wrk, r2, H2_F_DATA, H2FF_NONE, len, ptr,
+	    &req->acct.resp_bodybytes);
 	H2_Send_Rel(r2->h2sess, r2);
-	req->acct.resp_bodybytes += len;
 	return (0);
 }
 
@@ -173,7 +177,7 @@ h2_minimal_response(struct req *req, uint16_t status)
 	    H2_F_HEADERS,
 	    H2FF_HEADERS_END_HEADERS |
 		(status < 200 ? 0 : H2FF_HEADERS_END_STREAM),
-	    l, buf);
+	    l, buf, NULL);
 	H2_Send_Rel(r2->h2sess, r2);
 	return (0);
 }
@@ -214,35 +218,31 @@ static const uint8_t h2_500_resp[] = {
 	0x1f, 0x27, 0x07, 'V', 'a', 'r', 'n', 'i', 's', 'h',
 };
 
-void v_matchproto_(vtr_deliver_f)
-h2_deliver(struct req *req, struct boc *boc, int sendbody)
+static int
+h2_build_headers(struct vsb *resp, struct req *req)
 {
-	ssize_t sz, sz1;
 	unsigned u, l;
-	uint8_t buf[6];
-	const char *r;
-	struct http *hp;
-	struct sess *sp;
-	struct h2_req *r2;
-	struct vsb resp;
 	int i;
+	struct http *hp;
+	const char *r;
 	const struct hpack_static *hps;
-
-	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
-	CHECK_OBJ_ORNULL(boc, BOC_MAGIC);
-	CHECK_OBJ_NOTNULL(req->objcore, OBJCORE_MAGIC);
-	CAST_OBJ_NOTNULL(r2, req->transport_priv, H2_REQ_MAGIC);
-	sp = req->sp;
-	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+	uint8_t buf[6];
+	ssize_t sz, sz1;
 
 	l = WS_Reserve(req->ws, 0);
-	AN(VSB_new(&resp, req->ws->f, l, VSB_FIXEDLEN));
+	AN(VSB_new(resp, req->ws->f, l, VSB_FIXEDLEN));
+	if (l < 10) {
+		WS_Release(req->ws, 0);
+		return (-1);
+	}
+
+	AN(VSB_new(resp, req->ws->f, l, VSB_FIXEDLEN));
 
 	l = h2_status(buf, req->resp->status);
-	VSB_bcat(&resp, buf, l);
+	VSB_bcat(resp, buf, l);
 
 	hp = req->resp;
-	for (u = HTTP_HDR_FIRST; u < hp->nhd && !VSB_error(&resp); u++) {
+	for (u = HTTP_HDR_FIRST; u < hp->nhd && !VSB_error(resp); u++) {
 		r = strchr(hp->hd[u].b, ':');
 		AN(r);
 
@@ -263,25 +263,46 @@ h2_deliver(struct req *req, struct boc *boc, int sendbody)
 			VSLb(req->vsl, SLT_Debug,
 			    "HP {%d, \"%s\", \"%s\"} <%s>",
 			    hps->idx, hps->name, hps->val, hp->hd[u].b);
-			h2_enc_len(&resp, 4, hps->idx, 0x10);
+			h2_enc_len(resp, 4, hps->idx, 0x10);
 		} else {
-			VSB_putc(&resp, 0x10);
+			VSB_putc(resp, 0x10);
 			sz--;
-			h2_enc_len(&resp, 7, sz, 0);
+			h2_enc_len(resp, 7, sz, 0);
 			for (sz1 = 0; sz1 < sz; sz1++)
-				VSB_putc(&resp, tolower(hp->hd[u].b[sz1]));
+				VSB_putc(resp, tolower(hp->hd[u].b[sz1]));
 
 		}
 
 		while (vct_islws(*++r))
 			continue;
 		sz = hp->hd[u].e - r;
-		h2_enc_len(&resp, 7, sz, 0);
-		VSB_bcat(&resp, r, sz);
+		h2_enc_len(resp, 7, sz, 0);
+		VSB_bcat(resp, r, sz);
 	}
-	if (VSB_finish(&resp)) {
-		WS_MarkOverflow(req->ws);
+	return (VSB_finish(resp));
+}
+
+void v_matchproto_(vtr_deliver_f)
+h2_deliver(struct req *req, struct boc *boc, int sendbody)
+{
+	ssize_t sz;
+	const char *r;
+	struct sess *sp;
+	struct h2_req *r2;
+	struct vsb resp;
+
+	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
+	CHECK_OBJ_ORNULL(boc, BOC_MAGIC);
+	CHECK_OBJ_NOTNULL(req->objcore, OBJCORE_MAGIC);
+	CAST_OBJ_NOTNULL(r2, req->transport_priv, H2_REQ_MAGIC);
+	sp = req->sp;
+	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+
+	VSLb(req->vsl, SLT_RespProtocol, "HTTP/2.0");
+
+	if (h2_build_headers(&resp, req)) {
 		// We ran out of workspace, return minimal 500
+		WS_MarkOverflow(req->ws);
 		VSLb(req->vsl, SLT_Error, "workspace_client overflow");
 		VSLb(req->vsl, SLT_RespStatus, "500");
 		VSLb(req->vsl, SLT_RespReason, "Internal Server Error");
@@ -305,9 +326,8 @@ h2_deliver(struct req *req, struct boc *boc, int sendbody)
 	H2_Send_Get(req->wrk, r2->h2sess, r2);
 	H2_Send(req->wrk, r2, H2_F_HEADERS,
 	    (sendbody ? 0 : H2FF_HEADERS_END_STREAM) | H2FF_HEADERS_END_HEADERS,
-	    sz, r);
+	    sz, r, &req->acct.resp_hdrbytes);
 	H2_Send_Rel(r2->h2sess, r2);
-	req->acct.resp_hdrbytes += sz;
 
 	WS_Release(req->ws, 0);
 
diff --git a/bin/varnishd/http2/cache_http2_hpack.c b/bin/varnishd/http2/cache_http2_hpack.c
index 0524f3e32..f2ef11cf7 100644
--- a/bin/varnishd/http2/cache_http2_hpack.c
+++ b/bin/varnishd/http2/cache_http2_hpack.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2016 Varnish Software AS
+ * Copyright (c) 2016-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Martin Blix Grydeland <martin at varnish-software.com>
@@ -141,7 +141,7 @@ h2h_addhdr(struct http *hp, char *b, size_t namelen, size_t len)
 
 	if (n < HTTP_HDR_FIRST) {
 		/* Check for duplicate pseudo-header */
-		if (n < HTTP_HDR_FIRST && hp->hd[n].b != NULL) {
+		if (hp->hd[n].b != NULL) {
 			VSLb(hp->vsl, SLT_BogoHeader,
 			    "Duplicate pseudo-header: %.*s",
 			    (int)(len > 20 ? 20 : len), b);
@@ -176,8 +176,11 @@ h2h_decode_init(const struct h2_sess *h2)
 	INIT_OBJ(d, H2H_DECODE_MAGIC);
 	VHD_Init(d->vhd);
 	d->out_l = WS_Reserve(h2->new_req->http->ws, 0);
-	assert(d->out_l > 0);	/* Can't do any work without any buffer
-				   space. Require non-zero size. */
+	/*
+	 * Can't do any work without any buffer
+	 * space. Require non-zero size.
+	 */
+	XXXAN(d->out_l);
 	d->out = h2->new_req->http->ws->f;
 	d->reset = d->out;
 }
diff --git a/bin/varnishd/http2/cache_http2_proto.c b/bin/varnishd/http2/cache_http2_proto.c
index 194f225c5..6a98a1c4b 100644
--- a/bin/varnishd/http2/cache_http2_proto.c
+++ b/bin/varnishd/http2/cache_http2_proto.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2016 Varnish Software AS
+ * Copyright (c) 2016-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -132,28 +132,6 @@ h2_connectionerror(uint32_t u)
 
 /**********************************************************************/
 
-static void
-h2_tx_rst(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2,
-    uint32_t stream, h2_error h2e)
-{
-	char b[4];
-
-	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
-	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
-
-	Lck_Lock(&h2->sess->mtx);
-	VSLb(h2->vsl, SLT_Debug, "H2: stream %u: %s", stream, h2e->txt);
-	Lck_Unlock(&h2->sess->mtx);
-	vbe32enc(b, h2e->val);
-
-	H2_Send_Get(wrk, h2, r2);
-	H2_Send_Frame(wrk, h2, H2_F_RST_STREAM, 0, sizeof b, stream, b);
-	H2_Send_Rel(h2, r2);
-}
-
-/**********************************************************************
- */
-
 struct h2_req *
 h2_new_req(const struct worker *wrk, struct h2_sess *h2,
     unsigned stream, struct req *req)
@@ -172,10 +150,14 @@ h2_new_req(const struct worker *wrk, struct h2_sess *h2,
 	r2->h2sess = h2;
 	r2->stream = stream;
 	r2->req = req;
+	if (stream)
+		r2->counted = 1;
 	r2->r_window = h2->local_settings.initial_window_size;
 	r2->t_window = h2->remote_settings.initial_window_size;
 	req->transport_priv = r2;
 	Lck_Lock(&h2->sess->mtx);
+	if (stream)
+		h2->open_streams++;
 	VTAILQ_INSERT_TAIL(&h2->streams, r2, list);
 	Lck_Unlock(&h2->sess->mtx);
 	h2->refcnt++;
@@ -206,14 +188,20 @@ h2_del_req(struct worker *wrk, const struct h2_req *r2)
 }
 
 void
-h2_kill_req(struct worker *wrk, const struct h2_sess *h2,
+h2_kill_req(struct worker *wrk, struct h2_sess *h2,
     struct h2_req *r2, h2_error h2e)
 {
 
 	ASSERT_RXTHR(h2);
 	AN(h2e);
 	Lck_Lock(&h2->sess->mtx);
-	VSLb(h2->vsl, SLT_Debug, "KILL st=%u state=%d", r2->stream, r2->state);
+	VSLb(h2->vsl, SLT_Debug, "KILL st=%u state=%d sched=%d",
+	    r2->stream, r2->state, r2->scheduled);
+	if (r2->counted) {
+		assert(h2->open_streams > 0);
+		h2->open_streams--;
+		r2->counted = 0;
+	}
 	if (r2->error == NULL)
 		r2->error = h2e;
 	if (r2->scheduled) {
@@ -631,7 +619,12 @@ h2_rx_headers(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 	if (r2 == NULL) {
 		if (h2->rxf_stream <= h2->highest_stream)
 			return (H2CE_PROTOCOL_ERROR);	// rfc7540,l,1153,1158
-		if (h2->refcnt >= h2->local_settings.max_concurrent_streams) {
+		/* NB: we don't need to guard the read of h2->open_streams
+		 * because headers are handled sequentially so it cannot
+		 * increase under our feet.
+		 */
+		if (h2->open_streams >=
+		    h2->local_settings.max_concurrent_streams) {
 			VSLb(h2->vsl, SLT_Debug,
 			     "H2: stream %u: Hit maximum number of "
 			     "concurrent streams", h2->rxf_stream);
@@ -755,6 +748,10 @@ h2_rx_data(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 		return (H2SE_STREAM_CLOSED); // rfc7540,l,1766,1769
 	}
 	Lck_Lock(&h2->sess->mtx);
+	while (h2->mailcall != NULL && h2->error == 0 && r2->error == 0)
+		AZ(Lck_CondWait(h2->cond, &h2->sess->mtx, 0));
+	if (h2->error || r2->error)
+		return (h2->error ? h2->error : r2->error);
 	AZ(h2->mailcall);
 	h2->mailcall = r2;
 	h2->req0->r_window -= h2->rxf_len;
@@ -858,8 +855,10 @@ h2_vfp_body_fini(struct vfp_ctx *vc, struct vfp_entry *vfe)
 
 	if (vc->failed) {
 		CHECK_OBJ_NOTNULL(r2->req->wrk, WORKER_MAGIC);
-		h2_tx_rst(r2->req->wrk, h2, r2, r2->stream,
+		H2_Send_Get(r2->req->wrk, h2, r2);
+		H2_Send_RST(r2->req->wrk, h2, r2, r2->stream,
 		    H2SE_REFUSED_STREAM);
+		H2_Send_Rel(h2, r2);
 		Lck_Lock(&h2->sess->mtx);
 		r2->error = H2SE_REFUSED_STREAM;
 		if (h2->mailcall == r2) {
@@ -963,41 +962,60 @@ h2_procframe(struct worker *wrk, struct h2_sess *h2, h2_frame h2f)
 	if (h2->rxf_stream == 0 || h2e->connection)
 		return (h2e);	// Connection errors one level up
 
-	h2_tx_rst(wrk, h2, h2->req0, h2->rxf_stream, h2e);
+	H2_Send_Get(wrk, h2, h2->req0);
+	H2_Send_RST(wrk, h2, h2->req0, h2->rxf_stream, h2e);
+	H2_Send_Rel(h2, h2->req0);
 	return (0);
 }
 
-static int
-h2_stream_tmo(struct h2_sess *h2, const struct h2_req *r2)
+int
+h2_stream_tmo(struct h2_sess *h2, const struct h2_req *r2, vtim_real now)
 {
 	int r = 0;
 
 	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
 	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
+	Lck_AssertHeld(&h2->sess->mtx);
 
-	if (r2->t_winupd != 0 || r2->t_send != 0) {
-		Lck_Lock(&h2->sess->mtx);
-		if (r2->t_winupd != 0 &&
-		    h2->sess->t_idle - r2->t_winupd >
-		    cache_param->idle_send_timeout) {
-			VSLb(h2->vsl, SLT_Debug,
-			     "H2: stream %u: Hit idle_send_timeout waiting "
-			     "for WINDOW_UPDATE", r2->stream);
-			r = 1;
-		}
+	/* NB: when now is NAN, it means that idle_send_timeout was hit
+	 * on a lock condwait operation.
+	 */
+	if (isnan(now))
+		AN(r2->t_winupd);
 
-		if (r == 0 && r2->t_send != 0 &&
-		    h2->sess->t_idle - r2->t_send > cache_param->send_timeout) {
-			VSLb(h2->vsl, SLT_Debug,
-			     "H2: stream %u: Hit send_timeout", r2->stream);
-			r = 1;
-		}
-		Lck_Unlock(&h2->sess->mtx);
+	if (r2->t_winupd == 0 && r2->t_send == 0)
+		return (0);
+
+	if (isnan(now) || (r2->t_winupd != 0 &&
+	    now - r2->t_winupd > cache_param->idle_send_timeout)) {
+		VSLb(h2->vsl, SLT_Debug,
+		     "H2: stream %u: Hit idle_send_timeout waiting for"
+		     " WINDOW_UPDATE", r2->stream);
+		r = 1;
+	}
+
+	if (r == 0 && r2->t_send != 0 &&
+	    now - r2->t_send > cache_param->send_timeout) {
+		VSLb(h2->vsl, SLT_Debug,
+		     "H2: stream %u: Hit send_timeout", r2->stream);
+		r = 1;
 	}
 
 	return (r);
 }
 
+static int
+h2_stream_tmo_unlocked(struct h2_sess *h2, const struct h2_req *r2)
+{
+	int r;
+
+	Lck_Lock(&h2->sess->mtx);
+	r = h2_stream_tmo(h2, r2, h2->sess->t_idle);
+	Lck_Unlock(&h2->sess->mtx);
+
+	return (r);
+}
+
 /*
  * This is the janitorial task of cleaning up any closed & refused
  * streams, and checking if the session is timed out.
@@ -1023,15 +1041,17 @@ h2_sweep(struct worker *wrk, struct h2_sess *h2)
 			break;
 		case H2_S_CLOS_REM:
 			if (!r2->scheduled) {
-				h2_tx_rst(wrk, h2, h2->req0, r2->stream,
+				H2_Send_Get(wrk, h2, h2->req0);
+				H2_Send_RST(wrk, h2, h2->req0, r2->stream,
 				    H2SE_REFUSED_STREAM);
+				H2_Send_Rel(h2, h2->req0);
 				h2_del_req(wrk, r2);
 				continue;
 			}
 			/* FALLTHROUGH */
 		case H2_S_CLOS_LOC:
 		case H2_S_OPEN:
-			if (h2_stream_tmo(h2, r2)) {
+			if (h2_stream_tmo_unlocked(h2, r2)) {
 				tmo = 1;
 				continue;
 			}
@@ -1080,8 +1100,8 @@ h2_rxframe(struct worker *wrk, struct h2_sess *h2)
 	h2->sess->t_idle = VTIM_real();
 	hs = HTC_RxStuff(h2->htc, h2_frame_complete,
 	    NULL, NULL, NAN,
-	    h2->sess->t_idle + cache_param->timeout_idle,
-	    h2->local_settings.max_frame_size + 9);
+	    h2->sess->t_idle + SESS_TMO(h2->sess, timeout_idle),
+	    NAN, h2->local_settings.max_frame_size + 9);
 	switch (hs) {
 	case HTC_S_COMPLETE:
 		break;
diff --git a/bin/varnishd/http2/cache_http2_send.c b/bin/varnishd/http2/cache_http2_send.c
index 638fea632..f0a8b96cd 100644
--- a/bin/varnishd/http2/cache_http2_send.c
+++ b/bin/varnishd/http2/cache_http2_send.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2016 Varnish Software AS
+ * Copyright (c) 2016-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -30,6 +30,7 @@
 #include "config.h"
 
 #include <sys/uio.h>
+#include <errno.h>
 
 #include "cache/cache_varnishd.h"
 
@@ -39,8 +40,52 @@
 #include "vend.h"
 #include "vtim.h"
 
+#define H2_SEND_HELD(h2, r2) (VTAILQ_FIRST(&(h2)->txqueue) == (r2))
+
+static int
+h2_cond_wait(pthread_cond_t *cond, struct h2_sess *h2, struct h2_req *r2)
+{
+	vtim_real now, when = 0.;
+	int r;
+
+	AN(cond);
+	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
+	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
+
+	Lck_AssertHeld(&h2->sess->mtx);
+
+	now = VTIM_real();
+	if (cache_param->idle_send_timeout > 0.)
+		when = now + cache_param->idle_send_timeout;
+
+	r = Lck_CondWait(cond, &h2->sess->mtx, when);
+	assert(r == 0 || r == ETIMEDOUT);
+
+	now = VTIM_real();
+	/* NB: when we grab idle_send_timeout before acquiring the session
+	 * lock we may time out, but once we wake up both send_timeout and
+	 * idle_send_timeout may have changed meanwhile. For this reason
+	 * h2_stream_tmo() may not log what timed out and we need to call
+	 * again with a magic NAN "now" that indicates to h2_stream_tmo()
+	 * that the stream reached the idle_send_timeout via the lock and
+	 * force it to log it.
+	 */
+	if (h2_stream_tmo(h2, r2, now))
+		r = ETIMEDOUT;
+	else if (r == ETIMEDOUT)
+		AN(h2_stream_tmo(h2, r2, NAN));
+
+	if (r == ETIMEDOUT) {
+		if (r2->error == NULL)
+			r2->error = H2SE_CANCEL;
+		return (-1);
+	}
+
+	return (0);
+}
+
 static void
-h2_send_get(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
+h2_send_get_locked(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 {
 	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
 	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
@@ -51,7 +96,7 @@ h2_send_get(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 		ASSERT_RXTHR(h2);
 	r2->wrk = wrk;
 	VTAILQ_INSERT_TAIL(&h2->txqueue, r2, tx_list);
-	while (VTAILQ_FIRST(&h2->txqueue) != r2)
+	while (!H2_SEND_HELD(h2, r2))
 		AZ(Lck_CondWait(&wrk->cond, &h2->sess->mtx, 0));
 	r2->wrk = NULL;
 }
@@ -64,18 +109,18 @@ H2_Send_Get(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
 
 	Lck_Lock(&h2->sess->mtx);
-	h2_send_get(wrk, h2, r2);
+	h2_send_get_locked(wrk, h2, r2);
 	Lck_Unlock(&h2->sess->mtx);
 }
 
 static void
-h2_send_rel(struct h2_sess *h2, const struct h2_req *r2)
+h2_send_rel_locked(struct h2_sess *h2, const struct h2_req *r2)
 {
 	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
 	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
 
 	Lck_AssertHeld(&h2->sess->mtx);
-	assert(VTAILQ_FIRST(&h2->txqueue) == r2);
+	AN(H2_SEND_HELD(h2, r2));
 	VTAILQ_REMOVE(&h2->txqueue, r2, tx_list);
 	r2 = VTAILQ_FIRST(&h2->txqueue);
 	if (r2 != NULL) {
@@ -91,7 +136,7 @@ H2_Send_Rel(struct h2_sess *h2, const struct h2_req *r2)
 	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
 
 	Lck_Lock(&h2->sess->mtx);
-	h2_send_rel(h2, r2);
+	h2_send_rel_locked(h2, r2);
 	Lck_Unlock(&h2->sess->mtx);
 }
 
@@ -109,7 +154,7 @@ h2_mk_hdr(uint8_t *hdr, h2_frame ftyp, uint8_t flags,
 }
 
 /*
- * This is the "raw" frame sender, all per stream accounting and
+ * This is the "raw" frame sender, all per-stream accounting and
  * prioritization must have happened before this is called, and
  * the session mtx must be held.
  */
@@ -217,15 +262,14 @@ h2_do_window(struct worker *wrk, struct h2_req *r2,
 	Lck_Lock(&h2->sess->mtx);
 	if (r2->t_window <= 0 || h2->req0->t_window <= 0) {
 		r2->t_winupd = VTIM_real();
-		h2_send_rel(h2, r2);
+		h2_send_rel_locked(h2, r2);
 		while (r2->t_window <= 0 && h2_errcheck(r2, h2) == 0) {
 			r2->cond = &wrk->cond;
-			AZ(Lck_CondWait(r2->cond, &h2->sess->mtx, 0));
+			(void)h2_cond_wait(r2->cond, h2, r2);
 			r2->cond = NULL;
 		}
-		while (h2->req0->t_window <= 0 && h2_errcheck(r2, h2) == 0) {
-			AZ(Lck_CondWait(h2->winupd_cond, &h2->sess->mtx, 0));
-		}
+		while (h2->req0->t_window <= 0 && h2_errcheck(r2, h2) == 0)
+			(void)h2_cond_wait(h2->winupd_cond, h2, r2);
 
 		if (h2_errcheck(r2, h2) == 0) {
 			w = h2_win_limit(r2, h2);
@@ -234,7 +278,7 @@ h2_do_window(struct worker *wrk, struct h2_req *r2,
 			h2_win_charge(r2, h2, w);
 			assert (w > 0);
 		}
-		h2_send_get(wrk, h2, r2);
+		h2_send_get_locked(wrk, h2, r2);
 	}
 
 	if (w == 0 && h2_errcheck(r2, h2) == 0) {
@@ -256,9 +300,9 @@ h2_do_window(struct worker *wrk, struct h2_req *r2,
  * XXX: priority
  */
 
-void
-H2_Send(struct worker *wrk, struct h2_req *r2,
-    h2_frame ftyp, uint8_t flags, uint32_t len, const void *ptr)
+static void
+h2_send(struct worker *wrk, struct h2_req *r2, h2_frame ftyp, uint8_t flags,
+    uint32_t len, const void *ptr, uint64_t *counter)
 {
 	struct h2_sess *h2;
 	uint32_t mfs, tf;
@@ -270,8 +314,9 @@ H2_Send(struct worker *wrk, struct h2_req *r2,
 	h2 = r2->h2sess;
 	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
 	assert(len == 0 || ptr != NULL);
+	AN(counter);
 
-	assert(VTAILQ_FIRST(&h2->txqueue) == r2);
+	AN(H2_SEND_HELD(h2, r2));
 
 	if (h2_errcheck(r2, h2))
 		return;
@@ -285,19 +330,28 @@ H2_Send(struct worker *wrk, struct h2_req *r2,
 
 	Lck_Lock(&h2->sess->mtx);
 	mfs = h2->remote_settings.max_frame_size;
+	if (r2->counted && (
+	    (ftyp == H2_F_HEADERS && (flags & H2FF_HEADERS_END_STREAM)) ||
+	    (ftyp == H2_F_DATA && (flags & H2FF_DATA_END_STREAM)) ||
+	    ftyp == H2_F_RST_STREAM
+	    )) {
+		assert(h2->open_streams > 0);
+		h2->open_streams--;
+		r2->counted = 0;
+	}
 	Lck_Unlock(&h2->sess->mtx);
 
 	if (ftyp->respect_window) {
-		tf = h2_do_window(wrk, r2, h2,
-				  (len > mfs) ? mfs : len);
+		tf = h2_do_window(wrk, r2, h2, (len > mfs) ? mfs : len);
 		if (h2_errcheck(r2, h2))
 			return;
-		assert(VTAILQ_FIRST(&h2->txqueue) == r2);
+		AN(H2_SEND_HELD(h2, r2));
 	} else
 		tf = mfs;
 
 	if (len <= tf) {
 		H2_Send_Frame(wrk, h2, ftyp, flags, len, r2->stream, ptr);
+		*counter += len;
 	} else {
 		AN(ptr);
 		p = ptr;
@@ -309,10 +363,10 @@ H2_Send(struct worker *wrk, struct h2_req *r2,
 				tf = mfs;
 			if (ftyp->respect_window && p != ptr) {
 				tf = h2_do_window(wrk, r2, h2,
-						  (len > mfs) ? mfs : len);
+					(len > mfs) ? mfs : len);
 				if (h2_errcheck(r2, h2))
 					return;
-				assert(VTAILQ_FIRST(&h2->txqueue) == r2);
+				AN(H2_SEND_HELD(h2, r2));
 			}
 			if (tf < len) {
 				H2_Send_Frame(wrk, h2, ftyp,
@@ -321,13 +375,49 @@ H2_Send(struct worker *wrk, struct h2_req *r2,
 				if (ftyp->respect_window)
 					assert(tf == len);
 				tf = len;
-				H2_Send_Frame(wrk, h2, ftyp,
-				    final_flags, tf, r2->stream, p);
+				H2_Send_Frame(wrk, h2, ftyp, final_flags, tf,
+					r2->stream, p);
 				flags = 0;
 			}
 			p += tf;
 			len -= tf;
+			*counter += tf;
 			ftyp = ftyp->continuation;
+			flags &= ftyp->flags;
+			final_flags &= ftyp->flags;
 		} while (!h2->error && len > 0);
 	}
 }
+
+void
+H2_Send_RST(struct worker *wrk, struct h2_sess *h2, const struct h2_req *r2,
+    uint32_t stream, h2_error h2e)
+{
+	char b[4];
+
+	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
+	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
+	AN(H2_SEND_HELD(h2, r2));
+
+	Lck_Lock(&h2->sess->mtx);
+	VSLb(h2->vsl, SLT_Debug, "H2: stream %u: %s", stream, h2e->txt);
+	Lck_Unlock(&h2->sess->mtx);
+	vbe32enc(b, h2e->val);
+
+	H2_Send_Frame(wrk, h2, H2_F_RST_STREAM, 0, sizeof b, stream, b);
+}
+
+void
+H2_Send(struct worker *wrk, struct h2_req *r2, h2_frame ftyp, uint8_t flags,
+    uint32_t len, const void *ptr, uint64_t *counter)
+{
+	uint64_t dummy_counter;
+
+	if (counter == NULL)
+		counter = &dummy_counter;
+
+	h2_send(wrk, r2, ftyp, flags, len, ptr, counter);
+
+	if (h2_errcheck(r2, r2->h2sess) == H2SE_CANCEL)
+		H2_Send_RST(wrk, r2->h2sess, r2, r2->stream, H2SE_CANCEL);
+}
diff --git a/bin/varnishd/http2/cache_http2_session.c b/bin/varnishd/http2/cache_http2_session.c
index 246400de7..140007ffc 100644
--- a/bin/varnishd/http2/cache_http2_session.c
+++ b/bin/varnishd/http2/cache_http2_session.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2016 Varnish Software AS
+ * Copyright (c) 2016-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -53,21 +53,21 @@ static const char H2_prism[24] = {
 };
 
 static size_t
-h2_enc_settings(const struct h2_settings *h2s, uint8_t *buf, size_t n)
+h2_enc_settings(const struct h2_settings *h2s, uint8_t *buf, ssize_t n)
 {
-	size_t len = 0;
+	uint8_t *p = buf;
 
 #define H2_SETTING(U,l,v,d,...)				\
 	if (h2s->l != d) {				\
-		len += 6;				\
-		assert(len <= n);			\
-		vbe16enc(buf, v);			\
-		buf += 2;				\
-		vbe32enc(buf, h2s->l);			\
-		buf += 4;				\
+		n -= 6;					\
+		assert(n >= 0);				\
+		vbe16enc(p, v);				\
+		p += 2;					\
+		vbe32enc(p, h2s->l);			\
+		p += 4;					\
 	}
 #include "tbl/h2_settings.h"
-	return (len);
+	return (p - buf);
 }
 
 static const struct h2_settings H2_proto_settings = {
@@ -151,7 +151,7 @@ h2_del_sess(struct worker *wrk, struct h2_sess *h2, enum sess_close reason)
 
 	VHT_Fini(h2->dectbl);
 	AZ(pthread_cond_destroy(h2->winupd_cond));
-	req = h2->srq;
+	TAKE_OBJ_NOTNULL(req, &h2->srq, REQ_MAGIC);
 	AZ(req->ws->r);
 	sp = h2->sess;
 	Req_Cleanup(sp, wrk, req);
@@ -242,7 +242,7 @@ h2_ou_rel(struct worker *wrk, struct req *req)
 	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
 	AN (req->vcl);
 	VCL_Rel(&req->vcl);
-	Req_AcctLogCharge(wrk->stats, req);
+	Req_Cleanup(req->sp, wrk, req);
 	Req_Release(req);
 	return (0);
 }
@@ -293,7 +293,7 @@ h2_ou_session(struct worker *wrk, struct h2_sess *h2,
 
 	/* Wait for PRISM response */
 	hs = HTC_RxStuff(h2->htc, H2_prism_complete,
-	    NULL, NULL, NAN, h2->sess->t_idle + cache_param->timeout_idle,
+	    NULL, NULL, NAN, h2->sess->t_idle + cache_param->timeout_idle, NAN,
 	    sizeof H2_prism);
 	if (hs != HTC_S_COMPLETE) {
 		VSLb(h2->vsl, SLT_Debug, "H2: No/Bad OU PRISM (hs=%d)", hs);
@@ -301,7 +301,12 @@ h2_ou_session(struct worker *wrk, struct h2_sess *h2,
 		h2_del_req(wrk, r2);
 		return (0);
 	}
-	XXXAZ(Pool_Task(wrk->pool, &req->task, TASK_QUEUE_REQ));
+	if (Pool_Task(wrk->pool, &req->task, TASK_QUEUE_REQ)) {
+		r2->scheduled = 0;
+		h2_del_req(wrk, r2);
+		VSLb(h2->vsl, SLT_Debug, "H2: No Worker-threads");
+		return (0);
+	}
 	return (1);
 }
 
diff --git a/bin/varnishd/proxy/cache_proxy_proto.c b/bin/varnishd/proxy/cache_proxy_proto.c
index 1def50ebe..9232254d3 100644
--- a/bin/varnishd/proxy/cache_proxy_proto.c
+++ b/bin/varnishd/proxy/cache_proxy_proto.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2015 Varnish Software AS
+ * Copyright (c) 2015-2019 Varnish Software AS
  * Copyright (c) 2018 GANDI SAS
  * All rights reserved.
  *
@@ -530,7 +530,7 @@ vpx_new_session(struct worker *wrk, void *arg)
 
 	HTC_RxInit(req->htc, req->ws);
 	hs = HTC_RxStuff(req->htc, vpx_complete,
-	    NULL, NULL, NAN, sp->t_idle + cache_param->timeout_idle,
+	    NULL, NULL, NAN, sp->t_idle + cache_param->timeout_idle, NAN,
 	    1024);			// XXX ?
 	if (hs != HTC_S_COMPLETE) {
 		Req_Release(req);
diff --git a/bin/varnishd/waiter/cache_waiter.c b/bin/varnishd/waiter/cache_waiter.c
index fc316424b..06cc22bfc 100644
--- a/bin/varnishd/waiter/cache_waiter.c
+++ b/bin/varnishd/waiter/cache_waiter.c
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 2006 Verdens Gang AS
- * Copyright (c) 2006-2015 Varnish Software AS
+ * Copyright (c) 2006-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -133,7 +133,6 @@ Wait_Enter(const struct waiter *w, struct waited *wp)
 	CHECK_OBJ_NOTNULL(wp, WAITED_MAGIC);
 	assert(wp->fd > 0);			// stdin never comes here
 	AN(wp->func);
-	AN(wp->tmo);
 	wp->idx = BINHEAP_NOIDX;
 	return (w->impl->enter(w->priv, wp));
 }
diff --git a/bin/varnishd/waiter/waiter.h b/bin/varnishd/waiter/waiter.h
index 4a0d8a7d5..81b1f3923 100644
--- a/bin/varnishd/waiter/waiter.h
+++ b/bin/varnishd/waiter/waiter.h
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 2006 Verdens Gang AS
- * Copyright (c) 2006-2011 Varnish Software AS
+ * Copyright (c) 2006-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -62,7 +62,7 @@ struct waited {
 	void			*priv1;
 	uintptr_t		priv2;
 	waiter_handle_f		*func;
-	volatile vtim_real	*tmo;
+	vtim_dur		tmo;
 	vtim_real		idle;
 };
 
diff --git a/bin/varnishd/waiter/waiter_priv.h b/bin/varnishd/waiter/waiter_priv.h
index 502d34961..dccbc7a71 100644
--- a/bin/varnishd/waiter/waiter_priv.h
+++ b/bin/varnishd/waiter/waiter_priv.h
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 2006 Verdens Gang AS
- * Copyright (c) 2006-2011 Varnish Software AS
+ * Copyright (c) 2006-2019 Varnish Software AS
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -63,15 +63,14 @@ Wait_Tmo(const struct waited *wp)
 {
 	CHECK_OBJ_ORNULL(wp, WAITED_MAGIC);
 	AN(wp->tmo);
-	return (*wp->tmo);
+	return (wp->tmo);
 }
 
 static inline double
 Wait_When(const struct waited *wp)
 {
 	CHECK_OBJ_ORNULL(wp, WAITED_MAGIC);
-	AN(wp->tmo);
-	return (wp->idle + *wp->tmo);
+	return (wp->idle + wp->tmo);
 }
 
 void Wait_Call(const struct waiter *, struct waited *,
diff --git a/bin/varnishtest/tests/b00068.vtc b/bin/varnishtest/tests/b00068.vtc
new file mode 100644
index 000000000..5bb1fee4e
--- /dev/null
+++ b/bin/varnishtest/tests/b00068.vtc
@@ -0,0 +1,35 @@
+varnishtest "Check timeout_idle"
+
+varnish v1 -arg "-p timeout_idle=1" -vcl {
+	backend dummy { .host = "${bad_ip}"; }
+
+	sub vcl_deliver {
+		if (req.url == "/sess") {
+			set sess.timeout_idle = 2s;
+		}
+	}
+	sub vcl_backend_error {
+		set beresp.status = 200;
+		set beresp.ttl = 1h;
+	}
+} -start
+
+client c1 {
+	txreq
+	rxresp
+	delay 0.2
+	txreq
+	rxresp
+	delay 1.2
+	expect_close
+} -run
+
+client c1 {
+	txreq -url "/sess"
+	rxresp
+	delay 1.2
+	txreq
+	rxresp
+	delay 2.2
+	expect_close
+} -run
diff --git a/bin/varnishtest/tests/r02395.vtc b/bin/varnishtest/tests/r02395.vtc
new file mode 100644
index 000000000..99461e16c
--- /dev/null
+++ b/bin/varnishtest/tests/r02395.vtc
@@ -0,0 +1,22 @@
+varnishtest "Test between_bytes_timeout works fetching headers"
+
+server s1 {
+        rxreq
+        send "HTTP/1.0 "
+        delay 2
+} -start
+
+varnish v1 -vcl+backend {
+        sub vcl_recv { return (pass); }
+        sub vcl_backend_fetch { set bereq.between_bytes_timeout = 1s; }
+} -start
+
+varnish v1 -cliok "param.set debug +syncvsl"
+
+client c1 {
+        txreq
+        rxresp
+        expect resp.status == 503
+} -run
+
+server s1 -wait
diff --git a/bin/varnishtest/tests/r02923.vtc b/bin/varnishtest/tests/r02923.vtc
new file mode 100644
index 000000000..2fa9f65d1
--- /dev/null
+++ b/bin/varnishtest/tests/r02923.vtc
@@ -0,0 +1,99 @@
+varnishtest "race cond in max_concurrent_streams when request after receiving RST_STREAM"
+
+barrier b1 sock 2
+barrier b2 sock 2
+barrier b3 sock 2
+barrier b4 sock 2
+barrier b5 sock 3
+
+server s1 {
+	rxreq
+	expect req.url == /nosync
+	txresp
+	rxreq
+	expect req.url == /sync
+	txresp
+} -start
+
+varnish v1 -cliok "param.set feature +http2"
+varnish v1 -cliok "param.set debug +syncvsl"
+varnish v1 -cliok "param.set h2_max_concurrent_streams 3"
+
+varnish v1 -vcl+backend {
+	import vtc;
+
+	sub vcl_recv {
+	}
+
+	sub vcl_backend_fetch {
+		if(bereq.url ~ "/sync"){
+			vtc.barrier_sync("${b1_sock}");
+			vtc.barrier_sync("${b5_sock}");
+		}
+	}
+} -start
+
+client c1 {
+	txpri
+	stream 0 rxsettings -run
+
+	stream 1 {
+		txreq -url /sync
+		rxresp
+		expect resp.status == 200
+	} -start
+
+	stream 3 {
+		barrier b1 sync
+		delay .5
+		# Goes on waiting list
+		txreq -url /sync
+		delay .5
+		txrst -err 0x8
+		delay .5
+		barrier b2 sync
+	} -start
+
+	stream 5 {
+		barrier b2 sync
+		txreq -url /nosync
+		delay .5
+		rxresp
+		delay .5
+		expect resp.status == 200
+		barrier b3 sync
+	} -start
+
+	stream 7 {
+		barrier b3 sync
+		# Goes on waiting list
+		txreq -url /sync
+		delay .5
+		txrst -err 0x8
+		delay .5
+		barrier b4 sync
+	} -start
+
+	stream 9 {
+		barrier b4 sync
+		# Goes on waiting list
+		txreq -url /sync
+		delay .5
+		barrier b5 sync
+		delay .5
+		rxresp
+		delay .5
+		expect resp.status == 200
+	} -start
+
+	stream 11 {
+		barrier b5 sync
+		txreq -url /sync
+		delay .5
+		rxresp
+		delay .5
+		expect resp.status == 200
+	} -start
+
+
+} -run
diff --git a/bin/varnishtest/tests/r02934.vtc b/bin/varnishtest/tests/r02934.vtc
new file mode 100644
index 000000000..923baf47c
--- /dev/null
+++ b/bin/varnishtest/tests/r02934.vtc
@@ -0,0 +1,27 @@
+varnishtest "Bug in CONTINUATION flags when no sendbody"
+
+server s1 {
+	rxreq
+	expect req.proto == HTTP/1.1
+	txresp -hdr "Content-Type: text/plain" -hdrlen Foo 100
+
+} -start
+
+varnish v1 -cliok "param.set feature +http2"
+varnish v1 -cliok "param.set debug +syncvsl"
+varnish v1 -cliok "param.set debug +h2_nocheck"
+
+varnish v1 -vcl+backend { } -start
+
+client c1 {
+	stream 0 {
+		txsettings -framesize 64
+		rxsettings
+	} -run
+	stream 1 {
+		txreq -req HEAD
+		rxresp
+		expect resp.status == 200
+		expect resp.http.content-Type == "text/plain"
+	} -run
+} -run
diff --git a/bin/varnishtest/tests/r02937.vtc b/bin/varnishtest/tests/r02937.vtc
new file mode 100644
index 000000000..8a2d00d58
--- /dev/null
+++ b/bin/varnishtest/tests/r02937.vtc
@@ -0,0 +1,25 @@
+varnishtest "#2937: Panic on OU pool failure"
+
+server s1 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {} -start
+
+varnish v1 -cliok "param.set feature +http2"
+varnish v1 -cliok "debug.reqpool.fail F"
+
+client c1 {
+	send "GET / HTTP/1.1\r\n"
+	send "Host: foo\r\n"
+	send "Upgrade: h2c\r\n"
+	send "HTTP2-Settings: AAMAAABkAAQAAP__\r\n"
+	send "\r\n"
+	rxresp
+	expect resp.status == 101
+	expect resp.http.upgrade == h2c
+	expect resp.http.connection == Upgrade
+	txpri
+	expect_close
+} -run
diff --git a/bin/varnishtest/tests/t02012.vtc b/bin/varnishtest/tests/t02012.vtc
index 5319453e7..3f7ec8092 100644
--- a/bin/varnishtest/tests/t02012.vtc
+++ b/bin/varnishtest/tests/t02012.vtc
@@ -12,7 +12,7 @@ server s1 {
 
 varnish v1 -cliok "param.set feature +http2"
 varnish v1 -cliok "param.set debug +syncvsl"
-varnish v1 -cliok "param.set h2_max_concurrent_streams 3"
+varnish v1 -cliok "param.set h2_max_concurrent_streams 2"
 
 varnish v1 -vcl+backend {
 	import vtc;
diff --git a/bin/varnishtest/tests/t02015.vtc b/bin/varnishtest/tests/t02015.vtc
new file mode 100644
index 000000000..3da5c24d4
--- /dev/null
+++ b/bin/varnishtest/tests/t02015.vtc
@@ -0,0 +1,49 @@
+varnishtest "h2 ReqAcct"
+
+server s1 {
+	rxreq
+	txresp -bodylen 12345
+} -start
+
+varnish v1 -cliok "param.set feature +http2"
+varnish v1 -vcl+backend "" -start
+
+client c1 {
+	txpri
+
+	stream 0 {
+		rxsettings
+		expect settings.ack == false
+		txsettings -ack
+		txsettings -winsize 1000
+		rxsettings
+		expect settings.ack == true
+	} -run
+
+	stream 1 {
+		txreq -hdr stream 1
+		rxhdrs
+		rxdata
+		txwinup -size 11345
+		rxdata
+	} -run
+
+	stream 3 {
+		txreq -hdr stream 3
+		rxhdrs
+		rxdata
+		txrst
+	} -run
+} -run
+
+shell {
+	varnishncsa -n ${v1_name} -F '%{VSL:ReqAcct[5]}x' -d \
+		-q 'ReqHeader:stream == 1' |
+	grep 12345
+}
+
+shell {
+	varnishncsa -n ${v1_name} -F '%{VSL:ReqAcct[5]}x' -d \
+		-q 'ReqHeader:stream == 3' |
+	grep 1000
+}
diff --git a/bin/varnishtest/tests/t02016.vtc b/bin/varnishtest/tests/t02016.vtc
new file mode 100644
index 000000000..29ffea354
--- /dev/null
+++ b/bin/varnishtest/tests/t02016.vtc
@@ -0,0 +1,119 @@
+varnishtest "client h2 send timeouts"
+
+server s1 {
+	rxreq
+	txresp -bodylen 12345
+} -start
+
+varnish v1 -cliok "param.set feature +http2"
+varnish v1 -vcl+backend "" -start
+
+# coverage for send_timeout with c1
+
+varnish v1 -cliok "param.set send_timeout 1"
+
+logexpect l1 -v v1 {
+	expect * * Debug "Hit send_timeout"
+} -start
+
+client c1 {
+	txpri
+
+	stream 0 {
+		rxsettings
+		expect settings.ack == false
+		txsettings -ack
+		txsettings -winsize 1000
+		rxsettings
+		expect settings.ack == true
+	} -run
+
+	stream 1 {
+		txreq
+		rxhdrs
+		rxdata
+		# keep the stream idle for 2 seconds
+		delay 2
+		txwinup -size 256
+		# too late
+		rxrst
+		expect rst.err == CANCEL
+	} -run
+
+} -run
+
+logexpect l1 -wait
+
+# coverage for idle_send_timeout with c2
+
+varnish v1 -cliok "param.set idle_send_timeout 1"
+varnish v1 -cliok "param.reset send_timeout"
+
+logexpect l2 -v v1 {
+	expect * * Debug "Hit idle_send_timeout"
+} -start
+
+client c2 {
+	txpri
+
+	stream 0 {
+		rxsettings
+		expect settings.ack == false
+		txsettings -ack
+		txsettings -winsize 1000
+		rxsettings
+		expect settings.ack == true
+	} -run
+
+	stream 1 {
+		txreq
+		rxhdrs
+		rxdata
+		# keep the stream idle for 2 seconds
+		delay 2
+		rxrst
+		expect rst.err == CANCEL
+	} -run
+
+} -run
+
+logexpect l2 -wait
+
+# coverage for idle_send_timeout change with c3
+
+barrier b3 cond 2
+
+logexpect l3 -v v1 {
+	expect * * Debug "Hit idle_send_timeout"
+} -start
+
+client c3 {
+	txpri
+
+	stream 0 {
+		rxsettings
+		expect settings.ack == false
+		txsettings -ack
+		txsettings -winsize 1000
+		rxsettings
+		expect settings.ack == true
+	} -run
+
+	stream 1 {
+		txreq
+		rxhdrs
+		rxdata
+		# keep the stream idle for 2 seconds
+		barrier b3 sync
+		delay 2
+		rxrst
+		expect rst.err == CANCEL
+	} -run
+
+} -start
+
+barrier b3 sync
+varnish v1 -cliok "param.reset idle_send_timeout"
+
+client c3 -wait
+logexpect l3 -wait
diff --git a/doc/sphinx/reference/vcl_var.rst b/doc/sphinx/reference/vcl_var.rst
index e8c04d928..16a35106a 100644
--- a/doc/sphinx/reference/vcl_var.rst
+++ b/doc/sphinx/reference/vcl_var.rst
@@ -1185,6 +1185,17 @@ sess.xid	``VCL >= 4.1``
 
 	Unique ID of this session.
 
+sess.timeout_idle
+
+	Type: DURATION
+
+	Readable from: client
+
+	Writable from: client
+
+	Idle timeout for this session, defaults to the
+	``timeout_idle`` parameter, see :ref:`varnishd(1)`
+
 storage
 ~~~~~~~
 


More information about the varnish-commit mailing list