[project @ 2005-10-21 15:14:06 by simonmar]
[ghc-hetmet.git] / ghc / rts / win32 / ConsoleHandler.c
1 /*
2  * Console control handler support.
3  *
4  */
5 #include "Rts.h"
6 #include <windows.h>
7 #include "ConsoleHandler.h"
8 #include "SchedAPI.h"
9 #include "Schedule.h"
10 #include "RtsUtils.h"
11 #include "RtsFlags.h"
12 #include "AsyncIO.h"
13 #include "RtsSignals.h"
14
15 extern int stg_InstallConsoleEvent(int action, StgStablePtr *handler);
16
17 static BOOL WINAPI shutdown_handler(DWORD dwCtrlType);
18 static BOOL WINAPI generic_handler(DWORD dwCtrlType);
19
20 static rtsBool deliver_event = rtsTrue;
21 static StgInt console_handler = STG_SIG_DFL;
22
23 static HANDLE hConsoleEvent = INVALID_HANDLE_VALUE;
24
25 #define N_PENDING_EVENTS 16
26 StgInt stg_pending_events = 0;           /* number of undelivered events */
27 DWORD stg_pending_buf[N_PENDING_EVENTS]; /* their associated event numbers. */
28
29 /*
30  * Function: initUserSignals()
31  *
32  * Initialize the console handling substrate.
33  */
34 void
35 initUserSignals(void)
36 {
37     stg_pending_events = 0;
38     console_handler = STG_SIG_DFL;
39     if (hConsoleEvent == INVALID_HANDLE_VALUE) {
40         hConsoleEvent = 
41             CreateEvent ( NULL,  /* default security attributes */
42                           TRUE,  /* manual-reset event */
43                           FALSE, /* initially non-signalled */
44                           NULL); /* no name */
45     }
46     return;
47 }
48
49 /*
50  * Function: shutdown_handler()
51  *
52  * Local function that performs the default handling of Ctrl+C kind
53  * events; gently shutting down the RTS
54  *
55  * To repeat Signals.c remark -- user code may choose to override the
56  * default handler. Which is fine, assuming they put back the default
57  * handler when/if they de-install the custom handler.
58  * 
59  */
60 static BOOL WINAPI shutdown_handler(DWORD dwCtrlType)
61 {
62     switch (dwCtrlType) {
63     
64     case CTRL_CLOSE_EVENT:
65         /* see generic_handler() comment re: this event */
66         return FALSE;
67     case CTRL_C_EVENT:
68     case CTRL_BREAK_EVENT:
69
70         // If we're already trying to interrupt the RTS, terminate with
71         // extreme prejudice.  So the first ^C tries to exit the program
72         // cleanly, and the second one just kills it.
73         if (interrupted) {
74             stg_exit(EXIT_INTERRUPTED);
75         } else {
76             interruptStgRts();
77             /* Cheesy pulsing of an event to wake up a waiting RTS thread, if any */
78             abandonRequestWait();
79             resetAbandonRequestWait();
80         }
81         return TRUE;
82
83         /* shutdown + logoff events are not handled here. */
84     default:
85         return FALSE;
86     }
87 }
88
89
90 /*
91  * Function: initDefaultHandlers()
92  *
93  * Install any default signal/console handlers. Currently we install a
94  * Ctrl+C handler that shuts down the RTS in an orderly manner.
95  */
96 void initDefaultHandlers(void)
97 {
98     if ( !SetConsoleCtrlHandler(shutdown_handler, TRUE) ) {
99         errorBelch("warning: failed to install default console handler");
100     }
101 }
102
103
104 /*
105  * Function: blockUserSignals()
106  *
107  * Temporarily block the delivery of further console events. Needed to
108  * avoid race conditions when GCing the stack of outstanding handlers or
109  * when emptying the stack by running the handlers.
110  * 
111  */
112 void
113 blockUserSignals(void)
114 {
115     deliver_event = rtsFalse;
116 }
117
118
119 /*
120  * Function: unblockUserSignals()
121  *
122  * The inverse of blockUserSignals(); re-enable the deliver of console events.
123  */
124 void
125 unblockUserSignals(void)
126 {
127     deliver_event = rtsTrue;
128 }
129
130
131 /*
132  * Function: awaitUserSignals()
133  *
134  * Wait for the next console event. Currently a NOP (returns immediately.)
135  */
136 void awaitUserSignals(void)
137 {
138     return;
139 }
140
141
142 /*
143  * Function: startSignalHandlers()
144  *
145  * Run the handlers associated with the stacked up console events. Console
146  * event delivery is blocked for the duration of this call.
147  */
148 void startSignalHandlers(void)
149 {
150     StgStablePtr handler;
151
152     if (console_handler < 0) {
153         return;
154     }
155     blockUserSignals();
156     
157     handler = deRefStablePtr((StgStablePtr)console_handler);
158     while (stg_pending_events > 0) {
159         stg_pending_events--;
160         scheduleThread(&MainCapability,
161             createIOThread(&MainCapability,
162                            RtsFlags.GcFlags.initialStkSize, 
163                            rts_apply(&MainCapability,
164                                      (StgClosure *)handler,
165                                      rts_mkInt(&MainCapability,
166                                                stg_pending_buf[stg_pending_events]))));
167     }
168     unblockUserSignals();
169 }
170
171
172 /*
173  * Function: markSignalHandlers()
174  *
175  * Evacuate the handler stack. _Assumes_ that console event delivery
176  * has already been blocked.
177  */
178 void markSignalHandlers (evac_fn evac)
179 {
180     if (console_handler >= 0) {
181         StgPtr p = deRefStablePtr((StgStablePtr)console_handler);
182         evac((StgClosure**)&p);
183     }
184 }
185
186
187 /*
188  * Function: handleSignalsInThisThread()
189  * 
190  * Have current (OS) thread assume responsibility of handling console events/signals.
191  * Currently not used (by the console event handling code.)
192  */
193 void handleSignalsInThisThread(void)
194 {
195     return;
196 }
197
198 /* 
199  * Function: generic_handler()
200  *
201  * Local function which handles incoming console event (done in a sep OS thread),
202  * recording the event in stg_pending_events. 
203  */
204 static BOOL WINAPI generic_handler(DWORD dwCtrlType)
205 {
206     /* Ultra-simple -- up the counter + signal a switch. */
207     switch(dwCtrlType) {
208     case CTRL_CLOSE_EVENT:
209         /* Don't support the delivery of this event; if we
210          * indicate that we've handled it here and the Haskell handler
211          * doesn't take proper action (e.g., terminate the OS process),
212          * the user of the app will be unable to kill/close it. Not
213          * good, so disable the delivery for now.
214          */
215         return FALSE;
216     default:
217         if (!deliver_event) return TRUE;
218
219         if ( stg_pending_events < N_PENDING_EVENTS ) {
220             stg_pending_buf[stg_pending_events] = dwCtrlType;
221             stg_pending_events++;
222         }
223         /* Cheesy pulsing of an event to wake up a waiting RTS thread, if any */
224         abandonRequestWait();
225         resetAbandonRequestWait();
226         return TRUE;
227     }
228 }
229
230
231 /*
232  * Function: rts_InstallConsoleEvent()
233  *
234  * Install/remove a console event handler.
235  */
236 int
237 rts_InstallConsoleEvent(int action, StgStablePtr *handler)
238 {
239     StgInt previous_hdlr = console_handler;
240
241     switch (action) {
242     case STG_SIG_IGN:
243         console_handler = STG_SIG_IGN;
244         if ( !SetConsoleCtrlHandler(NULL, TRUE) ) {
245             errorBelch("warning: unable to ignore console events");
246         }
247         break;
248     case STG_SIG_DFL:
249         console_handler = STG_SIG_IGN;
250         if ( !SetConsoleCtrlHandler(NULL, FALSE) ) {
251             errorBelch("warning: unable to restore default console event handling");
252         }
253         break;
254     case STG_SIG_HAN:
255         console_handler = (StgInt)*handler;
256         if ( previous_hdlr < 0 ) {
257           /* Only install generic_handler() once */
258           if ( !SetConsoleCtrlHandler(generic_handler, TRUE) ) {
259             errorBelch("warning: unable to install console event handler");
260           }
261         }
262         break;
263     }
264     
265     if (previous_hdlr == STG_SIG_DFL || 
266         previous_hdlr == STG_SIG_IGN) {
267         return previous_hdlr;
268     } else {
269         *handler = (StgStablePtr)previous_hdlr;
270         return STG_SIG_HAN;
271     }
272 }
273
274 /*
275  * Function: rts_HandledConsoleEvent()
276  *
277  * Signal that a Haskell console event handler has completed its run.
278  * The explicit notification that a Haskell handler has completed is 
279  * required to better handle the delivery of Ctrl-C/Break events whilst
280  * an async worker thread is handling a read request on stdin. The 
281  * Win32 console implementation will abort such a read request when Ctrl-C
282  * is delivered. That leaves the worker thread in a bind: should it 
283  * abandon the request (the Haskell thread reading from stdin has been 
284  * thrown an exception to signal the delivery of Ctrl-C & hence have 
285  * aborted the I/O request) or simply ignore the aborted read and retry?
286  * (the Haskell thread reading from stdin isn't concerned with the
287  * delivery and handling of Ctrl-C.) With both scenarios being
288  * possible, the worker thread needs to be told -- that is, did the
289  * console event handler cause the IO request to be abandoned? 
290  *
291  */
292 void
293 rts_ConsoleHandlerDone(int ev)
294 {
295     if ( (DWORD)ev == CTRL_BREAK_EVENT ||
296          (DWORD)ev == CTRL_C_EVENT ) {
297         /* only these two cause stdin system calls to abort.. */
298         SetEvent(hConsoleEvent); /* event is manual-reset */
299         Sleep(0); /* yield */
300         ResetEvent(hConsoleEvent); /* turn it back off again */
301     }
302 }
303
304 /*
305  * Function: rts_waitConsoleHandlerCompletion()
306  *
307  * Esoteric entry point used by worker thread that got woken
308  * up as part Ctrl-C delivery.
309  */
310 int
311 rts_waitConsoleHandlerCompletion()
312 {
313     /* As long as the worker doesn't need to do a multiple wait,
314      * let's keep this HANDLE private to this 'module'.
315      */
316     return (WaitForSingleObject(hConsoleEvent, INFINITE) == WAIT_OBJECT_0);
317 }