From 0cbc008746d3e0828832552a1eaf0f6c58e967bb Mon Sep 17 00:00:00 2001
From: "Adam D. Ruppe" <destructionator@gmail.com>
Date: Mon, 28 Sep 2020 11:07:27 -0400
Subject: [PATCH] experimental hybrid config

---
 cgi.d    | 52 ++++++++++++++++++++++++++++++++++++++++++++--------
 dub.json |  4 ++++
 2 files changed, 48 insertions(+), 8 deletions(-)

diff --git a/cgi.d b/cgi.d
index 5820876..3e24bea 100644
--- a/cgi.d
+++ b/cgi.d
@@ -374,6 +374,12 @@ void cloexec(Socket s) {
 	}
 }
 
+version(embedded_httpd_hybrid) {
+	version=embedded_httpd_threads;
+	version=cgi_use_fork;
+	version=cgi_use_fiber;
+}
+
 // the servers must know about the connections to talk to them; the interfaces are vital
 version(with_addon_servers)
 	version=with_addon_servers_connections;
@@ -3854,6 +3860,7 @@ class CgiFiber : Fiber {
 
 version(embedded_httpd_threads)
 void doThreadHttpConnection(CustomCgi, alias fun)(Socket connection) {
+	assert(connection !is null);
 	version(cgi_use_fiber) {
 		auto fiber = new CgiFiber(&doThreadHttpConnectionGuts!(CustomCgi, fun));
 		import core.memory;
@@ -4224,16 +4231,19 @@ private void registerEventWakeup(bool* registered, Socket source, WakeupEvent e)
 				throw new Exception("epoll_ctl");
 		} else {
 			// initial registration
+			*registered = true ;
+			int fd = source.handle;
 			epoll_event evt;
 			evt.events = e | EPOLLONESHOT;
 			evt.data.ptr = cast(void*) f;
-			if(epoll_ctl(epfd, EPOLL_CTL_ADD, source.handle, &evt) == -1)
+			if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &evt) == -1)
 				throw new Exception("epoll_ctl");
-			*registered = true ;
 		}
 	};
 
 	Fiber.yield();
+
+	f.setPostYield(null);
 }
 
 version(cgi_use_fiber)
@@ -4451,10 +4461,12 @@ class ListeningConnectionManager {
 				}
 			}
 		} else {
-			semaphore = new Semaphore();
-
 			import std.parallelism;
 
+			version(cgi_use_fork) {
+				//asm { int 3; }
+				fork();
+			}
 
 			version(cgi_use_fiber) {
 				import core.sys.linux.epoll;
@@ -4472,7 +4484,7 @@ class ListeningConnectionManager {
 				if(epoll_ctl(epfd, EPOLL_CTL_ADD, listener.handle, &ev) == -1)
 					throw new Exception("epoll_ctl " ~ to!string(errno));
 
-				WorkerThread[] threads = new WorkerThread[](totalCPUs * 2 + 1);
+				WorkerThread[] threads = new WorkerThread[](totalCPUs * 1 + 1);
 				foreach(i, ref thread; threads) {
 					thread = new WorkerThread(this, handler, cast(int) i);
 					thread.start();
@@ -4497,6 +4509,8 @@ class ListeningConnectionManager {
 				}
 
 			} else {
+				semaphore = new Semaphore();
+
 				// I times 4 here because there's a good chance some will be blocked on i/o.
 				ConnectionThread[] threads = new ConnectionThread[](totalCPUs * 4);
 				foreach(i, ref thread; threads) {
@@ -4606,8 +4620,13 @@ class ListeningConnectionManager {
 			tcp = true;
 		}
 
-		Thread.getThis.priority = Thread.PRIORITY_MAX;
 		listener.listen(128);
+
+		version(cgi_use_fiber) version(cgi_use_fork)
+			listener.blocking = false;
+
+		// this is the UI control thread and thus gets more priority
+		Thread.getThis.priority = Thread.PRIORITY_MAX;
 	}
 
 	Socket listener;
@@ -4782,7 +4801,18 @@ class WorkerThread : Thread {
 				auto flags = events[idx].events;
 
 				if(cast(size_t) events[idx].data.ptr == cast(size_t) lcm.listener.handle) {
-					sn = lcm.listener.accept();
+					// this try/catch is because it is set to non-blocking mode
+					// and Phobos' stupid api throws an exception instead of returning
+					// if it would block. Why would it block? because a forked process
+					// might have beat us to it, but the wakeup event thundered our herds.
+					version(cgi_use_fork) {
+						try
+						sn = lcm.listener.accept();
+						catch(SocketAcceptException e) { continue; }
+					} else {
+						sn = lcm.listener.accept();
+					}
+
 					cloexec(sn);
 					if(lcm.tcp) {
 						// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
@@ -5091,6 +5121,9 @@ version(cgi_with_websocket) {
 				return true;
 			}
 
+			if(bfr.sourceClosed)
+				return false;
+
 			bfr.popFront(0);
 			if(bfr.sourceClosed)
 				return false;
@@ -5368,7 +5401,10 @@ version(cgi_with_websocket) {
 					default: // ignore though i could and perhaps should throw too
 				}
 			}
-			receiveBufferUsedLength -= s.length - d.length;
+
+			import core.stdc.string;
+			memmove(receiveBuffer.ptr, d.ptr, d.length);
+			receiveBufferUsedLength = d.length;
 
 			return m;
 		}
diff --git a/dub.json b/dub.json
index a65c01a..52597e7 100644
--- a/dub.json
+++ b/dub.json
@@ -207,6 +207,10 @@
 					"name": "embedded_httpd_processes",
 					"versions": ["embedded_httpd_processes"]
 				},
+				{
+					"name": "embedded_httpd_hybrid",
+					"versions": ["embedded_httpd_hybrid"]
+				},
 				{
 					"name": "cgi",
 					"versions": ["traditional_cgi"]