diff --git a/lib/context.c b/lib/context.c
index 83b356937fb0f1fa3599c3d2537ed875eb81f42f..1ed9e15d304df60d1441c2ead90423e44a6fedc6 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -504,11 +504,13 @@ static const struct lws_protocols protocols_dummy[] = {
 	/* first protocol must always be HTTP handler */
 
 	{
-		"http-only",		/* name */
-		lws_callback_http_dummy,		/* callback */
-		0,	/* per_session_data_size */
-		0,			/* max frame size / rx buffer */
-		0, NULL, 0
+		"http-only",			/* name */
+		lws_callback_http_dummy,	/* callback */
+		0,				/* per_session_data_size */
+		0,				/* rx_buffer_size */
+		0,				/* id */
+		NULL,				/* user */
+		0				/* tx_packet_size */
 	},
 	/*
 	 * the other protocols are provided by lws plugins
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index af8ad17228746393ec027c345618b3f9459f6320..e6ec7db89439ac49b56eb01fd16422d2716d4e6c 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -93,7 +93,7 @@ __lws_free_wsi(struct lws *wsi)
 	    wsi->user_space && !wsi->user_space_externally_allocated)
 		lws_free(wsi->user_space);
 
-	lws_free_set_NULL(wsi->rxflow_buffer);
+	lws_buflist_destroy_all_segments(&wsi->buflist_rxflow);
 	lws_free_set_NULL(wsi->trunc_alloc);
 	lws_free_set_NULL(wsi->ws);
 	lws_free_set_NULL(wsi->udp);
@@ -842,6 +842,10 @@ just_kill_connection:
 	__lws_remove_from_timeout_list(wsi);
 	lws_dll_lws_remove(&wsi->dll_hrtimer);
 
+	/* don't repeat event loop stuff */
+	if (wsi->told_event_loop_closed)
+		return;
+
 	/* checking return redundant since we anyway close */
 	if (wsi->desc.sockfd != LWS_SOCK_INVALID)
 		__remove_wsi_socket_from_fds(wsi);
@@ -849,7 +853,8 @@ just_kill_connection:
 		lws_same_vh_protocol_remove(wsi);
 
 	lwsi_set_state(wsi, LRS_DEAD_SOCKET);
-	lws_free_set_NULL(wsi->rxflow_buffer);
+	lws_buflist_destroy_all_segments(&wsi->buflist_rxflow);
+	lws_dll_lws_remove(&wsi->dll_rxflow);
 
 	if (wsi->role_ops->close_role)
 	    wsi->role_ops->close_role(pt, wsi);
@@ -952,6 +957,114 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *ca
 	lws_pt_unlock(pt);
 }
 
