{"id":108110,"date":"2023-04-28T07:00:00","date_gmt":"2023-04-28T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108110"},"modified":"2023-04-28T09:14:52","modified_gmt":"2023-04-28T16:14:52","slug":"20230428-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230428-00\/?p=108110","title":{"rendered":"On the finer points of cancelling timers and wait objects in Windows thread pool"},"content":{"rendered":"<p>The Windows thread pool lets you create, among other things, timers and waits, and you can cancel them, too.<\/p>\n<p>There are some finer points here.<\/p>\n<p>The way you cancel a timer is to call <code>Set\u00adThreadpool\u00adTimer<\/code> or <code>Set\u00adThreadpool\u00adTimer\u00adEx<\/code> with a null pointer as the due time. And the way you cancel a wait is to call <code>Set\u00adThreadpool\u00adWait<\/code> or <code>Set\u00adThreadpool\u00adWait\u00adEx<\/code> with a null handle.<\/p>\n<p>When you cancel a timer or wait, no future callbacks will be created, but existing ones are not recalled. Therefore, there&#8217;s a race condition if you issue you your cancellation just after the timer or wait has triggered: You cancelled it too late to prevent the callback from being scheduled, and you can&#8217;t detect this race from your callback because the callback may not have started running yet. This could be because the callback has been scheduled but hasn&#8217;t yet been given a thread to run on yet. Or it could be that your callback was unluckily pre-empted at its very first instruction.<\/p>\n<table class=\"cp3\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th style=\"border: 1px currentcolor; border-style: none solid solid none;\">Thread pool<\/th>\n<th style=\"border-bottom: solid 1px currentcolor;\">You<\/th>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor;\">Object is signaled \/ timer is ready<br \/>\nCallback scheduled<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor;\">\u00a0<\/td>\n<td>Cancel timer \/ wait<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor;\">Thread assigned to callback<br \/>\nCallback runs<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>A timer or wait is created in the &#8220;unset&#8221; state. You put into the &#8220;set&#8221; state by calling one of the <code>Set...<\/code> functions with a non-null due time or handle. You return it to the &#8220;unset&#8221; state by calling the the <code>Set...<\/code> functions with a null due time or handle.<\/p>\n<p>When you call one of the <code>Set...Ex<\/code> functions, it returns a value that tells you whether a callback was cancelled. But that&#8217;s only part of what you need to know in order to determine whether a callback is on its way. You also need to know whether the timer or wait was previously set.<\/p>\n<p>Here&#8217;s a table of the possibilities:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Previous state<\/th>\n<th>Was callback pending<\/th>\n<th>Could callback be recalled<\/th>\n<th>Set returns&#8230;<\/th>\n<\/tr>\n<tr>\n<td>Unset<\/td>\n<td>N\/A<\/td>\n<td>N\/A<\/td>\n<td><code>FALSE<\/code><\/td>\n<\/tr>\n<tr>\n<td>Set<\/td>\n<td>No<\/td>\n<td>N\/A<\/td>\n<td><code>TRUE<\/code><\/td>\n<\/tr>\n<tr>\n<td>Set<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td><code>TRUE<\/code><\/td>\n<\/tr>\n<tr style=\"border: solid 3px currentcolor;\">\n<td>Set<\/td>\n<td>Yes<\/td>\n<td>No<\/td>\n<td><code>FALSE<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The last row is the interesting one: When you cancel the timer or wait object, the thread pool tries to recall any pending callbacks, but sometimes a callback has already gone too far and could not be recalled. For example, the callback could be already in progress. In that case, the <code>Set...Ex<\/code> function returns <code>FALSE<\/code> to tell you that you&#8217;re not finished yet. You have to wait for the callback to complete before everything is finally done.<\/p>\n<p>The way you wait for the callback to complete is to call <code>Wait\u00adFor\u00adThreadpool\u00adTimer\u00adCallbacks<\/code> or <code>Wait\u00adFor\u00adThreadpool\u00adWait\u00adCallbacks<\/code>. It will wait for the completion of all outstanding callbacks for the specified timer or wait. A callback is deemed to have completed when it returns, or when it calls <code>Dissociate\u00adCurrent\u00adThread\u00adFrom\u00adCallback<\/code>. We can add this as another column to our table:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Previous state<\/th>\n<th>Was callback pending<\/th>\n<th>Could callback be recalled<\/th>\n<th>Set returns&#8230;<\/th>\n<th>Wait returns&#8230;<\/th>\n<\/tr>\n<tr>\n<td>Unset<\/td>\n<td>N\/A<\/td>\n<td>N\/A<\/td>\n<td><code>FALSE<\/code><\/td>\n<td>Immediately<\/td>\n<\/tr>\n<tr>\n<td>Set<\/td>\n<td>No<\/td>\n<td>N\/A<\/td>\n<td><code>TRUE<\/code><\/td>\n<td>Immediately<\/td>\n<\/tr>\n<tr>\n<td>Set<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td><code>TRUE<\/code><\/td>\n<td>Immediately<\/td>\n<\/tr>\n<tr>\n<td>Set<\/td>\n<td>Yes<\/td>\n<td>No<\/td>\n<td><code>FALSE<\/code><\/td>\n<td>After callback completes<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Fortunately, the <code>Wait\u00adFor\u00ad...\u00adCallbacks<\/code> functions wait in exactly the case we need them to wait.<\/p>\n<p>We can now put this information to use: Suppose you have a thread pool timer or wait that has been set, and you later realize that you don&#8217;t want to wait that long after all. What is the pattern for safely accelerating the callback?<\/p>\n<p>If we assume that the timer or wait is definitely set, then the <i>Unset<\/i> row is removed from consideration, and that means that a <code>FALSE<\/code> return value from <code>Set...Ex<\/code> tells us that a callback is in progress, or at least has proceeded past the point of no return. In that case, we don&#8217;t need to accelerate the callback; it&#8217;s already on its way.<\/p>\n<p>Otherwise, there was no callback in progress, so we need to make one. We can do that by resetting the timer or wait callback with a timeout of zero (which means <i>now<\/i>).<\/p>\n<pre>if (SetThreadpoolTimerEx(timer, nullptr, 0, 0)) {\r\n    FILETIME now = { 0, 0 };\r\n    SetThreadpoolTimer(timer, &amp;now, 0, 0);\r\n}\r\n\r\nif (SetThreadpoolWaitEx(wait, nullptr, nullptr, nullptr)) {\r\n    FILETIME now = { 0, 0 };\r\n    SetThreadpoolWait(wait, GetCurrentProcess(), &amp;now);\r\n}\r\n<\/pre>\n<p>In the case of making a wait callback run immediately, we have to give it a non-null handle, although we don&#8217;t care what it is. We use the pseudo-handle to the process itself, which the process will never observe as signaled. That way, the callback is made with the report that the wait timed out.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Assorted little details.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-108110","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Assorted little details.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108110","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=108110"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108110\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=108110"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108110"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108110"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}