This document describes, and presents a solution for, a rare but potentially troublesome condition that can occur in a JAWS script that uses the
GetTickCount function, especially when using it to manage the life
cycle of a cache.
Document revision history:
A change in JAWS, in or before JAWS 18.0, can cause comparisons of
GetTickCount values to return unexpected results. The change is the removal, or "masking off," of the highest-order bit in the function's return value.
The effect is only seen roughly once every 24 days of a computer's running without a reboot.
The effect on cache management code, however, can be that a cache that is intended to be refreshed in a few seconds or less may survive unchanged for up to 24 days.
The change in JAWS is not itself a bug but was probably meant to make the
GetTickCount value a more sensible value in most applications.
This document presents a working solution to the problems caused by the change for the cache management scenario.
GetTickCount function returns a value that starts counting at 0 on every reboot of a Windows computer. The value counts upward in milliseconds. If the computer is left running long enough without a
restart, this value can "wrap around" to 0 and begin counting upward again from there, much like the odometer in a car.
In older versions of JAWS, the
GetTickCount value was a 32-bit unsigned integer. This meant that the value would not return to 0 for about 49.7 days. However, the JAWS scripting language treats integers
ints) as 32-bit signed values. As a result, the value would suddenly become negative halfway through this time period.
Due to the way numbers are handled in JAWS scripting and in computers in general, this perhaps odd-looking behavior had the advantage of allowing "circular" treatment--i.e., subtracting one
GetTickCount value from
another worked out to return the actual time elapsed between the two values, in milliseconds, regardless of whether the number passed 0 or became negative between the two points of interest.
Such circular treatment is frequently used in the management of data caches, in order to make sure they are not kept too long without a refresh.
In JAWS 18 or an earlier undetermined version,
GetTickCount became a 31-bit value; that is, the highest-order bit (bit 32) was removed from the return value. This means that the return value of
GetTickCount() will never appear as negative in JAWS. However, it also means that the value can no longer be used in a circular fashion, as is often done when using the function as a means of checking for short
time-elapsed lengths, without a bit of extra care.
Specifically, if the
GetTickCount value passes 0 between two calls and the returned values are then compared to arrive at an elapsed time, the resulting elapsed-time value will be a negative number far below 0.
A JAWS script that uses
GetTickCount to check the age of a cache will, if exercised at the right time, fail to register the cache as old even when it is.
This problem may affect many scripts but only under extremely rare conditions. In order for the problem to manifest, all of the following must first occur:
GetTickCountcall must be made shortly before, and another after, the return value of that function wraps around to 0.
In this scenario, the "elapsed time" will be a negative number far below 0, and will thus appear as less than the time limit. This condition will persist for over 24 days before
GetTickCount again reaches similar
values to the first one saved above. In practical effect, this means that, under the very rare but possible conditions outlined here, the cache may become virtually permanent and never update until the next restart of JAWS or
The following event occurred on October 10, 2019, and is the event that caused this author to discover the issue presented here. On this day, the author:
GetTickCountreturn value wrapped around to 0.
This scenario was caused by a data cache maintained by the SFB scripts not expiring as intended, which was in turn caused by the
GetTickCount change discussed here.
The following solution effectively recreates the value bit removed by JAWS, so that the functionality of
GetTickCount in the cache management scenario will be restored.
The code below also returns the absolute value of the difference between tick counts, so that any negative result will still result in a cache rebuild very quickly.
Usage examples: if
gcCache.tc is the
getTickCount value from when the cache was last updated, change
if getTickCount() - gcCache.tc < 500to
if tickAge(gcCache.tc) < 500If the desired current
getTickCount()value is already in variable
if tc - gcCache.tc < 500to
if tickAge(gcCache.tc, tc) < 500This approach does create the possibility that a cache update will occur much sooner than usual once every 24.85 days, but the impact of this side effect is of course likely to be negligible.
The below code is hereby placed in the public domain.
int function tickAge(int tc0, optional int tc) ; Return the absolute value of the age, in milliseconds, of the getTickCount() return value passed as tc0. If tc is passed, it is used as current time; otherwise, getTickCount() is called for this. ; If tc0 is not an int, a maximal age is returned, on the assumption that this signifies an uninitialized counter. ; This function works around a change in JAWS, before or in 18.0, that masked off the top bit of getTickCount(), making circular use impractical without such care as this. ; If used in a JAWS version that does not mask off the top getTickCount() bit, this function will still work. ; This function is meant to be used to determine when to update a data cache for being too old. ; The absolute value is returned to guarantee no possibility of a negative result causing a cache to live longer than intended; that is, updating a little too often is better than not often enough. ; This function requires JAWS 14.0 or later due to its use of getVariantType(). if !tc0 && getVariantType(tc0) != VT_Int ; Uninitialized counter; return something like the C maxint. return 0x7FFFFFFFL elif !tc && getVariantType(tc) != VT_Int ; tc was not passed, so use the current value of the counter. tc = getTickCount() endIf if tc < tc0 && tc >= 0 ; getTickCount wrapped past 0 between tc0 and tc without the high-order bit. Mere subtraction would give the wrong answer. ; This puts the bit back to make subsequent math work as intended. tc = tc | 0x80000000L endIf var int delta = tc -tc0 ; Absolute value returned just in case of things like an actual tc0-->tc difference so large as to cause a huge negative result. ; For the typical use of this function, cache lifecycle maintenance, this should mean the cache is old and should be rebuilt. return abs(delta) endFunction