Practical Testing: 40 - Code updates and new functionality

Nineteen years ago I began a series of blog posts, called “Practical Testing”, about testing real-world, multi-threaded code. As with most code that works well, and is used by lots of people, we’re still changing it and improving it and using it. I’ve just done a precis of how we got here and now it’s time to continue the journey.

As I hinted at the end of the last episode, there were some outstanding issues to deal with and some new functionality to add and test. This version of the code has been updated to the code style used by Release 7.4/7.5 of The Server Framework which are transitional changes towards a cross-platform version of the code. I’ve finally renamed the CCallbackTimerQueueEx back to CCallbackTimerQueue. This is a change that was deliberately not done back in 2016 but which has been overdue for some time now; the ‘Ex’ was just a nasty wart. I’ve also changed the lock type used in CThreadedCallbackTimerQueue back to the slightly more performant, non re-entrant, lock now that the chance of re-entrant locking has been removed (again in episode 37, back in 2016). The last ‘polishing code without really changing much’ change is to update the licensing for this code from Public Domain to MIT. The older version of the code is, of course, still Public Domain and all versions from now on are under the MIT license and are very clear about that in each file.

The latest set of changes that the tests will help us with are to make the code that calls manipulates timers slightly easier to write and these have been useful for some clients.

The first is the addition of bool IQueueTimers::TimerIsSet(const Handle &handle) const which allows you to determine if a given timer is set or not… There will always be a race condition between using this to check to see if the timer IS set as the timer could be expiring on another thread at the same time that you’re calling this function; but as long as you’re not using this call to poll the timer for a timeout you should be fine. As usual, merely having to write about the code helps to improve it, and the tests that support it; I’ve just gone an added another test that covers TimerIsSet() being called whilst the timer is timing out and timeouts are being handled…

The next is changing the SetTimer() call from this:

      virtual bool SetTimer(
         const Handle &handle,
         Timer &timer,
         const Milliseconds timeout,
         const UserData userData) = 0;

to this:

      enum SetTimerIf
      {
         SetTimerAlways,
         SetTimerIfNotSet
      };

      virtual bool SetTimer(
         const Handle &handle,
         Timer &timer,
         const Milliseconds timeout,
         const UserData userData,
         const SetTimerIf setTimerIf = SetTimerAlways) = 0;

This allows the user of the timer to maintain less extra information alongside the timer handle for some use cases. We don’t need to know, or care, if the timer is set to never set a timer more than once. This is slightly more useful than calling TimerIsSet() and then SetTimer() as the test and set is atomic for the threaded timer queue…

Finally there’s a new function to update a timer. This is a slightly more complicated version of SetTimer() with a little more functionality for a narrow set of use cases…

      enum UpdateTimerIf
      {
         UpdateTimerIfNewTimeIsSooner,
         UpdateTimerIfNewTimeIsLater,
         UpdateAlways,
         UpdateAlwaysNoTimeoutChange,
      };

      virtual bool UpdateTimer(
         const Handle &handle,
         Timer &timer,
         const Milliseconds timeout,
         const UserData userData,
         const UpdateTimerIf updateIf = UpdateAlways,
         bool *pWasUpdated = nullptr) = 0;

The various options for updating are fairly self explanatory; you can update a timer if the new time is sooner, later or always and you can update the user-data and timer for a given handle even if you don’t change the timeout. A call to UpdateTimer() with UpdateAlways is the same as a call to SetTimer() with SetTimerAlways.

Code

The code is here on GitHub and new rules apply; The code will build with Visual Studio 2017, 2019 and 2022. CoreToolsTest is the solution that you want and it’s also the project that you should set as active. The code uses precompiled headers the right way so that you can build with precompiled headers for speed or build without them to ensure minimal code coupling. The various options are all controlled from the “Admin” project; edit Config.h and TargetWindowsVersion.h to change things… By default the project is set to build for Windows 10; this will mean that the code WILL NOT RUN on operating systems earlier than Windows Vista as it will try and use GetTickCount64() which isn’t available.

This version of the code is licensed under the MIT license.