From 89187b834199ce3e292f736b7fdec69008881fac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Deuc=D0=B5?= <shurd@sasktel.net>
Date: Thu, 18 Jan 2024 03:21:00 -0500
Subject: [PATCH] Add rwlock wrappers.

Recursive read locks, non-recursive write locks.
---
 src/xpdev/objects.mk   |   1 +
 src/xpdev/rwlockwrap.c | 132 +++++++++++++++++++++++++++++++++++++++++
 src/xpdev/rwlockwrap.h |  37 +++++++++++-
 src/xpdev/wraptest.c   |  76 +++++++++---------------
 4 files changed, 195 insertions(+), 51 deletions(-)
 create mode 100644 src/xpdev/rwlockwrap.c

diff --git a/src/xpdev/objects.mk b/src/xpdev/objects.mk
index 03286ab638..5805098b35 100644
--- a/src/xpdev/objects.mk
+++ b/src/xpdev/objects.mk
@@ -40,6 +40,7 @@ MTOBJS	= \
 	$(MTOBJODIR)$(DIRSEP)link_list$(OFILE) \
 	$(MTOBJODIR)$(DIRSEP)msg_queue$(OFILE) \
 	$(MTOBJODIR)$(DIRSEP)multisock$(OFILE) \
+	$(MTOBJODIR)$(DIRSEP)rwlockwrap$(OFILE) \
 	$(MTOBJODIR)$(DIRSEP)semwrap$(OFILE) \
 	$(MTOBJODIR)$(DIRSEP)netwrap$(OFILE) \
 	$(MTOBJODIR)$(DIRSEP)sockwrap$(OFILE) \
diff --git a/src/xpdev/rwlockwrap.c b/src/xpdev/rwlockwrap.c
new file mode 100644
index 0000000000..cbeca40c4f
--- /dev/null
+++ b/src/xpdev/rwlockwrap.c
@@ -0,0 +1,132 @@
+#include "rwlockwrap.h"
+
+#ifdef _WIN32
+
+bool
+rwlock_init(rwlock_t *lock)
+{
+	InitializeCriticalSection(&lock->lk);
+	InitializeCriticalSection(&lock->wlk);
+	readers = 0;
+	writers = 0;
+	writers_waiting = 0;
+	writer = (DWORD)-1;
+}
+
+bool
+rwlock_rdlock(rwlock_t *lock)
+{
+	DWORD obj;
+
+	EnterCriticalSection(&lock->lk);
+	while(lock->writers || lock->writers_waiting) {
+		LeaveCriticalSection(&lock->lk);
+		// Wait for current writer to release
+		EnterCriticalSection(&lock->wlk);
+		EnterCriticalSection(&lock->lk);
+		if (lock->writers || lock->writers_waiting) {
+			LeaveCriticalSection(&lock->lk);
+			LeaveCriticalSection(&lock->wlk);
+			/*
+			 * Just in case thread scheduling is weird on
+			 * Win32, allow time for a writer to grab wlk
+			 */
+			Sleep(1);
+			EnterCriticalSection(&lock->lk);
+			continue;
+		}
+		else {
+			lock->readers++;
+			LeaveCriticalSection(&lock->lk);
+			LeaveCriticalSection(&lock->wlk);
+			return true;
+		}
+	}
+	lock->readers++;
+	LeaveCriticalSection(&lock->lk);
+	return true;
+}
+
+bool
+rwlock_tryrdlock(rwlock_t *lock)
+{
+	bool ret = false;
+
+	EnterCriticalSection(&lock->lk);
+	if (lock->writers == 0 && lock->writers_waiting == 0) {
+		lock->readers++;
+		ret = true;
+	}
+	LeaveCriticalSection(&lock->lk);
+	return ret;
+}
+
+bool
+rwlock_wrlock(rwlock_t *lock)
+{
+	EnterCriticalSection(&lock->lk);
+	lock->writers_waiting++;
+	LeaveCriticalSection(&lock->lk);
+	EnterCriticalSection(&lock->wlk);
+	EnterCriticalSection(&lock->lk);
+	// No recursion
+	if (lock->writers == 0) {
+		lock->writers_waiting--;
+		lock->writers++;
+		lock->writer = GetCurrentThreadId();
+		LeaveCriticalSection(&lock->lk);
+		// Keep holding wlk
+		return true;
+	}
+	LeaveCriticalSection(&lock->lk);
+	LeaveCriticalSection(&lock->wlk);
+	return false;
+}
+
+bool
+rwlock_trywrlock(rwlock_t *lock)
+{
+	if (TryEnterCriticalSection(&lock->wlk)) {
+		EnterCriticalSection(&lock->lk);
+		// Prevent recursing on writer locks
+		if (lock->writers == 0) {
+			lock->writers++;
+			lock->writer = GetCurrentThreadId();
+			LeaveCriticalSection(&lock->lk);
+			return true;
+		}
+		LeaveCriticalSection(&lock->lk);
+		LeaveCriticalSection(&lock->wlk);
+		return false;
+	}
+	return false;
+}
+
+bool
+rwlock_unlock(rwlock_t *lock)
+{
+	bool ret = false;
+	EnterCriticalSection(&lock->lk);
+	if (lock->writers) {
+		if (lock->writer == GetCurrentThreadId()) {
+			lock->writers--;
+			LeaveCriticalSection(&lock->lk);
+			LeaveCriticalSection(&lock->wlk);
+			return true;
+		}
+		LeaveCriticalSection(&lock->lk);
+		return false;
+	}
+	lock->readers--;
+	return true;
+}
+
+#elif defined(__unix__)
+
+// All macros...
+
+#else
+
+#error no rwlock wrapper for this platform
+
+#endif
diff --git a/src/xpdev/rwlockwrap.h b/src/xpdev/rwlockwrap.h
index 6c7723a174..79cea157cc 100644
--- a/src/xpdev/rwlockwrap.h
+++ b/src/xpdev/rwlockwrap.h
@@ -1,9 +1,40 @@
 #ifndef RWLOCKWRAP_H
