diff --git a/README.esp8266.md b/README.esp8266.md
new file mode 100644
index 0000000000000000000000000000000000000000..ffcf7578d04664a9cc0cc043b3d40ebef4bbc81b
--- /dev/null
+++ b/README.esp8266.md
@@ -0,0 +1,34 @@
+ESP8266 lws port
+----------------
+
+lws can now work well on the ESP8266.
+
+You should get the ESP8266 Espressif SDK-based project here
+
+https://github.com/lws-team/esplws
+
+which includes lws as an "app" in the build.  The project provides full AP-based setup over the web, and once the device has been configured to associate to a local AP, a separate station vhost with the lws test protocols.
+
+Instructions for building that are here
+
+https://github.com/lws-team/esplws/blob/master/README.md
+
+There are also instructions there for how to remove the test apps from the build and customize your own station content.
+
+
+Information about lws integration on ESP8266
+--------------------------------------------
+
+The following existing lws features are used to make a nice integration:
+
+ - vhosts: there are separate vhosts for the configuration AP mode and the normal station mode.
+
+ - file_ops: the lws file operations are overridden and handled by a ROMFS parser
+
+ - mounts: mounts are used to serve files automatically from the ROMFS
+
+ - plugins: standalone protocol plugins are included into the build, so there are clean individual implementations for each protocol, while everything is statically linked
+
+ - lws stability and security features like bytewise parsers, sophisticated timeouts, http/1.1 keepalive support
+
+
diff --git a/README.md b/README.md
index d640c61d5fc0be6ed2a2ac8517b354898c8d64c4..7320af6bf13d775b168f4fdfe24bdc905c5ed0b9 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,11 @@
 libwebsockets
 -------------
 
+| News |
+------
+| ESP8266 is now supported in lws!  See https://github.com/warmcat/libwebsockets/blob/master/README.esp8266.md |
+
+
 This is the libwebsockets C library for lightweight websocket clients and
 servers.  For support, visit
 
@@ -15,10 +20,6 @@ and consider joining the project mailing list at
 
  https://libwebsockets.org/mailman/listinfo/libwebsockets
 
-| News |
-------
-| We have updated https://libwebsockets.org, if you would like your project using lws featured in the image carousel, send an image, project URL and brief summary to andy@warmcat.com |
-
 You can get the latest version of the library from git:
 
 - https://github.com/warmcat/libwebsockets
diff --git a/lib/client-handshake.c b/lib/client-handshake.c
index 678979fbb7b9b8d0182a7da738cb66adb4cc04f5..5f915bfa20dcd28f710cc49034590b072df60a74 100644
--- a/lib/client-handshake.c
+++ b/lib/client-handshake.c
@@ -175,6 +175,8 @@ lws_client_connect_2(struct lws *wsi)
 			goto oom4;
 		}
 
+		lws_change_pollfd(wsi, 0, LWS_POLLIN);
+
 		/*
 		 * past here, we can't simply free the structs as error
 		 * handling as oom4 does.  We have to run the whole close flow.
@@ -485,6 +487,9 @@ lws_client_connect_via_info(struct lws_client_connect_info *i)
 	if (i->context->requested_kill)
 		return NULL;
 
+	if (!i->context->protocol_init_done)
+		lws_protocol_init(i->context);
+
 	wsi = lws_zalloc(sizeof(struct lws));
 	if (wsi == NULL)
 		goto bail;
diff --git a/lib/parsers.c b/lib/parsers.c
index 4e03394ae665bf70801282d8017f14f2981e2c93..ecb7bc3be34436ab6c06aa417c1619277bf3f943 100644
--- a/lib/parsers.c
+++ b/lib/parsers.c
@@ -119,7 +119,7 @@ lws_header_table_attach(struct lws *wsi, int autoservice)
 	struct lws **pwsi;
 	int n;
 
-	lwsl_notice("%s: wsi %p: ah %p (tsi %d, count = %d) in\n", __func__, (void *)wsi,
+	lwsl_info("%s: wsi %p: ah %p (tsi %d, count = %d) in\n", __func__, (void *)wsi,
 		 (void *)wsi->u.hdr.ah, wsi->tsi, pt->ah_count_in_use);
 
 	/* if we are already bound to one, just clear it down */
@@ -291,14 +291,15 @@ int lws_header_table_detach(struct lws *wsi, int autoservice)
 	lws_header_table_reset(wsi, autoservice);
 	time(&wsi->u.hdr.ah->assigned);
 