+/* lws_buflist */
+
+int
+lws_buflist_append_segment(struct lws_buflist **head, uint8_t *buf, size_t len)
+{
+	int first = !*head;
+	void *p;
+
+	assert(buf);
+	assert(len);
+
+	/* append at the tail */
+	while (*head)
+		head = &((*head)->next);
+
+	lwsl_info("%s: len %u\n", __func__, (uint32_t)len);
+
+	*head = (struct lws_buflist *)
+			lws_malloc(sizeof(**head) + len, __func__);
+	if (!*head) {
+		lwsl_err("%s: OOM\n", __func__);
+		return -1;
+	}
+
+	(*head)->len = len;
+	(*head)->pos = 0;
+	(*head)->next = NULL;
+
+	p = (void *)(*head)->buf;
+	memcpy(p, buf, len);
+
+	return first; /* returns 1 if first segment just created */
+}
+
+static int
+lws_buflist_destroy_segment(struct lws_buflist **head)
+{
+	struct lws_buflist *old = *head;
+
+	assert(*head);
+	*head = (*head)->next;
+	lws_free(old);
+
+	return !*head; /* returns 1 if last segment just destroyed */
+}
+
+void
+lws_buflist_destroy_all_segments(struct lws_buflist **head)
+{
+	struct lws_buflist *p = *head, *p1;
+
+	while (p) {
+		p1 = p->next;
+		lws_free(p);
+		p = p1;
+	}
+
+	*head = NULL;
+}
+
+size_t
+lws_buflist_next_segment_len(struct lws_buflist **head, uint8_t **buf)
+{
+	if (!*head) {
+		if (buf)
+			*buf = NULL;
+
+		return 0;
+	}
+
+	if (!(*head)->len && (*head)->next)
+		lws_buflist_destroy_segment(head);
+
+	if (!*head) {
+		if (buf)
+			*buf = NULL;
+
+		return 0;
+	}
+
+	assert((*head)->pos < (*head)->len);
+
+	if (buf)
+		*buf = (*head)->buf + (*head)->pos;
+
+	return (*head)->len - (*head)->pos;
+}
+
+int
+lws_buflist_use_segment(struct lws_buflist **head, size_t len)
+{
+	assert(*head);
+	assert(len);
+
+	assert((*head)->pos + len <= (*head)->len);
+
+	(*head)->pos += len;
+	if ((*head)->pos == (*head)->len)
+		lws_buflist_destroy_segment(head);
+
+	if (!*head)
+		return 0;
+
+	return (*head)->len;
+}
+
+/* ... */
+
 LWS_VISIBLE LWS_EXTERN const char *
 lws_get_urlarg_by_name(struct lws *wsi, const char *name, char *buf, int len)
 {
@@ -1486,6 +1599,11 @@ lws_rx_flow_control(struct lws *wsi, int _enable)
 	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
 	int en = _enable;
 
+	// h2 ignores rx flow control atm
+	if (lwsi_role_h2(wsi) || wsi->http2_substream ||
+	    lwsi_role_h2_ENCAPSULATION(wsi))
+		return 0; // !!!
+
 	lwsl_info("%s: %p 0x%x\n", __func__, wsi, _enable);
 
 	if (!(_enable & LWS_RXFLOW_REASON_APPLIES)) {
@@ -2103,6 +2221,11 @@ __lws_rx_flow_control(struct lws *wsi)
 {
 	struct lws *wsic = wsi->child_list;
 
+	// h2 ignores rx flow control atm
+	if (lwsi_role_h2(wsi) || wsi->http2_substream ||
+	    lwsi_role_h2_ENCAPSULATION(wsi))
+		return 0; // !!!
+
 	/* if he has children, do those if they were changed */
 	while (wsic) {
 		if (wsic->rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE)
@@ -2116,13 +2239,13 @@ __lws_rx_flow_control(struct lws *wsi)
 		return 0;
 
 	/* stuff is still buffered, not ready to really accept new input */
-	if (wsi->rxflow_buffer) {
+	if (lws_buflist_next_segment_len(&wsi->buflist_rxflow, NULL)) {
 		/* get ourselves called back to deal with stashed buffer */
 		lws_callback_on_writable(wsi);
 		return 0;
 	}
 
-	/* pending is cleared, we can change rxflow state */
+	/* now the pending is cleared, we can change rxflow state */
 
 	wsi->rxflow_change_to &= ~LWS_RXFLOW_PENDING_CHANGE;
 
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index 13b0984fdf15a66f6bb69f54be42da989c99d8eb..db614c069577ff5f158b961e51bbfc569356dd5f 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -2380,8 +2380,8 @@ struct lws_protocols {
 	 * be able to consume it all without having to return to the event
 	 * loop.  That is supported in lws.
 	 *
-	 * If .tx_packet_size is 0, this also controls how much may be sent at once
-	 * for backwards compatibility.
+	 * If .tx_packet_size is 0, this also controls how much may be sent at
+	 * once for backwards compatibility.
 	 */
 	unsigned int id;
 	/**< ignored by lws, but useful to contain user information bound
@@ -5724,6 +5724,60 @@ lws_dll_lws_remove(struct lws_dll_lws *_a)
 	} \
 }
 
+struct lws_buflist;
+
+/**
+ * lws_buflist_append_segment(): add buffer to buflist at head
+ *
+ * \param head: list head
+ * \param buf: buffer to stash
+ * \param len: length of buffer to stash
+ *
+ * Returns -1 on OOM, 1 if this was the first segment on the list, and 0 if
+ * it was a subsequent segment.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_buflist_append_segment(struct lws_buflist **head, uint8_t *buf, size_t len);
+/**
+ * lws_buflist_next_segment_len(): number of bytes left in current segment
+ *
+ * \param head: list head
+ * \param buf: if non-NULL, *buf is written with the address of the start of
+ *		the remaining data in the segment
+ *
+ * Returns the number of bytes left in the current segment.  0 indicates
+ * that the buflist is empty (there are no segments on the buflist).
+ */
+LWS_VISIBLE LWS_EXTERN size_t
+lws_buflist_next_segment_len(struct lws_buflist **head, uint8_t **buf);
+/**
+ * lws_buflist_use_segment(): remove len bytes from the current segment
+ *
+ * \param head: list head
+ * \param len: number of bytes to mark as used
+ *
+ * If len is less than the remaining length of the current segment, the position
+ * in the current segment is simply advanced and it returns.
+ *
+ * If len uses up the remaining length of the current segment, then the segment
+ * is deleted and the list head moves to the next segment if any.
+ *
+ * Returns the number of bytes left in the current segment.  0 indicates
+ * that the buflist is empty (there are no segments on the buflist).
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_buflist_use_segment(struct lws_buflist **head, size_t len);
+/**
+ * lws_buflist_destroy_all_segments(): free all segments on the list
+ *
+ * \param head: list head
+ *
+ * This frees everything on the list unconditionally.  *head is always
+ * NULL after this.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_buflist_destroy_all_segments(struct lws_buflist **head);
+
 /**
  * lws_ptr_diff(): helper to report distance between pointers as an int
  *
diff --git a/lib/pollfd.c b/lib/pollfd.c
index d9029c44670da6628ec20180f39b7e77478367ea..18474b7edace6997d0f8574432660119592d64a8 100644
--- a/lib/pollfd.c
+++ b/lib/pollfd.c
@@ -377,7 +377,7 @@ __lws_change_pollfd(struct lws *wsi, int _and, int _or)
 
 	if (!wsi || (!wsi->protocol && !wsi->event_pipe) ||
 	    wsi->position_in_fds_table < 0)
-		return 1;
+		return 0;
 
 	context = lws_get_context(wsi);
 	if (!context)
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index c1a2c3025d8e4f801fb1a3dea48df60c53c74a8b..8f578733519bd9160bfd08ed1caaf91dad2455a4 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -524,7 +524,7 @@ enum lwsi_role {
 #define lwsi_role(wsi) (wsi->wsistate & LWSI_ROLE_MASK)
 #if !defined (_DEBUG)
 #define lwsi_set_role(wsi, role) wsi->wsistate = \
-			(wsi->wsistate & (~LWSI_ROLE_MASK)) | role
+				(wsi->wsistate & (~LWSI_ROLE_MASK)) | role
 #else
 void lwsi_set_role(struct lws *wsi, lws_wsi_state_t role);
 #endif
@@ -959,6 +959,7 @@ struct lws_context_per_thread {
 	struct lws *tx_draining_ext_list;
 	struct lws_dll_lws dll_head_timeout;
 	struct lws_dll_lws dll_head_hrtimer;
+	struct lws_dll_lws dll_head_rxflow;
 #if defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT)
 	struct lws_context *context;
 #endif
@@ -2018,6 +2019,15 @@ struct lws_access_log {
 };
 #endif
 
+struct lws_buflist {
+	struct lws_buflist *next;
+
+	size_t len;
+	size_t pos;
+
+	uint8_t buf[1]; /* true length of this is set by the oversize malloc */
+};
+
 #define lws_wsi_is_udp(___wsi) (!!___wsi->udp)
 
 struct lws {
@@ -2056,6 +2066,7 @@ struct lws {
 
 	struct lws_dll_lws dll_timeout;
 	struct lws_dll_lws dll_hrtimer;
+	struct lws_dll_lws dll_rxflow;
 #if defined(LWS_WITH_PEER_LIMITS)
 	struct lws_peer *peer;
 #endif
@@ -2072,8 +2083,9 @@ struct lws {
 #endif
 	void *user_space;
 	void *opaque_parent_data;
-	/* rxflow handling */
-	unsigned char *rxflow_buffer;
+
+	struct lws_buflist *buflist_rxflow;
+
 	/* truncated send handling */
 	unsigned char *trunc_alloc; /* non-NULL means buffering in progress */
 
@@ -2112,8 +2124,6 @@ struct lws {
 
 	/* ints */
 	int position_in_fds_table;
-	uint32_t rxflow_len;
-	uint32_t rxflow_pos;
 	uint32_t preamble_rx_len;
 	unsigned int trunc_alloc_len; /* size of malloc */
 	unsigned int trunc_offset; /* where we are in terms of spilling */
diff --git a/lib/roles/h1/client-h1.c b/lib/roles/h1/client-h1.c
index 1229aabb34630d462058051bebfabe6ad29b21b2..e3b8607f0ef9183b700f830477c0c741f3436e26 100644
--- a/lib/roles/h1/client-h1.c
+++ b/lib/roles/h1/client-h1.c
@@ -53,8 +53,9 @@ lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len)
 			continue;
 		}
 		/* account for what we're using in rxflow buffer */
-		if (wsi->rxflow_buffer)
-			wsi->rxflow_pos++;
+		if (lws_buflist_next_segment_len(&wsi->buflist_rxflow, NULL) &&
+		    !lws_buflist_use_segment(&wsi->buflist_rxflow, 1))
+			lws_dll_lws_remove(&wsi->dll_rxflow);
 
 		if (lws_client_rx_sm(wsi, *(*buf)++)) {
 			lwsl_debug("client_rx_sm exited\n");
diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c
index 8563204a596a0b7a325c7eada67a39d222abada2..b68152c016bd503a04c347980081fe84e6112b8f 100644
--- a/lib/roles/h2/http2.c
+++ b/lib/roles/h2/http2.c
@@ -2186,10 +2186,9 @@ lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
 		}
 
 		/* account for what we're using in rxflow buffer */
-		if (wsi->rxflow_buffer) {
-			wsi->rxflow_pos += (int)body_chunk_len;
-			assert(wsi->rxflow_pos <= wsi->rxflow_len);
-		}
+		if (lws_buflist_next_segment_len(&wsi->buflist_rxflow, NULL) &&
+		    !lws_buflist_use_segment(&wsi->buflist_rxflow, body_chunk_len))
+			lws_dll_lws_remove(&wsi->dll_rxflow);
 
 		buf += body_chunk_len;
 		len -= body_chunk_len;
diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c
index 148c6cb37a1996d0ce69d991eed9c1f085fab3e8..5c9a475b5864dd128e8e0bc6a636f8ed96743095 100644
--- a/lib/roles/h2/ops-h2.c
+++ b/lib/roles/h2/ops-h2.c
@@ -175,14 +175,10 @@ rops_handle_POLLIN_h2(struct lws_context_per_thread *pt, struct lws *wsi,
 	/* 3: RX Flowcontrol buffer / h2 rx scratch needs to be drained
 	 */
 
-	if (wsi->rxflow_buffer) {
-		lwsl_info("draining rxflow (len %d)\n",
-			wsi->rxflow_len - wsi->rxflow_pos);
-		assert(wsi->rxflow_pos < wsi->rxflow_len);
-		/* well, drain it */
-		eff_buf.token = (char *)wsi->rxflow_buffer +
-					wsi->rxflow_pos;
-		eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos;
+	eff_buf.token_len = lws_buflist_next_segment_len(&wsi->buflist_rxflow,
+						(uint8_t **)&eff_buf.token);
+	if (eff_buf.token_len) {
+		lwsl_info("draining rxflow (len %d)\n", eff_buf.token_len);
 		draining_flow = 1;
 		goto drain;
 	}
@@ -359,10 +355,9 @@ drain:
 		goto read;
 	}
 
-	if (draining_flow && wsi->rxflow_buffer &&
-	    wsi->rxflow_pos == wsi->rxflow_len) {
+	if (draining_flow && /* were draining, now nothing left */
+	    !lws_buflist_next_segment_len(&wsi->buflist_rxflow, NULL)) {
 		lwsl_info("%s: %p flow buf: drained\n", __func__, wsi);
-		lws_free_set_NULL(wsi->rxflow_buffer);
 		/* having drained the rxflow buffer, can rearm POLLIN */
 #ifdef LWS_NO_SERVER
 		n =
@@ -766,21 +761,41 @@ rops_callback_on_writable_h2(struct lws *wsi)
 	return 0;
 }
 
+static void
+lws_h2_dump_waiting_children(struct lws *wsi)
+{
+#if defined(_DEBUG)
+	lwsl_info("%s: %p: children waiting for POLLOUT service:\n",
+		  __func__, wsi);
+
+	wsi = wsi->h2.child_list;
+	while (wsi) {
+		if (wsi->h2.requested_POLLOUT)
+			lwsl_info("  * %p %s\n", wsi, wsi->protocol->name);
+		else
+			lwsl_info("    %p %s\n", wsi, wsi->protocol->name);
+
+		wsi = wsi->h2.sibling_list;
+	}
+#endif
+}
+
+/*
+ * we are the 'network wsi' for potentially many muxed child wsi with
+ * no network connection of their own, who have to use us for all their
+ * network actions.  So we use a round-robin scheme to share out the
+ * POLLOUT notifications to our children.
+ *
+ * But because any child could exhaust the socket's ability to take
+ * writes, we can only let one child get notified each time.
+ *
+ * In addition children may be closed / deleted / added between POLLOUT
+ * notifications, so we can't hold pointers
+ */
+
 static int
 rops_perform_user_POLLOUT_h2(struct lws *wsi)
 {
-	/*
-	 * we are the 'network wsi' for potentially many muxed child wsi with
-	 * no network connection of their own, who have to use us for all their
-	 * network actions.  So we use a round-robin scheme to share out the
-	 * POLLOUT notifications to our children.
-	 *
-	 * But because any child could exhaust the socket's ability to take
-	 * writes, we can only let one child get notified each time.
-	 *
-	 * In addition children may be closed / deleted / added between POLLOUT
-	 * notifications, so we can't hold pointers
-	 */
 	struct lws **wsi2, *wsi2a;
 	int write_type = LWS_WRITE_PONG, n;
 
@@ -792,16 +807,7 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
 		return 0;
 	}
 
-	lwsl_info("%s: %p: children waiting for POLLOUT service:\n", __func__, wsi);
-	wsi2a = wsi->h2.child_list;
-	while (wsi2a) {
-		if (wsi2a->h2.requested_POLLOUT)
-			lwsl_info("  * %p %s\n", wsi2a, wsi2a->protocol->name);
-		else
-			lwsl_info("    %p %s\n", wsi2a, wsi2a->protocol->name);
-
-		wsi2a = wsi2a->h2.sibling_list;
-	}
+	lws_h2_dump_waiting_children(wsi);
 
 	wsi2 = &wsi->h2.child_list;
 	if (!*wsi2)
@@ -842,7 +848,8 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
 		}
 
 		w->h2.requested_POLLOUT = 0;
-		lwsl_info("%s: child %p (state %d)\n", __func__, w, lwsi_state(w));
+		lwsl_info("%s: child %p (wsistate 0x%x)\n", __func__, w,
+			  w->wsistate);
 
 		/* if we arrived here, even by looping, we checked choked */
 		w->could_have_pending = 0;
@@ -855,7 +862,8 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
 				         strlen(w->h2.pending_status_body +
 					        LWS_PRE), LWS_WRITE_HTTP_FINAL);
 			lws_free_set_NULL(w->h2.pending_status_body);
-			lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream 1");
+			lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+					   "h2 end stream 1");
 			wa = &wsi->h2.child_list;
 			goto next_child;
 		}
@@ -892,7 +900,8 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
 			 */
 			if (n || w->h2.send_END_STREAM) {
 				lwsl_info("closing stream after h2 action\n");
-				lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream");
+				lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+						   "h2 end stream");
 				wa = &wsi->h2.child_list;
 			}
 
@@ -918,7 +927,8 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
 			 */
 			if (n < 0 || w->h2.send_END_STREAM) {
 				lwsl_debug("Closing POLLOUT child %p\n", w);
-				lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream file");
+				lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+						   "h2 end stream file");
 				wa = &wsi->h2.child_list;
 				goto next_child;
 			}
@@ -944,14 +954,16 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
 			if (n >= 0) {
 				lwsi_set_state(w, LRS_AWAITING_CLOSE_ACK);
 				lws_set_timeout(w, PENDING_TIMEOUT_CLOSE_ACK, 5);
-				lwsl_debug("sent close indication, awaiting ack\n");
+				lwsl_debug("sent close frame, awaiting ack\n");
 			}
 
 			goto next_child;
 		}
 
-		/* Acknowledge receipt of peer's notification he closed,
-		 * then logically close ourself */
+		/*
+		 * Acknowledge receipt of peer's notification he closed,
+		 * then logically close ourself
+		 */
 
 		if ((lwsi_role_ws(w) && w->ws->ping_pending_flag) ||
 		    (lwsi_state(w) == LRS_RETURNED_CLOSE &&
@@ -969,11 +981,12 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
 			/* well he is sent, mark him done */
 			w->ws->ping_pending_flag = 0;
 			if (w->ws->payload_is_close) {
-				/* oh... a close frame was it... then we are done */
+				/* oh... a close frame... then we are done */
 				lwsl_debug("Acknowledged peer's close packet\n");
 				w->ws->payload_is_close = 0;
 				lwsi_set_state(w, LRS_RETURNED_CLOSE);
-				lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "returned close packet");
+				lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+						   "returned close packet");
 				wa = &wsi->h2.child_list;
 				goto next_child;
 			}
@@ -986,8 +999,10 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
 		}
 
 		if (lws_callback_as_writeable(w)) {
-			lwsl_info("Closing POLLOUT child (end stream %d)\n", w->h2.send_END_STREAM);
-			lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 pollout handle");
+			lwsl_info("Closing POLLOUT child (end stream %d)\n",
+				  w->h2.send_END_STREAM);
+			lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+					   "h2 pollout handle");
 			wa = &wsi->h2.child_list;
 		} else
 			 if (w->h2.send_END_STREAM)