-
 #define RWLOCKWRAP_H
-#ifdef UNIX
+
+#include <stdbool.h>
+
+#if defined(__unix__)
+
 #include <pthread.h>
-#include <time.h>
+typedef pthread_rwlock_t rwlock_t;
+
+#define rwlock_init(lock) (pthread_rwlock_init(lock, NULL) == 0)
+#define rwlock_rdlock(lock) (pthread_rwlock_rdlock(lock) == 0)
+#define rwlock_tryrdlock(lock) (pthread_rwlock_tryrdlock(lock) == 0)
+#define rwlock_wrlock(lock) (pthread_rwlock_wrlock(lock) == 0)
+#define rwlock_trywrlock(lock) (pthread_rwlock_trywrlock(lock) == 0)
+#define rwlock_unlock(lock) (pthread_rwlock_unlock(lock) == 0)
+
+#elif defined(_WIN32)
+
+#include "threadwrap.h"
+
+typedef struct {
+	CRITICAL_SECTION lk;       // Protects access to all elements
+	CRITICAL_SECTION wlk;      // Locked by an active writer
+	unsigned readers;
+	unsigned writers;
+	unsigned writers_waiting;
+	DWORD writer;
+} rwlock_t;
+
+bool rwlock_init(rwlock_t *lock);
+bool rwlock_rdlock(rwlock_t *lock);
+bool rwlock_tryrdlock(rwlock_t *lock);
+bool rwlock_wrlock(rwlock_t *lock);
+bool rwlock_trywrlock(rwlock_t *lock);
+bool rwlock_unlock(rwlock_t *lock);
+
 #else
 #error Not implemented
 #endif
diff --git a/src/xpdev/wraptest.c b/src/xpdev/wraptest.c
index d3ead989c4..121afa6756 100644
--- a/src/xpdev/wraptest.c
+++ b/src/xpdev/wraptest.c
@@ -10,6 +10,7 @@
 #include "conwrap.h"
 #include "dirwrap.h"
 #include "filewrap.h"
+#include "rwlockwrap.h"
 #include "sockwrap.h"
 #include "threadwrap.h"
 #include "xpbeep.h"
