From f0c1f862563cc337b2ce3b8d053d4eaa98dd4cab Mon Sep 17 00:00:00 2001
From: Duncan Coutts <duncan@well-typed.com>
Date: Mon, 9 Jan 2023 00:20:39 +0000
Subject: [PATCH] Have the throwTo impl go via (new) IOManager APIs

rather than directly operating on the IO manager's data structures.

Specifically, when thowing an async exception to a thread that is
blocked waiting for I/O or waiting for a timer, then we want to cancel
that I/O waiting or cancel the timer. Currently this is done directly in
removeFromQueues() in RaiseAsync.c. We want it to go via proper APIs
both for modularity but also to let us support multiple I/O managers.

So add sync{IO,Delay}Cancel, which is the cancellation for the
corresponding sync{IO,Delay}. The implementations of these use the usual
"switch (iomgr_type)" style.
---
 rts/IOManager.c  | 55 +++++++++++++++++++++++++++++++++++++++++++++++-
 rts/IOManager.h  |  4 ++++
 rts/RaiseAsync.c | 20 ++++++------------
 3 files changed, 64 insertions(+), 15 deletions(-)

diff --git a/rts/IOManager.c b/rts/IOManager.c
index 5777df0871e1..f9fe4c2eecfd 100644
--- a/rts/IOManager.c
+++ b/rts/IOManager.c
@@ -23,7 +23,13 @@
 #include "RtsFlags.h"
 #include "RtsUtils.h"
 
-#if !defined(mingw32_HOST_OS) && defined(HAVE_SIGNAL_H)
+#if defined(IOMGR_ENABLED_SELECT)
+#include "Threads.h"
+#include "posix/Select.h"
+#include "posix/Signals.h"
+#endif
+
+#if defined(IOMGR_ENABLED_MIO_POSIX)
 #include "posix/Signals.h"
 #endif
 
@@ -33,6 +39,7 @@
 #endif
 
 #if defined(IOMGR_ENABLED_WIN32_LEGACY)
+#include "Threads.h"
 #include "win32/AsyncMIO.h"
 #include "win32/MIOManager.h"
 #endif
@@ -571,6 +578,29 @@ void syncIOWaitReady(Capability   *cap,
     }
 }
 
+
+void syncIOCancel(Capability *cap, StgTSO *tso)
+{
+    switch (iomgr_type) {
+#if defined(IOMGR_ENABLED_SELECT)
+        case IO_MANAGER_SELECT:
+            removeThreadFromDeQueue(cap, &cap->iomgr->blocked_queue_hd,
+                                         &cap->iomgr->blocked_queue_tl, tso);
+            break;
+#endif
+#if defined(IOMGR_ENABLED_WIN32_LEGACY)
+        case IO_MANAGER_WIN32_LEGACY:
+            removeThreadFromDeQueue(cap, &cap->iomgr->blocked_queue_hd,
+                                         &cap->iomgr->blocked_queue_tl, tso);
+            abandonWorkRequest(tso->block_info.async_result->reqID);
+            break;
+#endif
+        default:
+            barf("syncIOCancel not supported for I/O manager %d", iomgr_type);
+    }
+}
+
+
 #if defined(IOMGR_ENABLED_SELECT)
 static void insertIntoSleepingQueue(Capability *cap, StgTSO *tso, LowResTime target);
 #endif
@@ -617,6 +647,29 @@ void syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay)
     }
 }
 
+
+void syncDelayCancel(Capability *cap, StgTSO *tso)
+{
+    debugTrace(DEBUG_iomanager, "cancelling delay for thread %ld", (long) tso->id);
+    switch (iomgr_type) {
+#if defined(IOMGR_ENABLED_SELECT)
+        case IO_MANAGER_SELECT:
+            removeThreadFromQueue(cap, &cap->iomgr->sleeping_queue, tso);
+            break;
+#endif
+        /* Note: no case for IO_MANAGER_WIN32_LEGACY despite it having a case
+         * for syncDelay above. This is because the win32 legacy I/O manager
+         * treats delay as an I/O operation, using the BlockedOnDoProc blocking
+         * reason, rather than the BlockedOnDelay reason. As a consequence,
+         * cancellation goes via syncIOCancel instead. Yes, it's a bit weird.
+         */
+
+        default:
+            barf("syncDelayCancel not supported for I/O manager %d", iomgr_type);
+    }
+}
+
+
 #if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_WIN32_LEGACY)
 void appendToIOBlockedQueue(Capability *cap, StgTSO *tso)
 {
diff --git a/rts/IOManager.h b/rts/IOManager.h
index d1f2c3ea58ca..90e286959320 100644
--- a/rts/IOManager.h
+++ b/rts/IOManager.h
@@ -284,8 +284,12 @@ typedef enum { IORead, IOWrite } IOReadOrWrite;
 
 void syncIOWaitReady(Capability *cap, StgTSO *tso, IOReadOrWrite rw, HsInt fd);
 
+void syncIOCancel(Capability *cap, StgTSO *tso);
+
 void syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay);
 
+void syncDelayCancel(Capability *cap, StgTSO *tso);
+
 #if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_WIN32_LEGACY)
 /* Add a thread to the end of the queue of threads blocked on I/O.
  *
diff --git a/rts/RaiseAsync.c b/rts/RaiseAsync.c
index 31208c2f6fb8..542bf3e09a39 100644
--- a/rts/RaiseAsync.c
+++ b/rts/RaiseAsync.c
@@ -20,6 +20,7 @@
 #include "sm/Sanity.h"
 #include "Profiling.h"
 #include "Messages.h"
+#include "IOManager.h"
 #if defined(mingw32_HOST_OS)
 #include "win32/MIOManager.h"
 #endif
@@ -703,26 +704,17 @@ removeFromQueues(Capability *cap, StgTSO *tso)
       break;
   }
 
-#if !defined(THREADED_RTS)
   case BlockedOnRead:
   case BlockedOnWrite:
-#if defined(mingw32_HOST_OS)
   case BlockedOnDoProc:
-#endif
-      removeThreadFromDeQueue(cap, &cap->iomgr->blocked_queue_hd,
-                                   &cap->iomgr->blocked_queue_tl, tso);
-#if defined(mingw32_HOST_OS)
-      /* (Cooperatively) signal that the worker thread should abort
-       * the request.
-       */
-      abandonWorkRequest(tso->block_info.async_result->reqID);
-#endif
+      // These blocking reasons are only used by some I/O managers
+      syncIOCancel(cap, tso);
       goto done;
 
   case BlockedOnDelay:
-        removeThreadFromQueue(cap, &cap->iomgr->sleeping_queue, tso);
-        goto done;
-#endif
+      // This blocking reasons is only used by some I/O managers
+      syncDelayCancel(cap, tso);
+      goto done;
 
   default:
       barf("removeFromQueues: %d", tso->why_blocked);
-- 
GitLab