From 17db84d24801e8022d326dd7a8d46dcef309a9d6 Mon Sep 17 00:00:00 2001
From: Andrew Aladjev <aladjev.andrew@gmail.com>
Date: Wed, 24 Feb 2021 00:01:33 +0300
Subject: [PATCH] Backport mutexes for socket and connection lists on win32
 #4212

---
 test/socket/test_tcp.rb |  25 +++++++++
 version.h               |   6 +-
 win32/win32.c           | 120 ++++++++++++++++++++++++++--------------
 3 files changed, 108 insertions(+), 43 deletions(-)

diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb
index 11325fdedbf3..7962cbe95982 100644
--- a/test/socket/test_tcp.rb
+++ b/test/socket/test_tcp.rb
@@ -95,4 +95,29 @@ def test_accept_nonblock
       assert_raise(IO::WaitReadable) { svr.accept_nonblock(exception: true) }
     }
   end
+
+  def test_accept_multithread
+    attempts_count       = 5
+    server_threads_count = 3
+    client_threads_count = 3
+
+    attempts_count.times do
+      server_threads = Array.new(server_threads_count) do
+        Thread.new do
+          TCPServer.open("localhost", 0) do |server|
+            accept_threads = Array.new(client_threads_count) do
+              Thread.new { server.accept.close }
+            end
+            client_threads = Array.new(client_threads_count) do
+              Thread.new { TCPSocket.open(server.addr[3], server.addr[1]) }
+            end
+            client_threads.each(&:join)
+            accept_threads.each(&:join)
+          end
+        end
+      end
+
+      server_threads.each(&:join)
+    end
+  end
 end if defined?(TCPSocket)
diff --git a/version.h b/version.h
index 373ee70a6594..f3ada0137814 100644
--- a/version.h
+++ b/version.h
@@ -1,10 +1,10 @@
 #define RUBY_VERSION "2.6.7"
 #define RUBY_RELEASE_DATE "2021-01-31"
-#define RUBY_PATCHLEVEL 153
+#define RUBY_PATCHLEVEL 154
 
 #define RUBY_RELEASE_YEAR 2021
-#define RUBY_RELEASE_MONTH 1
-#define RUBY_RELEASE_DAY 31
+#define RUBY_RELEASE_MONTH 2
+#define RUBY_RELEASE_DAY 23
 
 #include "ruby/version.h"
 
diff --git a/win32/win32.c b/win32/win32.c
index d28bd56452de..fba5975bac93 100644
--- a/win32/win32.c
+++ b/win32/win32.c
@@ -690,9 +690,14 @@ rtc_error_handler(int e, const char *src, int line, const char *exe, const char
 
 static CRITICAL_SECTION select_mutex;
 #define NtSocketsInitialized 1
+
+static CRITICAL_SECTION socklist_mutex;
 static st_table *socklist = NULL;
+
+static CRITICAL_SECTION conlist_mutex;
 static st_table *conlist = NULL;
 #define conlist_disabled ((st_table *)-1)
+
 static char *uenvarea;
 
 /* License: Ruby's */
@@ -717,11 +722,13 @@ free_conlist(st_data_t key, st_data_t val, st_data_t arg)
 static void
 constat_delete(HANDLE h)
 {
+    EnterCriticalSection(&conlist_mutex);
     if (conlist && conlist != conlist_disabled) {
 	st_data_t key = (st_data_t)h, val;
 	st_delete(conlist, &key, &val);
 	xfree((struct constat *)val);
     }
+    LeaveCriticalSection(&conlist_mutex);
 }
 
 /* License: Ruby's */
@@ -732,6 +739,8 @@ exit_handler(void)
 	WSACleanup();
 	DeleteCriticalSection(&select_mutex);
     }
+    DeleteCriticalSection(&socklist_mutex);
+    DeleteCriticalSection(&conlist_mutex);
     if (uenvarea) {
 	free(uenvarea);
 	uenvarea = NULL;
@@ -742,15 +751,20 @@ exit_handler(void)
 static void
 vm_exit_handler(ruby_vm_t *vm)
 {
+    EnterCriticalSection(&socklist_mutex);
     if (socklist) {
 	st_free_table(socklist);
 	socklist = NULL;
     }
+    LeaveCriticalSection(&socklist_mutex);
+
+    EnterCriticalSection(&conlist_mutex);
     if (conlist && conlist != conlist_disabled) {
 	st_foreach(conlist, free_conlist, 0);
 	st_free_table(conlist);
 	conlist = NULL;
     }
+    LeaveCriticalSection(&conlist_mutex);
 }
 
 /* License: Ruby's */
@@ -783,6 +797,8 @@ StartSockets(void)
 	rb_fatal("could not find version 2 of winsock dll");
 
     InitializeCriticalSection(&select_mutex);
+    InitializeCriticalSection(&socklist_mutex);
+    InitializeCriticalSection(&conlist_mutex);
 
     atexit(exit_handler);
 }
