Page 1 of 1

✅ #IfTimeout and keyboard hook bug

Posted: 20 Mar 2019, 12:44
by lvalkov
This issue has been resolved as of:
v1.1.30.02 wrote:Fixed #if expressions stalling in Sleep and similar.
The behavior now matches the description in the documentation precisely.

#IfTimeout reads:
The timeout in ms, default 1000ms. If the timeout is exceeded, the expression will continue to evaluate, however, the hook will behave as though it had evaluated to false.
To illustrate the issue, I have prepared the following piece of code:

Code: Select all

#If delay()
Space::MsgBox

delay() {
	Sleep 1
	return true
}
When Space is pressed, the script shall evaluate delay(). This should take about "1 ms" (realistically, up to ~16 ms, however, certainly below the supposed 1000 ms threshold). Finally, a MsgBox should be displayed.

I would expect that, as long as delay() takes less than a 1000 ms to execute, pressing Space should display a MsgBox.

What I observe, however, is quite different. The moment I press Space, the script will hang for, according to guesstimation, about 200 ms, during which time all keystrokes will be buffered. Once that has elapsed, all buffered keystrokes are reproduced instantaneously, in the order that they were captured. The #If directive has at this point evaluated to false. The Space hotkey is never triggered, and no MsgBox is ever displayed.

The 1.1.30.01 Unicode x64 interpreter was used to run this uncompiled script on Windows 10 1809 x64. No other scripts were running during testing. Neither were, to the best of my knowledge, any other programs that could interfere with the keyboard hook.


I was also unable to locate the value LowLevelHooksTimeout, supposedly residing in HKEY_CURRENT_USER\Control Panel\Desktop per the documentation instructions.

Re: #IfTimeout and keyboard hook bug

Posted: 20 Mar 2019, 14:33
by joefiesta
Ran your code on Win 7 pro 32-bit. No problems. Got the msgbox exactly as expected.

Ahk version 1.1.30.01.

Re: #IfTimeout and keyboard hook bug

Posted: 22 Mar 2019, 05:35
by lexikos
The quote above does not appear to match the documentation of v1.1.30.01.

The default value for the IfTimeout setting (not the default system timeout) is 1000.
If this directive is unspecified in the script, it will behave as though set to 1000.
Source: #IfTimeout - Syntax & Usage | AutoHotkey
It sounds like lvalkov has encountered the system timeout, not the timeout imposed by AutoHotkey (which is intended as a safe-guard against the system timeout).
Note that the system implements its own timeout, defined by the DWORD value LowLevelHooksTimeout in the following registry key:

HKEY_CURRENT_USER\Control Panel\Desktop

If the system timeout value is exceeded, the system may stop calling the script's keyboard hook, thereby preventing hook hotkeys from working until the hook is re-registered or the script is reloaded. The hook can usually be re-registered by suspending and un-suspending all hotkeys.

Microsoft's documentation is unclear about the details of this timeout, but research indicates the following for Windows 7 and later: If LowLevelHooksTimeout is not defined, the default timeout is 300ms. The hook may time out up to 10 times, but is silently removed if it times out an 11th time.
Source: #IfTimeout - Syntax & Usage | AutoHotkey
#IfTimeout was designed and its default setting was chosen in 2008. Note that Windows 7 was released in 2009, and the researched mentioned above was done shortly before those details were added to the documentation in 2018...

Re: #IfTimeout and keyboard hook bug

Posted: 22 Mar 2019, 08:51
by lvalkov
Because I was unable to locate any LowLevelHooksTimeout values in the registry on Windows 10, I must now operate under the assumption that LowLevelHooksTimeout is undefined, meaning the system timeout is supposed to be in the ballpark of around 300 ms. If up to 300 ms are allowed for, why then does Sleep 1 trigger the system timeout? The way I understand it, as long as the function delay() takes less than 300 ms to execute, no timeout should ever be triggered. As a result of that, the Space keystroke should be suppressed by AutoHotkey's keyboard hook and the hotkey's routine should run, i.e. a MsgBox should show up.

