{"id":104901,"date":"2021-02-24T07:00:00","date_gmt":"2021-02-24T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104901"},"modified":"2024-04-16T16:00:35","modified_gmt":"2024-04-16T23:00:35","slug":"20210224-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210224-00\/?p=104901","title":{"rendered":"What is so special about the Application STA?"},"content":{"rendered":"<p>Windows 8 introduced a new COM threading model which is a variation of the single-threaded apartment (STA). It&#8217;s called the <i>Application STA<\/i>.<\/p>\n<p>The Application STA is a single-threaded apartment, but with the additional restriction that it cannot be re-entered. Consider the following timeline, with time proceeding from top to bottom:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th valign=\"top\">Thread 1<\/th>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<th valign=\"top\">Thread 2<br \/>\n(normal STA)<\/th>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<th valign=\"top\">Thread 3<\/th>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>Call thread 3<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u2192<\/td>\n<td>Start operation A<\/td>\n<\/tr>\n<tr style=\"height: 4em;\">\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>(waiting)<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>(working)<\/td>\n<\/tr>\n<tr>\n<td>Call thread 2<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u2192<\/td>\n<td>Start operation B<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>We have three threads, call them Threads 1, 2, and 3. Thread 2 makes a call to Thread 3 and waits for a response. While Thread 2 is waiting, Thread 1 makes a call into Thread 2. This causes Thread 2 to become re-entered, and that&#8217;s a tricky situation.<\/p>\n<p>If the second call is to perform some operation unrelated to the first call, then you&#8217;re probably going to be okay. But suppose the second call is to perform an operation on the same object that the first call is working on. In that case, the first call will have its object modified out from under it.<\/p>\n<pre>void FrobAllWidgets()\r\n{\r\n  for (auto&amp;&amp; widget : m_widgets) {\r\n    widget.Frob(); \/\/ calls to thread 3\r\n  }\r\n}\r\n\r\nvoid AddWidget(Widget const&amp; widget)\r\n{\r\n  m_widgets.push_back(widget);\r\n}\r\n<\/pre>\n<p>Suppose Thread 2 is doing a <code>Frob\u00adAll\u00adWidgets<\/code>, and the call to <code>widget.Frob();<\/code> results in a call to Thread 3.<\/p>\n<p>While one of those calls is in progress, Thread 2 is just sitting around waiting for the call to complete.<\/p>\n<p>And just at that moment, Thread 1 comes in and adds another widget.<\/p>\n<p>This mutates the vector of widgets, causing the <code>for<\/code> loop in <code>Frob\u00adAll\u00adWidgets<\/code> to go haywire because the vector was resized, causing all the iterators to become invalid.<\/p>\n<p>You might try to fix this by adding a critical section:<\/p>\n<pre><span style=\"border: solid 1px currentcolor;\">wil::critical_section m_cs;<\/span>\r\n\r\nvoid FrobAllWidgets()\r\n{\r\n  <span style=\"border: solid 1px currentcolor;\">auto guard = m_cs.lock();<\/span>\r\n  for (auto&amp;&amp; widget : m_widgets) {\r\n    widget.Frob(); \/\/ calls to thread 3\r\n  }\r\n}\r\n\r\nvoid AddWidget(Widget const&amp; widget)\r\n{\r\n  <span style=\"border: solid 1px currentcolor;\">auto guard = m_cs.lock();<\/span>\r\n  m_widgets.push_back(widget);\r\n}\r\n<\/pre>\n<p>You think you fixed it, but in fact nothing has changed. Critical sections support recursive acquisition, so what happens is that the re-entrant call to <code>Add\u00adWidget<\/code> tries to acquire the critical section, and it <i>succeeds<\/i>, because the owner is the same thread.<\/p>\n<p>Okay, so switch to something that does not support recursive acquisition, like a shared reader-writer lock.<\/p>\n<pre><span style=\"border: solid 1px currentcolor;\">wil::srwlock m_srw;<\/span>\r\n\r\nvoid FrobAllWidgets()\r\n{\r\n  <span style=\"border: solid 1px currentcolor;\">auto guard = m_srw.lock_shared();<\/span>\r\n  for (auto&amp;&amp; widget : m_widgets) {\r\n    widget.Frob(); \/\/ calls to thread 3\r\n  }\r\n}\r\n\r\nvoid AddWidget(Widget const&amp; widget)\r\n{\r\n  <span style=\"border: solid 1px currentcolor;\">auto guard = m_srw.lock_exclusive();<\/span>\r\n  m_widgets.push_back(widget);\r\n}\r\n<\/pre>\n<p>Well, at least this time there&#8217;s no crash. Instead the call hangs, because the re-entrant call to to <code>Add\u00adWidget<\/code> tries to acquire the exclusive lock, but it cannot because of the existing call to <code>Frob\u00adAll\u00adWidgets<\/code>. And that existing call cannot complete until <code>Add\u00adWidgets<\/code> completes, because <code>Add\u00adWidgets<\/code> is running on the same stack.<\/p>\n<table class=\"cp3\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>FrobAllWidgets<\/td>\n<\/tr>\n<tr>\n<td>\u21b3 Widget::Frob<\/td>\n<\/tr>\n<tr>\n<td style=\"padding-left: 1em;\">\u21b3 WaitForFrobToFinish<\/td>\n<\/tr>\n<tr>\n<td style=\"padding-left: 2em;\">\u21b3 ReceiveInboundCall<\/td>\n<\/tr>\n<tr>\n<td style=\"padding-left: 3em;\">\u21b3 AddWidget<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><code>Add\u00adWidget<\/code> is running on the same stack, so <code>Frob\u00adAll\u00adWidgets<\/code> cannot return until the stack unwinds, which means that <code>Add\u00adWidget<\/code> needs to return, but it can&#8217;t.<\/p>\n<p>The Application STA tries to address this problem by blocking re-entrancy. The diagram now looks like this:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th valign=\"top\">Thread 1<\/th>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<th valign=\"top\">Thread 2<br \/>\n(application STA)<\/th>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<th valign=\"top\">Thread 3<\/th>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>Call thread 3<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u2192<\/td>\n<td>Start operation A<\/td>\n<\/tr>\n<tr style=\"height: 4em;\">\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>(waiting)<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>(working)<\/td>\n<\/tr>\n<tr>\n<td>Call thread 2<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u21b4<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u2193<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u2193<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u2190<\/td>\n<td>Finished<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u2193<\/td>\n<td>Finish<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u21b3<\/td>\n<td>Operation B starts<\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>When Thread 1 makes a call into the Application STA while the Application STA is waiting for a response from Thread 3, the call is not allowed to proceed, because that would result in re-entrancy. The call from Thread 1 is placed on hold until Thread 2 is no longer waiting for an outbound call to complete. Once Thread 2 returns to a normal state, it can receive inbound calls again.<\/p>\n<p>You can detect that you are running in an Application STA by calling <code>Co\u00adGet\u00adApartment\u00adType<\/code> and checking for an apartment type of STA and an apartment type qualifier of APPLICATION_<wbr \/>STA.<\/p>\n<p>Next time, we&#8217;ll look at another quirk of the Application STA.<\/p>\n<p><b>Bonus chatter<\/b>: I forgot to include the Application STA in my <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20180208-00\/?p=97986\"> list of possible results from <code>Co\u00adGet\u00adApartment\u00adType<\/code><\/a>, so I&#8217;ve retroactively updated it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Basically, it blocks re-entrancy.<\/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-104901","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Basically, it blocks re-entrancy.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104901","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=104901"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104901\/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=104901"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104901"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104901"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}