@@ -997,18 +1012,7 @@ next_child:
 		wsi2 = wa;
 	} while (wsi2 && *wsi2 && !lws_send_pipe_choked(wsi));
 
-	lwsl_info("%s: %p: children waiting for POLLOUT service: %p\n",
-		  __func__, wsi, wsi->h2.child_list);
-	wsi2a = wsi->h2.child_list;
-	while (wsi2a) {
-		if (wsi2a->h2.requested_POLLOUT)
-			lwsl_debug("  * %p\n", wsi2a);
-		else
-			lwsl_debug("    %p\n", wsi2a);
-
-		wsi2a = wsi2a->h2.sibling_list;
-	}
-
+	// lws_h2_dump_waiting_children(wsi);
 
 	wsi2a = wsi->h2.child_list;
 	while (wsi2a) {
diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c
index 7e997d775aa17f99c39894c0e7126d1483985549..9e1436072f54933f63e87c4331b61d4c9dceed6e 100644
--- a/lib/roles/ws/ops-ws.c
+++ b/lib/roles/ws/ops-ws.c
@@ -941,14 +941,10 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 	/* 3: RX Flowcontrol buffer / h2 rx scratch needs to be drained
 	 */
 
-	if (wsi->rxflow_buffer) {
-		lwsl_info("draining rxflow (len %d)\n",
-			wsi->rxflow_len - wsi->rxflow_pos);
-		assert(wsi->rxflow_pos < wsi->rxflow_len);
-		/* well, drain it */
-		eff_buf.token = (char *)wsi->rxflow_buffer +
-					wsi->rxflow_pos;
-		eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos;
+	eff_buf.token_len = lws_buflist_next_segment_len(&wsi->buflist_rxflow,
+						(uint8_t **)&eff_buf.token);
+	if (eff_buf.token_len) {
+		lwsl_info("draining rxflow (len %d)\n", eff_buf.token_len);
 		draining_flow = 1;
 		goto drain;
 	}
@@ -1145,10 +1141,9 @@ drain:
 		goto read;
 	}
 
-	if (draining_flow && wsi->rxflow_buffer &&
-	    wsi->rxflow_pos == wsi->rxflow_len) {
+	if (draining_flow && /* were draining, now nothing left */
+	    !lws_buflist_next_segment_len(&wsi->buflist_rxflow, NULL)) {
 		lwsl_info("%s: %p flow buf: drained\n", __func__, wsi);
-		lws_free_set_NULL(wsi->rxflow_buffer);
 		/* having drained the rxflow buffer, can rearm POLLIN */
 #ifdef LWS_NO_SERVER
 		n =
diff --git a/lib/roles/ws/server-ws.c b/lib/roles/ws/server-ws.c
index d2db682541f6fbba8c72fa63aa2924f0410377a5..fa7ddfe8f8aabe9071583c9ca4174ce9a8e0105e 100644
--- a/lib/roles/ws/server-ws.c
+++ b/lib/roles/ws/server-ws.c
@@ -559,7 +559,7 @@ bail:
 int
 lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len)
 {
-	int m;
+	int m, draining_flow = 0;
 
 	lwsl_parser("%s: received %d byte packet\n", __func__, (int)len);
 
@@ -572,6 +572,7 @@ lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len)
 		if (wsi->rxflow_bitmap) {
 			lws_rxflow_cache(wsi, *buf, 0, (int)len);
 			lwsl_parser("%s: cached %ld\n", __func__, (long)len);
+			buf += len; /* stashing it is taking care of it */
 			return 1;
 		}
 
@@ -583,18 +584,21 @@ lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len)
 		}
 
 		/* account for what we're using in rxflow buffer */