-	assert(wsi->position_in_fds_table != -1);
+	/* clients acquire the ah and then insert themselves in fds table... */
+	if (wsi->position_in_fds_table != -1) {
+		lwsl_info("%s: Enabling %p POLLIN\n", __func__, wsi);
 
-	lwsl_info("%s: Enabling %p POLLIN\n", __func__, wsi);
-
-	/* he has been stuck waiting for an ah, but now his wait is over,
-	 * let him progress
-	 */
-	_lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa);
+		/* he has been stuck waiting for an ah, but now his wait is over,
+		 * let him progress
+		 */
+		_lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa);
+	}
 
 	/* point prev guy to next guy in list instead */
 	*pwsi = wsi->u.hdr.ah_wait_list;
diff --git a/lib/pollfd.c b/lib/pollfd.c
index cafa0befe99d37d22acea765b30e33bacf2a68b9..a56908b83a9c5e72908b7b14227792240823618e 100644
--- a/lib/pollfd.c
+++ b/lib/pollfd.c
@@ -43,6 +43,8 @@ _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa)
 	pa->prev_events = pfd->events;
 	pa->events = pfd->events = (pfd->events & ~_and) | _or;
 
+	//lwsl_notice("%s: wsi %p, posin %d. from %d -> %d\n", __func__, wsi, wsi->position_in_fds_table, pa->prev_events, pa->events);
+
 
 	if (wsi->http2_substream)
 		return 0;
@@ -147,6 +149,9 @@ insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi)
 	if (wsi->position_in_fds_table == -1)
 #endif
 		wsi->position_in_fds_table = pt->fds_count;
+
+	// lwsl_notice("%s: %p: setting posinfds %d\n", __func__, wsi, wsi->position_in_fds_table);
+
 	pt->fds[wsi->position_in_fds_table].fd = wsi->sock;
 #if LWS_POSIX
 	pt->fds[wsi->position_in_fds_table].events = LWS_POLLIN;
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 93d340ac285516695517974d7f8dad235d687434..e70750288064aed248ef64acc402a22dbf62ab19 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -701,6 +701,7 @@ struct lws_context_per_thread {
 
 	short ah_count_in_use;
 	unsigned char tid;
+	unsigned char lock_depth;
 };
 
 /*
@@ -1752,13 +1753,15 @@ lws_pt_mutex_destroy(struct lws_context_per_thread *pt)
 static LWS_INLINE void
 lws_pt_lock(struct lws_context_per_thread *pt)
 {
-	pthread_mutex_lock(&pt->lock);
+	if (!pt->lock_depth++)
+		pthread_mutex_lock(&pt->lock);
 }
 
 static LWS_INLINE void
 lws_pt_unlock(struct lws_context_per_thread *pt)
 {
-	pthread_mutex_unlock(&pt->lock);
+	if (!(--pt->lock_depth))
+		pthread_mutex_unlock(&pt->lock);
 }
 #else
 #define lws_pt_mutex_init(_a) (void)(_a)
diff --git a/test-server/test-client.c b/test-server/test-client.c
index a06deb5f3dcc510655ac3407831a5015026e289e..a6ffe29766cdfbc04d25db81a946edb64df911e4 100644
--- a/test-server/test-client.c
+++ b/test-server/test-client.c
@@ -39,8 +39,9 @@
 
 static int deny_deflate, longlived, mirror_lifetime;
 static struct lws *wsi_dumb, *wsi_mirror;
+static struct lws *wsi_multi[3];
 static volatile int force_exit;
-static unsigned int opts;
+static unsigned int opts, rl_multi[3];
 static int flag_no_mirror_traffic;
 #if defined(LWS_USE_POLARSSL)
 #else
@@ -87,6 +88,8 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
 			void *user, void *in, size_t len)
 {
 	const char *which = "http";
+	char which_wsi[10];
+	int n;
 
 	switch (reason) {
 
@@ -116,6 +119,13 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
 			wsi_mirror = NULL;
 		}
 
+		for (n = 0; n < ARRAY_SIZE(wsi_multi); n++)
+			if (wsi == wsi_multi[n]) {
+				sprintf(which_wsi, "multi %d", n);
+				which = which_wsi;
+				wsi_multi[n] = NULL;
+			}
+
 		lwsl_err("CLIENT_CONNECTION_ERROR: %s: %s %p\n", which, in);
 		break;
 
@@ -152,6 +162,10 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
 		}
 		break;
 
+	case LWS_CALLBACK_CLIENT_WRITEABLE:
+		lwsl_notice("Client wsi %p writable\n", wsi);
+		break;
+
 	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
 		wsi_dumb = NULL;
 		force_exit = 1;
@@ -322,6 +336,7 @@ static struct option options[] = {
 	{ "strict-ssl",	no_argument,		NULL, 'S' },
 	{ "version",	required_argument,	NULL, 'v' },
 	{ "undeflated",	no_argument,		NULL, 'u' },
+	{ "multi-test",	no_argument,		NULL, 'm' },
 	{ "nomirror",	no_argument,		NULL, 'n' },
 	{ "longlived",	no_argument,		NULL, 'l' },
 	{ "pingpong-secs", required_argument,	NULL, 'P' },
@@ -350,8 +365,8 @@ static int ratelimit_connects(unsigned int *last, unsigned int secs)
 
 int main(int argc, char **argv)
 {
-	int n = 0, ret = 0, port = 7681, use_ssl = 0, ietf_version = -1;
-	unsigned int rl_dumb = 0, rl_mirror = 0, do_ws = 1, pp_secs = 0;
+	int n = 0, m, ret = 0, port = 7681, use_ssl = 0, ietf_version = -1;
+	unsigned int rl_dumb = 0, rl_mirror = 0, do_ws = 1, pp_secs = 0, do_multi = 0;
 	struct lws_context_creation_info info;
 	struct lws_client_connect_info i;
 	struct lws_context *context;
@@ -370,7 +385,7 @@ int main(int argc, char **argv)
 		goto usage;
 
 	while (n >= 0) {
-		n = getopt_long(argc, argv, "Snuv:hsp:d:lC:K:A:P:", options, NULL);
+		n = getopt_long(argc, argv, "Snuv:hsp:d:lC:K:A:P:m", options, NULL);
 		if (n < 0)
 			continue;
 		switch (n) {
@@ -401,6 +416,9 @@ int main(int argc, char **argv)
 		case 'u':
 			deny_deflate = 1;
 			break;
+		case 'm':
+			do_multi = 1;
+			break;
 		case 'n':
 			flag_no_mirror_traffic = 1;
 			lwsl_notice("Disabled sending mirror data (for pingpong testing)\n");
@@ -544,30 +562,52 @@ int main(int argc, char **argv)
 	 * asynchronously.
 	 */
 
