Surprising Behavior When Handling Console Control Signals

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
[Shambles]
Posts: 100
Joined: 20 May 2014, 21:24

Surprising Behavior When Handling Console Control Signals

18 Sep 2017, 04:37

I have been attempting to write a command-line application in AutoHotkey.

The code below demonstrates the problem I am having:

Code: Select all

global Signal := "off"  ; Prevent "" from being handled as 0.

HandlerRoutine(dwCtrlType)
{
    Signal := dwCtrlType
    return true
}

DllCall("AttachConsole", "int", -1)
DllCall("SetConsoleCtrlHandler", "uint", RegisterCallback("HandlerRoutine"), "int", 1)

CheckForSignals()
{
    if (Signal == 0)       ; CTRL_C_EVENT
    {
        throw Exception("Ctrl+C was pressed")
    }
    else if (Signal == 1)  ; CTRL_BREAK_EVENT
    {
        throw Exception("Ctrl+Break was pressed")
    }
    else if (Signal == 2)  ; CTRL_CLOSE_EVENT
    {
        throw Exception("console window closed")
    }
}

Main()
{
    while (true)
    {
        try
        {
            CheckForSignals()
        }
        catch Ex
        {
            MsgBox % "Intercepted: " . Ex["Message"]
            Signal := "off"
        }
    }
}

Main()
I compile it with AutoHotkey. Then I edit its PE header to change the Subsystem field from WINDOWS_GUI (2) to WINDOWS_CUI (3) using a tool like LordPE or a hex editor like Frhed (the little endian 16-bit integer is at offset 372 or 0x174). This causes the standard streams to refer to the console it runs in and prevents Command Prompt from receiving a copy of its input. In other words, it causes it to behave like a normal command-line application.

I expect this code to react to Ctrl+C and Ctrl+Break being pressed.

When HandlerRoutine returns true, the program works as expected sometimes and crashes others (sometimes with out-of-bounds memory accesses). When HandlerRoutine returns false, the program appears to ignore my code and exit.

Is there some alternate way to write this to make it possible to reliably
  • notice Ctrl+C was pressed, run whatever code I want, and not exit
  • notice Ctrl+Break was pressed, run whatever code I want, then exit
  • notice the console window closed, run whatever code I want, then exit
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: Surprising Behavior When Handling Console Control Signals

18 Sep 2017, 10:20

[Shambles] wrote:When HandlerRoutine returns true, the program works as expected sometimes and crashes others (sometimes with out-of-bounds memory accesses). When HandlerRoutine returns false, the program appears to ignore my code and exit.
The console ctrl handler function runs on another thread. Use Lexikos' RegisterSyncCallback instead and AutoHotkey won't be unstable after your function has been called.

