From 3328daecac993197e4b0c9c0a92ad9f13832090b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Deuc=D0=B5?= <shurd@sasktel.net>
Date: Thu, 18 Jan 2024 20:03:13 -0500
Subject: [PATCH] Add a pair of condition variables to help avoid spinning.

Instead of tight loops with 1ms Sleep()s in them, add events for
zero readers and zero writers that we can wait for instead.

Unfortunately, since Events aren't interlocked with a critical section
like condition variables are with mutexes in pthreads, we can't
rely on this for race-free code, so for read locks, we still may
spin under write pressure.
---
 src/xpdev/rwlockwrap.c | 21 ++++++++++++++++++++-
 src/xpdev/rwlockwrap.h |  2 ++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/src/xpdev/rwlockwrap.c b/src/xpdev/rwlockwrap.c
index 519913599b..4b98f4f6b0 100644
--- a/src/xpdev/rwlockwrap.c
+++ b/src/xpdev/rwlockwrap.c
@@ -34,6 +34,8 @@ rwlock_init(rwlock_t *lock)
 {
 	InitializeCriticalSection(&lock->lk);
 	InitializeCriticalSection(&lock->wlk);
+	lock->zeror = CreateEvent(NULL, TRUE, TRUE, NULL);
+	lock->zerow = CreateEvent(NULL, TRUE, TRUE, NULL);
 	lock->readers = 0;
 	lock->writers = 0;
 	lock->writers_waiting = 0;
@@ -55,6 +57,10 @@ rwlock_rdlock(rwlock_t *lock)
 	}
 	while(rc->count == 0 && (lock->writers || lock->writers_waiting)) {
 		LeaveCriticalSection(&lock->lk);
+		if (WaitForSingleObject(lock->zerow, INFINITE) != WAIT_OBJECT_0) {
+			EnterCriticalSection(&lock->lk);
+			continue;
+		}
 		// Wait for current writer to release
 		EnterCriticalSection(&lock->wlk);
 		EnterCriticalSection(&lock->lk);
@@ -72,12 +78,14 @@ rwlock_rdlock(rwlock_t *lock)
 		else {
 			lock->readers++;
 			rc->count++;
+			ResetEvent(lock->zeror);
 			LeaveCriticalSection(&lock->lk);
 			LeaveCriticalSection(&lock->wlk);
 			return TRUE;
 		}
 	}
 	lock->readers++;
+	ResetEvent(lock->zeror);
 	rc->count++;
 	LeaveCriticalSection(&lock->lk);
 	return TRUE;
@@ -98,6 +106,7 @@ rwlock_tryrdlock(rwlock_t *lock)
 	if (rc->count || (lock->writers == 0 && lock->writers_waiting == 0)) {
 		rc->count++;
 		lock->readers++;
+		ResetEvent(lock->zeror);
 		ret = TRUE;
 	}
 	LeaveCriticalSection(&lock->lk);
@@ -110,6 +119,7 @@ rwlock_wrlock(rwlock_t *lock)
 	BOOL ret = FALSE;
 	EnterCriticalSection(&lock->lk);
 	lock->writers_waiting++;
+	ResetEvent(lock->zerow);
 	LeaveCriticalSection(&lock->lk);
 	EnterCriticalSection(&lock->wlk);
 	EnterCriticalSection(&lock->lk);
@@ -117,7 +127,10 @@ rwlock_wrlock(rwlock_t *lock)
 	while (lock->readers) {
 		LeaveCriticalSection(&lock->lk);
 		LeaveCriticalSection(&lock->wlk);
-		Sleep(1);
+		if (WaitForSingleObject(lock->zeror, INFINITE) != WAIT_OBJECT_0) {
+			EnterCriticalSection(&lock->lk);
+			continue;
+		}
 		EnterCriticalSection(&lock->wlk);
 		EnterCriticalSection(&lock->lk);
 	}
@@ -128,6 +141,7 @@ rwlock_wrlock(rwlock_t *lock)
 	else {
 		lock->writers_waiting--;
 		lock->writers++;
+		ResetEvent(lock->zerow);
 		lock->writer = GetCurrentThreadId();
 		ret = TRUE;
 	}
@@ -144,6 +158,7 @@ rwlock_trywrlock(rwlock_t *lock)
 		// Prevent recursing on writer locks
 		if (lock->readers == 0 && lock->writers == 0) {
 			lock->writers++;
+			ResetEvent(lock->zerow);
 			lock->writer = GetCurrentThreadId();
 			LeaveCriticalSection(&lock->lk);
 			return TRUE;
@@ -166,6 +181,8 @@ rwlock_unlock(rwlock_t *lock)
 	if (lock->writers) {
 		if (lock->writer == GetCurrentThreadId()) {
 			lock->writers--;
+			if ((lock->writers_waiting + lock->writers) == 0)
+				SetEvent(lock->zerow);
 			LeaveCriticalSection(&lock->lk);
 			LeaveCriticalSection(&lock->wlk);
 			return TRUE;
@@ -182,6 +199,8 @@ rwlock_unlock(rwlock_t *lock)
 				*prev = rc->next;
 				free(rc);
 			}
+			if (lock->readers == 0)
+				SetEvent(lock->zeror);
 			LeaveCriticalSection(&lock->lk);
 			return TRUE;
 		}
diff --git a/src/xpdev/rwlockwrap.h b/src/xpdev/rwlockwrap.h
index e75dfe5bc6..258bc000c0 100644
--- a/src/xpdev/rwlockwrap.h
+++ b/src/xpdev/rwlockwrap.h
@@ -28,6 +28,8 @@ struct rwlock_reader_thread {
 typedef struct {
 	CRITICAL_SECTION lk;       // Protects access to all elements
 	CRITICAL_SECTION wlk;      // Locked by an active writer
+	HANDLE zeror;              // Event set whenever there are zero readers
+	HANDLE zerow;              // Event set whenever writers_waiting + writers is zero
 	unsigned readers;
 	unsigned writers;
 	unsigned writers_waiting;
-- 
GitLab