{"id":108023,"date":"2023-04-06T07:00:00","date_gmt":"2023-04-06T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108023"},"modified":"2023-04-05T21:24:51","modified_gmt":"2023-04-06T04:24:51","slug":"20230406-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230406-00\/?p=108023","title":{"rendered":"An ignored exception can be misinterpreted as a hang, particularly in coroutines"},"content":{"rendered":"<p>Consider the following function:<\/p>\n<pre>void DoAwesomeThings()\r\n{\r\n    try {\r\n        Step1();\r\n        Step2();\r\n        Step3();\r\n    }\r\n    catch (...)\r\n    {\r\n    }\r\n}\r\n<\/pre>\n<p>If an exception occurs in <code>Step2()<\/code>, then from the point of view of <code>Do\u00adAwesome\u00adThings()<\/code>, it will appear to have hung, since control never returned.<\/p>\n<pre>void DoAwesomeThings()\r\n{\r\n    try {\r\n        Step1();\r\n        printf(\"About to call Step2!\\n\");\r\n        Step2();\r\n        printf(\"Step2 returned!\\n\"); \/\/ never executes!\r\n        Step3();\r\n    }\r\n    catch (...)\r\n    {\r\n    }\r\n}\r\n<\/pre>\n<p>Now, one difference is that if you break into the debugger, you&#8217;ll find that there is no call to <code>Step2()<\/code> anywhere on the stack, and that may be a clue that the function didn&#8217;t actually hang. But if all you have is log file, your log file just ends at<\/p>\n<pre>About to call Step2!\r\n<\/pre>\n<p>and if you didn&#8217;t think about the <code>catch (...)<\/code>, you would conclude that <code>Step2()<\/code> is hung.<\/p>\n<p>Okay, so that was pretty obvious. I mean, who would make this kind of misinterpretation?<\/p>\n<p>Let&#8217;s make it a little less obvious: Let&#8217;s put it in a coroutine.<\/p>\n<pre>winrt::IAsyncAction DoAwesomeThings()\r\n{\r\n    Step1();\r\n    Step2();\r\n    Step3();\r\n}\r\n<\/pre>\n<p>Recall that the coroutine transformation, among other things, wraps the entire coroutine body inside a <code>try...catch<\/code>:<\/p>\n<pre>winrt::IAsyncAction DoAwesomeThings()\r\n{\r\n    co_await promise.initial_suspend();\r\n    <span style=\"color: #08f;\">try {<\/span>\r\n        Step1();\r\n        printf(\"About to call Step2!\\n\");\r\n        Step2();\r\n        printf(\"Step2 returned!\\n\"); \/\/ never executes!\r\n        Step3();\r\n    <span style=\"color: #08f;\">} catch (...) {\r\n        promise.unhandled_exception();\r\n    }<\/span>\r\n    co_await promise.final_suspend();\r\n}\r\n<\/pre>\n<p>This time, when <code>Step2()<\/code> throws an exception, it is caught by the coroutine infrastructure which calls the promise&#8217;s <code>unhandled_exception()<\/code>. The <code>unhandled_exception()<\/code> function typically saves the exception somewhere, so that it can be rethrown when somebody does a <code>co_await<\/code> on the coroutine return object.<\/p>\n<p>In this case, what happens is that the exception is saved in the <code>IAsyncAction<\/code> object, ready to be re-thrown by the <code>co_await<\/code> in <code>co_await DoAwesomeThings()<\/code>.<\/p>\n<p>But say you never do that <code>co_await<\/code>.<\/p>\n<pre>void BeReallyAwesome()\r\n{\r\n    DoAwesomeThings(); \/\/ no co_await\r\n    DoMoreAwesomeThings();\r\n}\r\n<\/pre>\n<p>If you look at the log file, you see the &#8220;About to call Step2!&#8221; and no &#8220;Step2 returned!&#8221;. And since you never did a <code>co_await<\/code>, the exception that was saved in the <code>IAsyncAction<\/code> is discarded without ever having a chance to be rethrown. Result: It looks like the coroutine simply hung.<\/p>\n<p>Note that each coroutine return type makes its own decision about how unhandled exceptions are dealt with, and what to do if an exception never gets rethrown. Here&#8217;s a table of some of the ones you&#8217;re like to encounter in Windows code.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Coroutine type<\/th>\n<th>Unhandled exception<\/th>\n<th><code>co_await<\/code><\/th>\n<th>Unobserved exception<\/th>\n<\/tr>\n<tr>\n<td><code>winrt::IAsync...<\/code><\/td>\n<td>Save for later<\/td>\n<td>Rethrow<\/td>\n<td>Discarded<\/td>\n<\/tr>\n<tr>\n<td><code>winrt::fire_and_forget<\/code><\/td>\n<td>Fail fast<\/td>\n<td>N\/A<\/td>\n<td>N\/A<\/td>\n<\/tr>\n<tr>\n<td><code>Concurrency::task<\/code><\/td>\n<td>Save for later<\/td>\n<td>Rethrow<\/td>\n<td>Fail fast<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For the last two popular Windows coroutine types, the exception gets reported eventually. The <code>fire_<wbr \/>and_<wbr \/>forget<\/code> crashes the process immediately, and <code>Concurrency::<wbr \/>task<\/code> rethrows the exception (if observed) or crashes the process (if never observed).<\/p>\n<p>It&#8217;s the <code>IAsync...<\/code> that can cause exceptions to mysteriously vanish: If you simply discard them without every performing a <code>co_await<\/code>, then you never learn of any unhandled exceptions that occurred.<\/p>\n<p>They just disappear.<\/p>\n<p>We&#8217;ll apply this knowledge next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I mean, execution seems to have stopped.<\/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-108023","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>I mean, execution seems to have stopped.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108023","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=108023"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108023\/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=108023"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108023"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108023"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}