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.