-		if (wsi->rxflow_buffer) {
-			wsi->rxflow_pos++;
-			if (wsi->rxflow_pos > wsi->rxflow_len)
-				assert(0);
+		if (lws_buflist_next_segment_len(&wsi->buflist_rxflow, NULL)) {
+			draining_flow = 1;
+			if (!lws_buflist_use_segment(&wsi->buflist_rxflow, 1))
+				lws_dll_lws_remove(&wsi->dll_rxflow);
 		}
 
 		/* consume payload bytes efficiently */
 		if (wsi->lws_rx_parse_state ==
 		    LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED) {
 			m = lws_payload_until_length_exhausted(wsi, buf, &len);
-			if (wsi->rxflow_buffer)
-				wsi->rxflow_pos += m;
+			if (lws_buflist_next_segment_len(&wsi->buflist_rxflow, NULL)) {
+				draining_flow = 1;
+				if (!lws_buflist_use_segment(&wsi->buflist_rxflow, m))
+					lws_dll_lws_remove(&wsi->dll_rxflow);
+			}
 		}
 
 		/* process the byte */
@@ -603,9 +607,10 @@ lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len)
 			return -1;
 		len--;
 
-		if (wsi->rxflow_buffer && wsi->rxflow_pos == wsi->rxflow_len) {
+		if (draining_flow && /* were draining, now nothing left */
+		    !lws_buflist_next_segment_len(&wsi->buflist_rxflow, NULL)) {
 			lwsl_debug("%s: %p flow buf: drained\n", __func__, wsi);
-			lws_free_set_NULL(wsi->rxflow_buffer);
+
 			/* having drained the rxflow buffer, can rearm POLLIN */
 #ifdef LWS_NO_SERVER
 			m =
diff --git a/lib/service.c b/lib/service.c
index 74d8586267de605ea334c9b72109f45f13c0e20f..f9b709f4cd68a073c50104151c8f42b5a88b192c 100644
--- a/lib/service.c
+++ b/lib/service.c
@@ -277,37 +277,36 @@ __lws_service_timeout_check(struct lws *wsi, time_t sec)
 
 int lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len)
 {
+	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+	uint8_t *buffered;
+	size_t blen;
+	int ret = 0, m;
+
 	if (wsi->role_ops->rxflow_cache)
 		if (wsi->role_ops->rxflow_cache(wsi, buf, n, len))
 			return 0;
 
 	/* his RX is flowcontrolled, don't send remaining now */
-	if (wsi->rxflow_buffer) {
-		if (buf >= wsi->rxflow_buffer &&
-		    &buf[len - 1] < &wsi->rxflow_buffer[wsi->rxflow_len]) {
+	blen = lws_buflist_next_segment_len(&wsi->buflist_rxflow, &buffered);
+	if (blen) {
+		if (buf >= buffered && buf + len <= buffered + blen) {
 			/* rxflow while we were spilling prev rxflow */
 			lwsl_info("%s: staying in rxflow buf\n", __func__);
-			return 1;
-		} else {
-			lwsl_err("%s: conflicting rxflow buf, "
-				 "current %p len %d, new %p len %d\n", __func__,
-				 wsi->rxflow_buffer, wsi->rxflow_len, buf, len);
-			assert(0);
+
 			return 1;
 		}
+		ret = 1;
 	}
 
 	/* a new rxflow, buffer it and warn caller */
-	lwsl_info("%s: new rxflow input buffer len %d\n", __func__, len - n);
-	wsi->rxflow_buffer = lws_malloc(len - n, "rxflow buf");
-	if (!wsi->rxflow_buffer)
-		return -1;
 
-	wsi->rxflow_len = len - n;
-	wsi->rxflow_pos = 0;
-	memcpy(wsi->rxflow_buffer, buf + n, len - n);
+	m = lws_buflist_append_segment(&wsi->buflist_rxflow, buf + n, len - n);
+	if (m < 0)
+		return -1;
+	if (m)
+		lws_dll_lws_add_front(&wsi->dll_rxflow, &pt->dll_head_rxflow);
 
-	return 0;
+	return ret;
 }
 
 /* this is used by the platform service code to stop us waiting for network
diff --git a/lwsws/main.c b/lwsws/main.c
index 6efe5c87b2631784cf6f43b7a26cf85c16115dc0..dcc7b7fa00cc96d249165237603cfa836e9b72c6 100644
--- a/lwsws/main.c
+++ b/lwsws/main.c
@@ -121,6 +121,7 @@ context_creation(void)
 
 	info.external_baggage_free_on_destroy = config_strings;
 	info.max_http_header_pool = 1024;
+	info.pt_serv_buf_size = 8192;
 	info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 |
 			      LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
 			      LWS_SERVER_OPTION_LIBUV;
diff --git a/test-apps/test.html b/test-apps/test.html
index 786ced01f0c25607a6eddc7dd7de4cddc18d08b6..91c6dc2e38c439b606d461125577925489fbf325 100644
--- a/test-apps/test.html
+++ b/test-apps/test.html
@@ -845,7 +845,7 @@ function ev_mousemove (ev) {
 		clearTimeout(lm_timer);
 		pending = "";
 	} else
-		lm_timer = setTimeout(lm_timer_handler, 30);
+		lm_timer = setTimeout(lm_timer_handler, 1);
 
 	last_x = x;
 	last_y = y;