@@ -795,11 +811,17 @@ StartSockets(void)
 static inline int
 socklist_insert(SOCKET sock, int flag)
 {
+    int ret;
+
+    EnterCriticalSection(&socklist_mutex);
     if (!socklist) {
 	socklist = st_init_numtable();
 	install_vm_exit_handler();
     }
-    return st_insert(socklist, (st_data_t)sock, (st_data_t)flag);
+    ret = st_insert(socklist, (st_data_t)sock, (st_data_t)flag);
+    LeaveCriticalSection(&socklist_mutex);
+
+    return ret;
 }
 
 /* License: Ruby's */
@@ -809,11 +831,15 @@ socklist_lookup(SOCKET sock, int *flagp)
     st_data_t data;
     int ret;
 
-    if (!socklist)
-	return 0;
-    ret = st_lookup(socklist, (st_data_t)sock, (st_data_t *)&data);
-    if (ret && flagp)
-	*flagp = (int)data;
+    EnterCriticalSection(&socklist_mutex);
+    if (socklist) {
+	ret = st_lookup(socklist, (st_data_t)sock, (st_data_t *)&data);
+	if (ret && flagp)
+	    *flagp = (int)data;
+    } else {
+	ret = 0;
+    }
+    LeaveCriticalSection(&socklist_mutex);
 
     return ret;
 }
@@ -826,17 +852,21 @@ socklist_delete(SOCKET *sockp, int *flagp)
     st_data_t data;
     int ret;
 
-    if (!socklist)
-	return 0;
-    key = (st_data_t)*sockp;
-    if (flagp)
-	data = (st_data_t)*flagp;
-    ret = st_delete(socklist, &key, &data);
-    if (ret) {
-	*sockp = (SOCKET)key;
+    EnterCriticalSection(&socklist_mutex);
+    if (socklist) {
+	key = (st_data_t)*sockp;
 	if (flagp)
-	    *flagp = (int)data;
+	    data = (st_data_t)*flagp;
+	ret = st_delete(socklist, &key, &data);
+	if (ret) {
+	    *sockp = (SOCKET)key;
+	    if (flagp)
+		*flagp = (int)data;
+	}
+    } else {
+	ret = 0;
     }
+    LeaveCriticalSection(&socklist_mutex);
 
     return ret;
 }
@@ -6571,32 +6601,36 @@ constat_handle(HANDLE h)
 {
     st_data_t data;
     struct constat *p;
+
+    EnterCriticalSection(&conlist_mutex);
     if (!conlist) {
 	if (console_emulator_p()) {
 	    conlist = conlist_disabled;
-	    return NULL;
-	}
-	conlist = st_init_numtable();
-	install_vm_exit_handler();
-    }
-    else if (conlist == conlist_disabled) {
-	return NULL;
-    }
-    if (st_lookup(conlist, (st_data_t)h, &data)) {
-	p = (struct constat *)data;
-    }
-    else {
-	CONSOLE_SCREEN_BUFFER_INFO csbi;
-	p = ALLOC(struct constat);
-	p->vt100.state = constat_init;
-	p->vt100.attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
-	p->vt100.reverse = 0;
-	p->vt100.saved.X = p->vt100.saved.Y = 0;
-	if (GetConsoleScreenBufferInfo(h, &csbi)) {
-	    p->vt100.attr = csbi.wAttributes;
+	} else {
+	    conlist = st_init_numtable();
+	    install_vm_exit_handler();
+	}
+    }
+    if (conlist != conlist_disabled) {
+	if (st_lookup(conlist, (st_data_t)h, &data)) {
+	    p = (struct constat *)data;
+	} else {
+	    CONSOLE_SCREEN_BUFFER_INFO csbi;
+	    p = ALLOC(struct constat);
+	    p->vt100.state = constat_init;
+	    p->vt100.attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
+	    p->vt100.reverse = 0;
+	    p->vt100.saved.X = p->vt100.saved.Y = 0;
+	    if (GetConsoleScreenBufferInfo(h, &csbi)) {
+		p->vt100.attr = csbi.wAttributes;
+	    }
+	    st_insert(conlist, (st_data_t)h, (st_data_t)p);
 	}
-	st_insert(conlist, (st_data_t)h, (st_data_t)p);
+    } else {
+	p = NULL;
     }
+    LeaveCriticalSection(&conlist_mutex);
+
     return p;
 }
 
@@ -6606,10 +6640,16 @@ constat_reset(HANDLE h)
 {
     st_data_t data;
     struct constat *p;
-    if (!conlist || conlist == conlist_disabled) return;
-    if (!st_lookup(conlist, (st_data_t)h, &data)) return;
-    p = (struct constat *)data;
-    p->vt100.state = constat_init;
+
+    EnterCriticalSection(&conlist_mutex);
+    if (
+	conlist && conlist != conlist_disabled &&
+	st_lookup(conlist, (st_data_t)h, &data)
+    ) {
+	p = (struct constat *)data;
+	p->vt100.state = constat_init;
+    }
+    LeaveCriticalSection(&conlist_mutex);
 }
 
 #define FOREGROUND_MASK (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY)
