[project @ 2004-11-17 19:07:38 by sof]
authorsof <unknown>
Wed, 17 Nov 2004 19:07:40 +0000 (19:07 +0000)
committersof <unknown>
Wed, 17 Nov 2004 19:07:40 +0000 (19:07 +0000)
Expose Win32 console event handling to the user.

Added RTS support for registering and delivering console events quite
a while ago (rts/win32/ConsoleHandler.c), but got bored with it before
completing the job. Here's the concluding commit; it does the following:

- new module, base/GHC/ConsoleHandler.hs which supports registering of
  console event handlers (the null module on plats other than mingw).
- special handling of aborted async read()s on 'standard input' in
  rts/win32/IOManager.c (together with GHC.Conc.asyncRead). See comments
  in that IOManager.c as to why this is needed.
  [ Any other code that performs blocking I/O on 'standard input' will
    need to be tweaked too to be console event handler/signal friendly.]
- for now, disable the delivery of 'close' events (see
  rts/win32/ConsoleHandler.c:generic_handler() for reasons why)

Feel free to hoik GHC/ConsoleHandler.hs around the lib hierarchy to wherever
is considered more fitting. Unifying functionality between System.Posix.Signals
and GHC.ConsoleHandler is one (obvious) thing to do.

-- Demonstrating GHC.ConsoleHandler use; win32 only
module Main(main) where

import GHC.ConsoleHandler
import System.IO  (hFlush, stdout)
import GHC.Conc   (threadDelay)

main :: IO ()
main = do
  installHandler (Catch (\ _ -> putStrLn "Caught console event; ignoring" >> hFlush stdout))
  loop
 where
  loop = do
    threadDelay 100000
    ls <- getLine
    putStrLn ls
    loop
--

ghc/rts/win32/ConsoleHandler.c
ghc/rts/win32/IOManager.c

index 4220f29..a1d3bd9 100644 (file)
@@ -50,9 +50,11 @@ static BOOL WINAPI shutdown_handler(DWORD dwCtrlType)
 {
     switch (dwCtrlType) {
     
+    case CTRL_CLOSE_EVENT:
+       /* see generic_handler() comment re: this event */
+       return FALSE;
     case CTRL_C_EVENT:
     case CTRL_BREAK_EVENT:
-    case CTRL_CLOSE_EVENT:
 
        // If we're already trying to interrupt the RTS, terminate with
        // extreme prejudice.  So the first ^C tries to exit the program
@@ -185,12 +187,23 @@ void handleSignalsInThisThread(void)
 static BOOL WINAPI generic_handler(DWORD dwCtrlType)
 {
     /* Ultra-simple -- up the counter + signal a switch. */
-    if ( stg_pending_events < N_PENDING_EVENTS ) {
-       stg_pending_buf[stg_pending_events] = dwCtrlType;
-       stg_pending_events++;
+    switch(dwCtrlType) {
+    case CTRL_CLOSE_EVENT:
+       /* Don't support the delivery of this event; if we
+        * indicate that we've handled it here and the Haskell handler
+        * doesn't take proper action (e.g., terminate the OS process),
+        * the user of the app will be unable to kill/close it. Not
+        * good, so disable the delivery for now.
+        */
+       return FALSE;
+    default:
+       if ( stg_pending_events < N_PENDING_EVENTS ) {
+           stg_pending_buf[stg_pending_events] = dwCtrlType;
+           stg_pending_events++;
+       }
+       context_switch = 1;
+       return TRUE;
     }
-    context_switch = 1;
-    return TRUE;
 }
 
 
index c08c4e3..96e5794 100644 (file)
@@ -42,7 +42,7 @@ IOWorkerProc(PVOID param)
     WorkQueue* pq = iom->workQueue;
     WorkItem*  work;
     int        len = 0, fd = 0;
-    DWORD      errCode;
+    DWORD      errCode = 0;
     void*      complData;
 
     hWaits[0] = (HANDLE)iom->hExitEvent;
@@ -96,9 +96,40 @@ IOWorkerProc(PVOID param)
                            errCode = WSAGetLastError();
                        }
                    } else {
+                       DWORD dw;
+
+                       /* Do the read(), with extra-special handling for Ctrl+C */
                        len = read(work->workData.ioData.fd,
                                   work->workData.ioData.buf,
                                   work->workData.ioData.len);
+                       if ( len == 0 && work->workData.ioData.len != 0 ) {
+                           /* Given the following scenario:
+                            *     - a console handler has been registered that handles Ctrl+C
+                            *       events.
+                            *     - we've not tweaked the 'console mode' settings to turn on
+                            *       ENABLE_PROCESSED_INPUT.
+                            *     - we're blocked waiting on input from standard input.
+                            *     - the user hits Ctrl+C.
+                            *
+                            * The OS will invoke the console handler (in a separate OS thread),
+                            * and the above read() (i.e., under the hood, a ReadFile() op) returns
+                            * 0, with the error set to ERROR_OPERATION_ABORTED. We don't
+                            * want to percolate this non-EOF condition too far back up, but ignore
+                            * it. However, we do want to give the RTS an opportunity to deliver the
+                            * console event.
+                            * 
+                            * Hence, we set 'errorCode' to (-2), which we then look out for in
+                            * GHC.Conc.asyncRead.
+                            */
+                           dw = GetLastError();
+                           if ( dw == ERROR_OPERATION_ABORTED ) {
+                               /* Only do the retry when dealing with the standard input handle. */
+                               HANDLE h  = (HANDLE)GetStdHandle(STD_INPUT_HANDLE);
+                               if ( _get_osfhandle(work->workData.ioData.fd) == (long)h ) {
+                                   errCode = (DWORD)-2;
+                               }
+                           }
+                       }
                        if (len == -1) { errCode = errno; }
                    }
                    complData = work->workData.ioData.buf;