+	m = 0;
 	while (!force_exit) {
 
-		if (do_ws) {
-			if (!wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
-				lwsl_notice("dumb: connecting\n");
-				i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
-				i.pwsi = &wsi_dumb;
-				lws_client_connect_via_info(&i);
-			}
-
-			if (!wsi_mirror && ratelimit_connects(&rl_mirror, 2u)) {
-				lwsl_notice("mirror: connecting\n");
-				i.protocol = protocols[PROTOCOL_LWS_MIRROR].name;
-				i.pwsi = &wsi_mirror;
-				wsi_mirror = lws_client_connect_via_info(&i);
-			}
-		} else
-			if (!wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
-				lwsl_notice("http: connecting\n");
-				i.pwsi = &wsi_dumb;
-				lws_client_connect_via_info(&i);
+		if (do_multi) {
+			for (n = 0; n < ARRAY_SIZE(wsi_multi); n++) {
+				if (!wsi_multi[n] && ratelimit_connects(&rl_multi[n], 2u)) {
+					lwsl_notice("dumb %d: connecting\n", n);
+					i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
+					i.pwsi = &wsi_multi[n];
+					lws_client_connect_via_info(&i);
+				}
 			}
+		} else {
+
+			if (do_ws) {
+				if (!wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
+					lwsl_notice("dumb: connecting\n");
+					i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
+					i.pwsi = &wsi_dumb;
+					lws_client_connect_via_info(&i);
+				}
+
+				if (!wsi_mirror && ratelimit_connects(&rl_mirror, 2u)) {
+					lwsl_notice("mirror: connecting\n");
+					i.protocol = protocols[PROTOCOL_LWS_MIRROR].name;
+					i.pwsi = &wsi_mirror;
+					wsi_mirror = lws_client_connect_via_info(&i);
+				}
+			} else
+				if (!wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
+					lwsl_notice("http: connecting\n");
+					i.pwsi = &wsi_dumb;
+					lws_client_connect_via_info(&i);
+				}
+		}
 
 		lws_service(context, 500);
+
+		if (do_multi) {
+			m++;
+			if (m == 10) {
+				m = 0;
+				lwsl_notice("doing lws_callback_on_writable_all_protocol\n");
+				lws_callback_on_writable_all_protocol(context, &protocols[PROTOCOL_DUMB_INCREMENT]);
+			}
+		}
 	}
 
 	lwsl_err("Exiting\n");