@@ -63,12 +64,12 @@ int main()
 
 	printf("Testing rwlocks...\n");
 	do {
-		pthread_rwlock_t lock;
-		if ((i=pthread_rwlock_init(&lock, NULL)) != 0) {
-			printf("pthread_rwlock_init() failed (%d)\n", i);
+		rwlock_t lock;
+		if (!rwlock_init(&lock)) {
+			printf("rwlock_init() failed (%d)\n", i);
 			continue;
 		}
-		// Start two copies of the ddlock thread...
+		// Start two copies of the rdlock thread...
 		if (_beginthread(rwlock_rdlock_thread, 0, &lock) == -1UL) {
 			printf("Unable to start rwlock_rdlock_thread\n");
 			continue;
@@ -437,101 +438,80 @@ static void sopen_child_thread(void* arg)
 
 static void rwlock_rdlock_thread(void *arg)
 {
-	pthread_rwlock_t *lock = arg;
+	rwlock_t *lock = arg;
 	int locks = 0;
-	int i;
-	struct timespec abstime = {0};
 
 	// Grab the lock three times...
 	for (locks = 0; locks < 3; locks++) {
-		if ((i = pthread_rwlock_rdlock(lock)) != 0) {
+		if (!rwlock_rdlock(lock)) {
 			printf("Failed to obtain rdlock #%d\n", locks + 1);
 			break;
 		}
 	}
 	// Try to grab the lock (should succeed)
-	if ((i = pthread_rwlock_tryrdlock(lock)) == 0) {
+	if (rwlock_tryrdlock(lock)) {
 		locks++;
 	}
 	else {
-		printf("tryrdlock failed: %d\n", i);
+		printf("tryrdlock failed\n");
 	}
-	// Try to grab the lock before Jan 1st, 1970 (should succeed)
-	if ((i = pthread_rwlock_timedrdlock(lock, &abstime)) == 0) {
-		locks++;
-	}
-	else {
-		printf("timedrdlock failed: %d\n", i);
-	}
-	printf("Obtained %d locks (should be 5)\n", locks);
+	printf("Obtained %d locks (should be 4)\n", locks);
 	for (; locks > 0; locks--) {
 		SLEEP(1000);
-		if ((i = pthread_rwlock_unlock(lock)) != 0) {
+		if (!rwlock_unlock(lock)) {
 			printf("Failed to unlock rdlock #%d\n", locks);
 		}
 	}
 	SLEEP(1000);
 	// Try to grab the lock (should fail)
-	if ((i = pthread_rwlock_tryrdlock(lock)) == 0) {
-		printf("tryrdlock incorrectly succeeded: %d\n", i);
-		pthread_rwlock_unlock(lock);
-	}
-	// Try to grab the lock before Jan 1st, 1970 (should fail)
-	if ((i = pthread_rwlock_timedrdlock(lock, &abstime)) == 0) {
-		printf("timedrdlock incorrectly succeeded: %d\n", i);
-		pthread_rwlock_unlock(lock);
+	if (rwlock_tryrdlock(lock)) {
+		printf("tryrdlock incorrectly succeeded\n");
+		rwlock_unlock(lock);
 	}
-	// Wait up to five seconds for the lock (should succeed)
-	clock_gettime(CLOCK_REALTIME, &abstime);
-	abstime.tv_sec += 5;
 	printf("Waiting for lock\n");
-	if ((i = pthread_rwlock_timedrdlock(lock, &abstime)) == 0) {
+	if (rwlock_rdlock(lock)) {
 		locks++;
 	}
 	else {
-		printf("timedrdlock failed: %d\n", i);
+		printf("timedrdlock failed\n");
 	}
 	printf("Wait done\n");
 	SLEEP(1000);
 	for (; locks > 0; locks--) {
-		pthread_rwlock_unlock(lock);
+		rwlock_unlock(lock);
 	}
 }
 
 static void rwlock_wrlock_thread(void *arg)
 {
-	pthread_rwlock_t *lock = arg;
-	int i;
-	struct timespec abstime = {0};
+	rwlock_t *lock = arg;
 
 	// Sleep to allow readers to grab locks...
 	SLEEP(1000);
 	// Try to grab lock (should fail)
-	if ((i = pthread_rwlock_trywrlock(lock)) == 0) {
+	if (rwlock_trywrlock(lock)) {
 		printf("trywrlock() incorrectly succeeded\n");
-		pthread_rwlock_unlock(lock);
+		rwlock_unlock(lock);
 	}
 	// Try to grab lock (should succeed)
 	printf("Waiting for write lock\n");
-	if ((i = pthread_rwlock_wrlock(lock)) != 0) {
-		printf("wrlock() failed %d\n", i);
+	if (!rwlock_wrlock(lock)) {
+		printf("wrlock() failed\n");
 	}
 	printf("wrlock Wait done\n");
 	// Enjoy the lock for a bit...
 	SLEEP(2000);
 	printf("wrlock unlocking\n");
-	pthread_rwlock_unlock(lock);
+	rwlock_unlock(lock);
 	SLEEP(100);
-	// Wait up to five seconds for the lock (should succeed)
-	clock_gettime(CLOCK_REALTIME, &abstime);
-	abstime.tv_sec += 5;
-	printf("Waiting up to 5 seconds for wrlock\n");
-	if ((i = pthread_rwlock_timedwrlock(lock, &abstime)) != 0) {
-		printf("timedrdlock failed: %d\n", i);
+	// Wait for the lock (should succeed)
+	printf("Waiting wrlock\n");
+	if (!rwlock_wrlock(lock)) {
+		printf("wrlock failed\n");
 	}
 	else {
 		SLEEP(1000);
-		pthread_rwlock_unlock(lock);
+		rwlock_unlock(lock);
 	}
 	printf("wrlock wait done\n");
 }
-- 
GitLab