2
votes

In order to facilitate reading a user-specified file while handling bad input, I've written this function:

readGetFile :: IO FilePath -> IO String
readGetFile getName =
    catch (getName >>= readFile) (\e->do
        let err = show (e :: IOException)
        printf "Couldn't open file %v. Please enter a valid filename.\n" err
        readGetFile getName >>= return)

This question is not about this function, and it is just used to illustrate a point.

If I call this function from GHCi, and eventually enter a good filename, it works as expected. However, if I try to terminate it early by hitting CTRL-C, strange things happen.

*Input> readGetFile getLine
{BadFile.jpeg
}Couldn't open file BadFile.jpeg: openFile: does not exist (No such file or directory). Please enter a valid filename.
{^C}Couldn't open file <stdin>: hGetLine: end of file. Please enter a valid filename.
{^C}CouInterrupted.
ldn't p*Input> e file <stdin>: hGetLine: end of file. Please enter a valid filename.
{^C}Couldn't open file <stdin>: hGetLine: end of file. Please enter a valid filename.

*Input> {
}Couldn't open file : openFile: does not exist (No such file or directory). Please enter a valid filename.

Note that text in {} is entered by me (and I did not type {}). {^C} is me just hitting CTRL-C.

Obviously, the recursion is continuing to run and read from stdin, even as GHCi tries to resume running.

How can I terminate this loop from within GHCi?

Note: I have done hSetBuffering stdout NoBuffering. This question is not about the implementation of readGetFile and an answer must answer the bolded question above. An answer may provide a better way of implementing readGetFile in addition if the answer provides reasoning for why it is nicer in respect to keyboard interrupts while still achieving the desired goal.

This apparently only occurs on Windows. I am on Windows running a default installation of Haskell Platform Core.

1

1 Answers

1
votes

It looks like this is a known issue in GHCi.

Thanks for the report, this seems to be an issue with the mingw-w64 runtime. I haven't narrowed it down yet, but essentially something seems to be eating the events and it never reaches ghci.

CtrlHandler is never called.

The following example shows the issue. It works when compiled using the Microsoft compiler but doesn't with mingw-w64's GHC.

#include <windows.h>
#include <stdio.h>

BOOL CtrlHandler( DWORD fdwCtrlType ) {   switch( fdwCtrlType )   {
    // Handle the CTRL-C signal.
    case CTRL_C_EVENT:
      printf( "Ctrl-C event\n\n" );
      Beep( 750, 300 );
      return( TRUE );

    // CTRL-CLOSE: confirm that the user wants to exit.
    case CTRL_CLOSE_EVENT:
      Beep( 600, 200 );
      printf( "Ctrl-Close event\n\n" );
      return( TRUE );

    // Pass other signals to the next handler.
    case CTRL_BREAK_EVENT:
      Beep( 900, 200 );
      printf( "Ctrl-Break event\n\n" );
      return FALSE;

    case CTRL_LOGOFF_EVENT:
      Beep( 1000, 200 );
      printf( "Ctrl-Logoff event\n\n" );
      return FALSE;

    case CTRL_SHUTDOWN_EVENT:
      Beep( 750, 500 );
      printf( "Ctrl-Shutdown event\n\n" );
      return FALSE;

    default:
      return FALSE;   } }

int main( void ) {   if( SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE ) )   {
    printf( "\nThe Control Handler is installed.\n" );
    printf( "\n -- Now try pressing Ctrl+C or Ctrl+Break, or" );
    printf( "\n    try logging off or closing the console...\n" );
    printf( "\n(...waiting in a loop for events...)\n\n" );

    while( 1 ){ }   }   else   {
    printf( "\nERROR: Could not set control handler");
    return 1;   } return 0; }

This also doesn't work in an msys2 console, even though it appears to. In the case of msys2 bash is disabling ENABLE_PROCESSED_INPUT so that CTRL+C is not processed by the system and is reported as a input stream. Bash is then just terminating the process by force.

In the case of GHCi, something does clearly happen with the runtime since it suddenly starts allocating a large amount of memory and enters some kind of deadlock. So there are two separate issues here.

(Note: I did not create this issue. It appears to have been first reported 2017-03-01. The issue is currently labeled as not an x86 issue but as effecting unknown/multiple architectures.)