Ticket #4942 (new bug)

Opened 2 years ago

Last modified 9 months ago

GHC.ConsoleHandler does not call back application when Close button is pressed

Reported by: Amatic Owned by:
Priority: low Milestone: 7.6.2
Component: GHC API Version: 6.12.3
Keywords: Cc: lightwing15@…, fryguybob@…
Operating System: Windows Architecture: x86
Type of failure: Incorrect result at runtime Difficulty:
Test Case: Blocked By:
Blocking: Related Tickets:

Description

The test program below is not called back by GHC.ConsoleHandler? when the Close button is pressed during the threadDelay call. A message is written to console_event.log as expected when Ctrl-C or Ctrl-Break is pressed, but not when the Close button is pressed.

import Control.Concurrent (threadDelay)
import GHC.ConsoleHandler
import System.IO

onConsoleEventReceived :: ConsoleEvent -> IO ()
onConsoleEventReceived event = withFile "console_event.log" AppendMode $ \ file -> do
  hPutStrLn file $ case event of
    ControlC  -> "Received Ctrl-C event"
    Break     -> "Received Ctrl-Break event"
    Close     -> "Received X button event"
    _         -> "Received other console event"
  hFlush file
    
main :: IO ()
main = installHandler (Catch onConsoleEventReceived) >> threadDelay (20*1000000)

The host OS is Windows XP SP3.

Please let me know if you need any more info.

Change History

Changed 2 years ago by igloo

  • milestone set to 7.2.1

Thanks for the report.

Changed 2 years ago by fryguybob

It looks like this behavior is by design, looking in rts/win32/ConsoleHandler.c:

static BOOL WINAPI generic_handler(DWORD dwCtrlType)
{
    /* Ultra-simple -- up the counter + signal a switch. */
    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 (!deliver_event) return TRUE;

#if defined(THREADED_RTS)
        sendIOManagerEvent((StgWord8) ((dwCtrlType<<1) | 1));
#else
        if ( stg_pending_events < N_PENDING_EVENTS ) {
            stg_pending_buf[stg_pending_events] = dwCtrlType;
            stg_pending_events++;
        }

        // we need to wake up awaitEvent()
        abandonRequestWait();
#endif
        return TRUE;
    }
}

To get the behavior desired CTRL_CLOSE_EVENT would have to be handled before generic_handler returns and have it return FALSE to which windows responds by terminating the process or never return and ensure that rts_ConsoleHandlerDone terminates the process in the cases of CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, or CTRL_SHUTDOWN_EVENT. I suppose there could be situations where returning TRUE could be desired but the API given in GHC.ConsoleHandler doesn't provide for that and I'm not sure it is particularly friendly to users (see remarks in the  MSDN docs).

I don't know enough about the implications of not returning from generic_handler and having rts_ConsoleHandlerDone terminate the process, but I think I will experiment a little and see what happens.

Changed 2 years ago by fryguybob

  • cc fryguybob@… added

Changed 2 years ago by fryguybob

I tried making the generic_handler handle CTRL_CLOSE_EVENT like the other events, except not returning when it encountering CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, or CTRL_SHUTDOWN_EVENT and making rts_ConsoleHandlerDone end the process with stg_exit(EXIT_INTERRUPTED). This appeared to work nicely for the code above when not using -threaded. But there are still some of things to work out:

  • If there isn't a handler installed, rts_ConsoleHandlerDone isn't called. So in this case we should return FALSE from generic_handler. I don't think there is a reliable way to detect this right now.
  • The threaded runtime doesn't call rts_ConsoleHandlerDone but I think that it could be easily changed.
  • What is the correct way to not return from generic_handler (in my experiment I did Sleep(INFINITE))?
  • What is the correct way to terminate the process in rts_ConsoleHandlerDone? Perhaps generic_handler should wait on an event that is always set when there is no handler and gets set in rts_ConsoleHandlerDone otherwise.

Anyway, perhaps someone more experience than me can chime in.

Changed 2 years ago by simonmar

I think generic_handler is only run if a handler is installed. Although looking at the code that doesn't seem to apply if a handler is installed and then uninstalled again. That looks like a bug, becuase then stg_pending_buf will fill up.

You'll notice that rts_ConsoleHandlerDone is completely bogus (see my comment). However, we could do something similar at the Haskell level - look at the way exit is handled in GHC.TopHandler. Having the handler automatically exit the process after running is an interesting idea, but I wonder if it ought to be under the control of the handler (perhaps by returning a Bool?).

Changed 16 months ago by igloo

  • priority changed from normal to low
  • milestone changed from 7.4.1 to 7.6.1

Changed 9 months ago by igloo

  • milestone changed from 7.6.1 to 7.6.2
Note: See TracTickets for help on using tickets.