hs_exit()/shutdownHaskell(): wait for outstanding foreign calls to complete before...
authorSimon Marlow <simonmar@microsoft.com>
Tue, 24 Jul 2007 15:30:57 +0000 (15:30 +0000)
committerSimon Marlow <simonmar@microsoft.com>
Tue, 24 Jul 2007 15:30:57 +0000 (15:30 +0000)
This is pertinent to #1177.  When shutting down a DLL, we need to be
sure that there are no OS threads that can return to the code that we
are about to unload, and previously the threaded RTS was unsafe in
this respect.

When exiting a standalone program we don't have to be quite so
paranoid: all the code will disappear at the same time as any running
threads.  Happily exiting a program happens via a different path:
shutdownHaskellAndExit().  If we're about to exit(), then there's no
need to wait for foreign calls to complete.

rts/Capability.c
rts/Capability.h
rts/RtsStartup.c
rts/Schedule.c
rts/Schedule.h

index 73a3427..ffaa372 100644 (file)
@@ -639,7 +639,7 @@ prodOneCapability (void)
  * ------------------------------------------------------------------------- */
 
 void
-shutdownCapability (Capability *cap, Task *task)
+shutdownCapability (Capability *cap, Task *task, rtsBool safe)
 {
     nat i;
 
@@ -696,8 +696,24 @@ shutdownCapability (Capability *cap, Task *task)
            yieldThread();
            continue;
        }
+
+        // If "safe", then busy-wait for any threads currently doing
+        // foreign calls.  If we're about to unload this DLL, for
+        // example, we need to be sure that there are no OS threads
+        // that will try to return to code that has been unloaded.
+        // We can be a bit more relaxed when this is a standalone
+        // program that is about to terminate, and let safe=false.
+        if (cap->suspended_ccalling_tasks && safe) {
+           debugTrace(DEBUG_sched, 
+                      "thread(s) are involved in foreign calls, yielding");
+            cap->running_task = NULL;
+           RELEASE_LOCK(&cap->lock);
+            yieldThread();
+            continue;
+        }
+            
        debugTrace(DEBUG_sched, "capability %d is stopped.", cap->no);
-    freeCapability(cap);
+        freeCapability(cap);
        RELEASE_LOCK(&cap->lock);
        break;
     }
index dedd635..c50fe7f 100644 (file)
@@ -217,7 +217,7 @@ void prodAllCapabilities (void);
 // Waits for a capability to drain of runnable threads and workers,
 // and then acquires it.  Used at shutdown time.
 //
-void shutdownCapability (Capability *cap, Task *task);
+void shutdownCapability (Capability *cap, Task *task, rtsBool wait_foreign);
 
 // Attempt to gain control of a Capability if it is free.
 //
index a363c13..8be4044 100644 (file)
@@ -355,12 +355,25 @@ hs_add_root(void (*init_root)(void))
     initProfiling2();
 }
 
-/* -----------------------------------------------------------------------------
-   Shutting down the RTS
-   -------------------------------------------------------------------------- */
+/* ----------------------------------------------------------------------------
+ * Shutting down the RTS
+ *
+ * The wait_foreign parameter means:
+ *       True  ==> wait for any threads doing foreign calls now.
+ *       False ==> threads doing foreign calls may return in the
+ *                 future, but will immediately block on a mutex.
+ *                 (capability->lock).
+ * 
+ * If this RTS is a DLL that we're about to unload, then you want
+ * safe=True, otherwise the thread might return to code that has been
+ * unloaded.  If this is a standalone program that is about to exit,
+ * then you can get away with safe=False, which is better because we
+ * won't hang on exit if there is a blocked foreign call outstanding.
+ *
+ ------------------------------------------------------------------------- */
 
-void
-hs_exit(void)
+static void
+hs_exit_(rtsBool wait_foreign)
 {
     if (hs_init_count <= 0) {
        errorBelch("warning: too many hs_exit()s");
@@ -386,7 +399,7 @@ hs_exit(void)
 #endif
 
     /* stop all running tasks */
-    exitScheduler();
+    exitScheduler(wait_foreign);
     
 #if defined(GRAN)
     /* end_gr_simulation prints global stats if requested -- HWL */
@@ -497,6 +510,14 @@ hs_exit(void)
 
 }
 
+// The real hs_exit():
+void
+hs_exit(void)
+{
+    hs_exit_(rtsTrue);
+    // be safe; this might be a DLL
+}
+
 // Compatibility interfaces
 void
 shutdownHaskell(void)
@@ -509,7 +530,8 @@ shutdownHaskellAndExit(int n)
 {
     if (hs_init_count == 1) {
        OnExitHook();
-       hs_exit();
+       hs_exit_(rtsFalse);
+        // we're about to exit(), no need to wait for foreign calls to return.
 #if defined(PAR)
        /* really exit (stg_exit() would call shutdownParallelSystem() again) */
        exit(n);
index 441e979..afd8c28 100644 (file)
@@ -2577,7 +2577,8 @@ initScheduler(void)
 }
 
 void
-exitScheduler( void )
+exitScheduler( rtsBool wait_foreign )
+               /* see Capability.c, shutdownCapability() */
 {
     Task *task = NULL;
 
@@ -2599,7 +2600,7 @@ exitScheduler( void )
        nat i;
        
        for (i = 0; i < n_capabilities; i++) {
-           shutdownCapability(&capabilities[i], task);
+           shutdownCapability(&capabilities[i], task, wait_foreign);
        }
        boundTaskExiting(task);
        stopTaskManager();
index ba5efc2..ddac92b 100644 (file)
@@ -18,7 +18,7 @@
  * Locks assumed   :  none
  */
 void initScheduler (void);
-void exitScheduler (void);
+void exitScheduler (rtsBool wait_foreign);
 void freeScheduler (void);
 
 // Place a new thread on the run queue of the current Capability