(Also, I recommend just actually doing everything in the HandlerRoutine function - once it returns, your CheckForSignals function isn't guaranteed to be called, which you can see if you place a simple MsgBox in HandlerRoutine and close the Command Prompt window by clicking on the X. When I do just that on Windows 10, I see HandlerRoutine displaying a message, but never any message from the catch in the main loop.)
[Shambles]
Posts: 100
Joined: 20 May 2014, 21:24

Re: Surprising Behavior When Handling Console Control Signals

18 Sep 2017, 14:36

qwerty12 wrote:The console ctrl handler function runs on another thread. Use Lexikos' RegisterSyncCallback instead and AutoHotkey won't be unstable after your function has been called.

(Also, I recommend just actually doing everything in the HandlerRoutine function - once it returns, your CheckForSignals function isn't guaranteed to be called, which you can see if you place a simple MsgBox in HandlerRoutine and close the Command Prompt window by clicking on the X. When I do just that on Windows 10, I see HandlerRoutine displaying a message, but never any message from the catch in the main loop.)
I can look into RegisterSyncCallback, but I am much less confident about your other suggested changes.

I lack a sound mental model of how signal handling works at the assembly level. I know that on Unix the recommended way to handle them is to only write a single global variable, as I have done, because only certain procedures are guaranteed to work correctly inside a signal handler. Obviously, this is not Unix. There can be problems with atomicity, but surely reading or writing a single global variable is an atomic operation even in AutoHotkey on Windows. Nothing but HandlerRoutine and the exception handler(s) will read or write that variable. I am at a loss for how to make the signal handler simpler or more reliable.

I know from experimentation that I cannot throw an exception from within the signal handler, and I need to unwind the stack when a signal occurs in this program.

Assuming unimaginable behavior does not occur, like the signal handler automatically killing the main thread, my CheckForSignals function should be guaranteed to be called because the main thread is doing nothing but that in an infinite loop. That was the simplest approximation of "if the user tells you to stop, back out of whatever you were doing in a controlled way, then wait for more instructions if it was Ctrl+C, else exit the program cleanly" that I could think of for my example code. That is what I am actually trying to do. The cleanup will be considerably more involved than displaying a message box in the actual program. I was originally testing this by writing to stdout, but it had the same problems and would have made my example code longer. What I knew (and conveyed to you above) about Unix signal handlers made me afraid to attempt cleanup in the signal handler.

This should be a common need for command-line applications. I know they are not frequently written in AutoHotkey, but I have my reasons.
Update:
I read about RegisterSyncCallback. Will it even work for a command-line application? I am uncertain if a command-line application receives window messages.
Update:
I tested these suggestions. Switching from RegisterCallback to RegisterSyncCallback does seem to remove all unreliable behavior from my command-line application.

It can respond reliably to Ctrl+C and Ctrl+Break being pressed.

It cannot respond to the console window being closed, even though that is supposed to be possible. Placing the code inside the signal handler did not change this. If anyone knows how to solve this problem, please tell us the solution.

Is RegisterCallback always unreliable? Is there a reason RegisterSyncCallback is not built-in or the default behavior?
Last edited by [Shambles] on 18 Sep 2017, 21:54, edited 1 time in total.
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: Surprising Behavior When Handling Console Control Signals

18 Sep 2017, 21:53

I lack a sound mental model of how signal handling works at the assembly level. I know that on Unix the recommended way to handle them is to only write a single global variable, as I have done, because only system calls are guaranteed to work correctly inside a signal handler. Obviously, this is not Unix. There can be problems with atomicity, but surely reading or writing a single global variable is an atomic operation even in AutoHotkey on Windows. Nothing but HandlerRoutine and the exception handler(s) will read or write that variable. I am at a loss for how to make the signal handler simpler or more reliable.
My knowledge of *NIX signal handlers isn't much, so I can't speak there, but I will say that with RegisterSyncCallback, your handler function will run on the same thread AutoHotkey is running rest of the script on so there shouldn't be any atomicity problems I think in that case.
Assuming unimaginable behavior does not occur, like the signal handler automatically killing the main thread, my CheckForSignals function should be guaranteed to be called because the main thread is doing nothing but that in an infinite loop. That was the simplest approximation of "if the user tells you to stop, back out of whatever you were doing in a controlled way, then wait for more instructions if it was Ctrl+C, else exit the program cleanly" that I could think of for my example code. That is what I am actually trying to do. The cleanup will be considerably more involved than displaying a message box in the actual program. I was originally testing this by writing to stdout, but it had the same problems and would have made my example code longer. What I knew (and conveyed to you above) about Unix signal handlers made me afraid to attempt cleanup in the signal handler.
Not being knowledgeable about Windows signal handlers, either, all I can post are my observations and an exception was not thrown every single time I closed the command prompt window hosting your script (but I did see an exception message when I hit Ctrl+C/Break). The only consistent thing I did see was the message displayed in the handler function itself. But saying that, it could be me / a Windows 10 thing, as I just tried the same compiled script executable in Windows 2000 and when I close the window there, your script does throw an exception to that effect.
I read about RegisterSyncCallback. Will it even work for a command-line application? I am uncertain if a command-line application receives window messages.
Yes. Usually, a command-line application wouldn't have a message loop, but AutoHotkey always creates a standard message loop behind the scenes no matter what subsystem the script is running under.

(Admittedly, I'm not sure what effect having the console handler call to a function on your main thread has. Is it always guaranteed to interrupt the script? If the console handler absolutely must be run on the original separate thread "KernelBase.dll!CtrlRoutine" creates, then I recommend using MCode or something for that particular function.
[Shambles]
Posts: 100
Joined: 20 May 2014, 21:24

Re: Surprising Behavior When Handling Console Control Signals

18 Sep 2017, 22:13

qwerty12 wrote:Not being knowledgeable about Windows signal handlers, either, all I can post are my observations and an exception was not thrown every single time I closed the command prompt window hosting your script (but I did see an exception message when I hit Ctrl+C/Break). The only consistent thing I did see was the message displayed in the handler function itself. But saying that, it could be me / a Windows 10 thing, as I just tried the same compiled script executable in Windows 2000 and when I close the window there, your script does throw an exception to that effect.
As I just finished writing above, I have been reliably unable to get my script to react to the console window being closed. I would be surprised if this behavior was Windows version specific. The documentation for HandlerRoutine callback function indicates CTRL_C_EVENT (0), CTRL_BREAK_EVENT (1), and CTRL_CLOSE_EVENT (2) should be possible for console applications to handle, though CTRL_LOGOFF_EVENT (5) and CTRL_SHUTDOWN_EVENT (6) are not. It makes no mention of the Windows version effecting the behavior. For what it is worth, I am testing this on Windows 7.
qwerty12 wrote:Admittedly, I'm not sure what effect having the console handler call to a function on your main thread has. Is it always guaranteed to interrupt the script? If the console handler absolutely must be run on the original separate thread "KernelBase.dll!CtrlRoutine" creates, then I recommend using MCode or something for that particular function.
I did not understand this paragraph.
just me
Posts: 9450
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Surprising Behavior When Handling Console Control Signals

19 Sep 2017, 00:52

The CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT signals give the process an opportunity to clean up before termination. A HandlerRoutine can perform any necessary cleanup, then take one of the following actions:
  • Call the ExitProcess function to terminate the process.
  • Return FALSE. If none of the registered handler functions returns TRUE, the default handler terminates the process.
  • Return TRUE. In this case, no other handler functions are called and the system terminates the process.
HandlerRoutine -> Remarks
[Shambles]
Posts: 100
Joined: 20 May 2014, 21:24

Re: Surprising Behavior When Handling Console Control Signals

19 Sep 2017, 02:25

just me wrote:
The CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT signals give the process an opportunity to clean up before termination. A HandlerRoutine can perform any necessary cleanup, then take one of the following actions:
  • Call the ExitProcess function to terminate the process.
  • Return FALSE. If none of the registered handler functions returns TRUE, the default handler terminates the process.
  • Return TRUE. In this case, no other handler functions are called and the system terminates the process.
HandlerRoutine -> Remarks
Upon careful reading, that seems to imply that after the signal handler(s) return(s) from handling a CTRL_CLOSE_EVENT the process is immediately terminated. There is no opportunity for the main thread to do anything. I tested it and can confirm that behavior.

The cleanup code for CTRL_CLOSE_EVENT must be in the signal handler or a procedure it calls. This reliably works now that I am using RegisterSyncCallback. It had reliably not worked when I was using RegisterCallback.

I believed there would be 5 to 20 seconds for the main thread to run due to careless reading of discussions like this. :oops:

Thank you everyone for the help! :D

I suppose the only unsolved mystery is why RegisterCallback is unreliable and why it has not been replaced with RegisterSyncCallback.
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: Surprising Behavior When Handling Console Control Signals

19 Sep 2017, 05:49

[Shambles] wrote:
qwerty12 wrote:Admittedly, I'm not sure what effect having the console handler call to a function on your main thread has. Is it always guaranteed to interrupt the script? If the console handler absolutely must be run on the original separate thread "KernelBase.dll!CtrlRoutine" creates, then I recommend using MCode or something for that particular function.
I did not understand this paragraph.
Sorry, I meant something along the lines of if having the console handler run on the same thread as the rest of your script wasn't desirable, then you'd have to have to use MCode for the handler function. Also: I'm not particularly in the know about AHK threads (my usual approach is to add Critical if something mustn't be interrupted and hope it works), so my train of thought was as there's an infinite loop going on at the bottom, would it be always interrupted? But no matter with this rubbish as it seems to work for you.
[Shambles] wrote:I suppose the only unsolved mystery is why RegisterCallback is unreliable and why it has not been replaced with RegisterSyncCallback.
As I understand it, it's not RegisterCallback itself that's unreliable, it's the running of AutoHotkey code on a thread other than the main one. Lexikos explains that (unsurprisingly) better than I ever could. As for why it's not the default, that's presumably because it's slower, having to force the code to run on the main thread through messages (I've only encountered two cases where it was needed, and I've used RegisterCallback quite a few times). I do hope it becomes an option in V2, however!
[Shambles]
Posts: 100
Joined: 20 May 2014, 21:24

Re: Surprising Behavior When Handling Console Control Signals

19 Sep 2017, 16:58

qwerty12 wrote:Sorry, I meant something along the lines of if having the console handler run on the same thread as the rest of your script wasn't desirable, then you'd have to have to use MCode for the handler function. Also: I'm not particularly in the know about AHK threads (my usual approach is to add Critical if something mustn't be interrupted and hope it works), so my train of thought was as there's an infinite loop going on at the bottom, would it be always interrupted? But no matter with this rubbish as it seems to work for you.
I do not care which thread the console handler runs on so long as it behaves correctly.

It might help if I explain why the example code is structured as it is.

Imagine you are writing something like
  • a compiler
  • an interpreter with a listener (a.k.a. REPL)
Each of these might need to be aborted:
  • compiler -- infinite Lisp-like macro evaluation, infinite C++-like template instantiation, or time-consuming optimization
  • interpreter -- infinite loop or infinite recursion
I chose these examples because they can be used to illustrate the different kinds of behavior you might want.

If the compiler is taking longer than you can tolerate, you want the process to terminate gracefully. Ctrl+C and Ctrl+Break should do that.

If an algorithm in the interpreter stops making progress, you will normally want the algorithm, not the process, to terminate gracefully. Ctrl+C should do that. You might want debugging information (e.g. a core dump or stack trace) with that to try to find the input that causes the misbehavior. On Windows, Ctrl+Break should do that.

In both cases, if you close the console window, the you want the process to terminate gracefully.

Terminating a process gracefully is usually simple. The operating system will free most resources automatically. You might need the signal to cause some code to run to ensure files remain in a sane state so that the program can run successfully on them in the future, but the need for that should be avoided when possible. That code will not get a chance to run if power is interrupted, the process is forcibly terminated, or a similar situation occurs.

Terminating an algorithm in an interpreter is usually not simple. You might want to unwind the evaluation stack, which usually uses the real call stack, to get to a point where the problem can be handled and the interpreter can start listening for input again. You might not want to unwind the interpreted call stack, which might not be the same as the evaluation stack, at the same time because you might need it to display a stack trace. You might need to execute additional code when unwinding the interpreted call stack, such as when handling dynamic-wind in a Scheme interpreter, which ensures file handles do not leak. All of this requires the thread that the interpreter runs in, not the thread the signal handler runs in, to be in control.

That is why the main thread needed to poll for signals.

If all of this seems dreadfully academic or unlikely, rest assured that it is not. CLI applications are much easier to automate than GUI applications. Command-line applications that can automate GUI applications are very useful to system administrators. Command-line applications with interpreter-like behavior are useful for more than interpreters. For example, Emacs and similar editors that communicate through the standard streams (stdin, stdout, and stderr) can use command-line applications written in AutoHotkey to automate GUI applications.

It is a pity that AutoHotkey makes creating command-line applications so difficult.
qwerty12 wrote:As I understand it, it's not RegisterCallback itself that's unreliable, it's the running of AutoHotkey code on a thread other than the main one. Lexikos explains that (unsurprisingly) better than I ever could. As for why it's not the default, that's presumably because it's slower, having to force the code to run on the main thread through messages (I've only encountered two cases where it was needed, and I've used RegisterCallback quite a few times). I do hope it becomes an option in V2, however!
I spent some time trying to imagine a situation where RegisterCallback would run in the same thread (i.e. where it would not be unreliable).

I believe it would be safe to use in DllCalls that only perform processing. RegisterSyncCallback should be safe in the same situations.

I have never used DllCalls like that because it is safer to write processing code in AutoHotkey. AutoHotkey might silently corrupt the values of my variables because of its weak typing ((1/0) = (42 * "weasel") = Func("Format") = "" = 0), but at least it will not corrupt the memory of my process. I only use DllCalls for I/O AutoHotkey does not support, which rarely, if ever, requires a callback, and for setting up event handlers, where RegisterCallback is definitely unreliable. For my purposes, it appears to never be safe to use. I suspect I am not alone.

The efficiency lost by replacing RegisterCallback with RegisterSyncCallback is insignificant, but the gain in safety is significant.

I worry that it may become incompatible with v2 if it is not included in it.
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: Surprising Behavior When Handling Console Control Signals

19 Sep 2017, 20:31

[Shambles] wrote:It might help if I explain why the example code is structured as it is.[...]
I'm not a programmer, but now I understand the reasons behind your implementation, because of your detailed explanations. Thank you for that.
I spent some time trying to imagine a situation where RegisterCallback would run in the same thread (i.e. where it would not be unreliable).
Honestly speaking, from my limited experience, the callback functions I've dealt with do usually run on the same thread a function set it up on. The three exceptions I've seen are IMMNotificationClient (I was lucky that I had to reinitialise COM on the new thread or I wouldn't have had an idea what was going on), RegisterWaitForSingleObject and, well, this.
The efficiency lost by replacing RegisterCallback with RegisterSyncCallback is insignificant, but the gain in safety is significant.
I'm still of the opinion that a built-in choice, like "Fast" is, would be great, but not a total replacement. RegisterSyncCallback doesn't support certain things that RegisterCallback does, so I couldn't see it happening anyway.

EDIT:

I won't take up any more of your time now that you have a solution and better arguments than I do, but, yes, I do agree that at the very least a link to RegisterSyncCallback would be nice to have in the documentation (if not the eventual integration of RegisterSyncCallback into AutoHotkey itself). Lexikos has already done a great job explaining its use, I'm sure that could be adapted for the documentation.
Last edited by qwerty12 on 20 Sep 2017, 10:08, edited 1 time in total.
[Shambles]
Posts: 100
Joined: 20 May 2014, 21:24

Re: Surprising Behavior When Handling Console Control Signals

20 Sep 2017, 04:34

You are a programmer. You write programs. Your knowledge indicates you are experienced, and your solution to my problem indicates you are not bad at it.

People denying themselves or others the title is used as an excuse to make terrible decisions, like designing a programming language "for non-programmers", which results in programming languages that are designed to not be used (or less kindly, designed to be unusable).
qwerty12 wrote:I'm still of the opinion that a built-in choice, like "Fast" is, would be great, but not a total replacement. RegisterSyncCallback doesn't support certain things that RegisterCallback does, so I couldn't see it happening anyway.
While I have a strong distaste for anything being unsafe without adequate justification (like AutoHotkey's dangerous and inefficient weak typing), I could live with this. Perhaps something that synchronous callbacks cannot do is necessary enough (not infinitesimal speed gains) to make non-synchronous callbacks worth retaining. I do believe it should be built-in so that it will be maintained. There should be a warning in the documentation that non-synchronous uses can cause the behavior seen here and the reason for it so that programmers can determine if the problem will affect their code. Synchronous should be the default because it is safer.

Lexicos' own code, which is what I based mine on, is unreliable because of this problem.

No one benefits from unnecessary unsafety, and anyone that thinks they are too clever to make mistakes quickly proves themselves wrong.

I might write a tutorial to save others the trouble I have gone through. Maybe it will encourage better support for command-line applications in AutoHotkey in the future.
Update:
I wrote that tutorial.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: No registered users and 375 guests