This isn't what is observed on Windows 10, as already mentioned. Instead, when Space is pressed, all keyboard input is buffered for roughly 200 ms, then the Space keystroke is reproduced, followed by any remaining buffered ones, if present. At no point is the Space keystroke ever captured and suppressed by AutoHotkey's keyboard hook. Subsequently, no MsgBox is ever displayed.


I fired up Windows 7 x86 in a VM. Ran the script from within Windows 7. The following was observed: upon pressing Space, AHK's keyboard hook fails to suppress it, meaning a Space keystroke is immediately reproduced. In contrast to the observed behavior on Windows 10, however, a MsgBox does show up afterwards. I couldn't manage to locate a LowLevelHooksTimeout value in the registry on Windows 7 either, so assuming a system timeout of 300 ms, I decided to bump Sleep's duration up to 300 and see what happens. Sure enough, for values greater than ~250, the system timeout was consistently kicking in. That said, I wouldn't give particular credence to the results of this experiment. After all, it was run in a VM.

Re: ✅ #IfTimeout and keyboard hook bug

Posted: 05 Apr 2019, 17:13
by lexikos
I thought I replied a second time to this topic. I noticed the unusual checkmark that was added to the topic title and thought to suggest that a post be marked as the solution instead (there's a button for it at the top of each post, although as an admin I'm not certain who it is visible to), but it seems I hadn't posted a solution yet. ;)

I did some debugging, and believe this is what happens when I press the hotkey on v1.1.30.01:
  • The hook thread sends a message to the script thread: "evaluate this #if expression".
  • The script thread calls delay() and then Sleep 1.
  • Sleep calls the internal MsgSleep() function.
  • MsgSleep() calls GetMessage().
  • GetMessage() stalls for approximately LowLevelHooksTimeout milliseconds (or 300 if the registry value is not set).
    Note that changes to the LowLevelHooksTimeout registry value do not take effect immediately. I rebooted after each change, but I assume logging out would suffice since it is a per-user setting.
  • MsgSleep() and Sleep return.
  • delay() returns true.
The initial message sent by the hook has a timeout which depends on #IfTimeout. This timeout and the system timeout are independent: if our timeout expires, the hotkey will not execute. If the system timeout expires, the key is immediately passed through even though our hook is still running, and the hotkey might still execute since we generally have no way of detecting what's happened.

With #IfTimeout and LowLevelHooksTimeout at defaults/absent, the end result is that I get a MsgBox but Space is not blocked. If I rapidly press Space multiple times, the result is unpredictable, but generally the first GetMessage() takes around 300ms and subsequent calls take longer but are still limited by #IfTimeout. This is harder to debug, and might be related to how AutoHotkey emulates multi-threading, or might be occurring because the hook thread is unresponsive (waiting for the script thread).

Ordinarily, GetMessage() does not return until a message is available. However, MsgSleep() ensures that a Win32 timer is set so that GetMessage() will return after 10ms or so. This timer may already be set, such as if the script has joystick hotkeys or active script timers (SetTimer). When no other messages exist in the queue and the timer interval elapses, GetMessage() returns a WM_TIMER message.

In this case, GetMessage() is not returning a WM_TIMER message until after the system stops waiting for the keyboard hook. I could not find any documentation that explains this phenomenon. One theory is that the system message queue is locked while keyboard hooks are being called, and GetMessage() waits for it to unlock before processing the lower priority items such as timers.

Further explanation

There's a simple workaround I can add have added for this, since there are already other conditions which will cause MsgSleep() to use PeekMessage() and Sleep() instead of GetMessage().

However, it is generally best to design #if expressions to complete as quickly as possible and minimize side effects. It is unwise to perform any action which could result in interruption of the thread, such as Sleep.

Re: ✅ #IfTimeout and keyboard hook bug

Posted: 05 Apr 2019, 18:21
by lvalkov
Even though older threads in the Bug Reports subforum suggest that marking a post as solved should be possible, I am unable to locate the button in question. Sorry.
The explanation makes sense. Thank you.