{"id":102989,"date":"2019-10-11T07:00:00","date_gmt":"2019-10-11T14:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=102989"},"modified":"2019-10-10T22:44:17","modified_gmt":"2019-10-11T05:44:17","slug":"20191011-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191011-00\/?p=102989","title":{"rendered":"Fibers aren&#8217;t useful for much any more; there&#8217;s just one corner of it that remains useful for a reason unrelated to fibers"},"content":{"rendered":"<p>Fibers were the new hotness back in 1996, but the initial excitement was gradually met with the realizations that fibers are awful. Gor Nishanov has a <a href=\"http:\/\/www.open-std.org\/JTC1\/SC22\/WG21\/docs\/papers\/2018\/p1364r0.pdf\"> fantastic write-up of the history of fibers and why they suck<\/a>. Of particular note is that nearly all of the original proponents of fibers subsequently abandoned them.<\/p>\n<p>Fibers make asynchronous functions appear to be synchronous. Depending on what color glasses you are wearing, this is either a cool trick or a hidden gotcha. Over time, the consensus of most of the computing community has settled on the side of &#8220;hidden gotcha&#8221;.<\/p>\n<p>But there&#8217;s still one part of Windows fibers that is still useful: The fiber destruction callback.<\/p>\n<p>Okay, let&#8217;s step back and look at thread-local storage first. Windows thread-local storage works like this:<\/p>\n<ul>\n<li>You allocate a thread-local storage slot.<\/li>\n<li>You learn that a thread has been created via the <code>DLL_<\/code><code>THREAD_<\/code><code>ATTACH<\/code> notification. You can initialize the thread-local storage in response to this notification.<\/li>\n<li>You learn that a thread has been destroyed via the <code>DLL_<\/code><code>THREAD_<\/code><code>DETACH<\/code> notification. You can clean up the thread-local storage in response to this notification.<\/li>\n<li>You free the thread-local storage slot.<\/li>\n<\/ul>\n<p>There are a few problems here.<\/p>\n<p>One problem is the case of the thread that existed prior to the allocation of the thread-local storage slot. It&#8217;s possible that you never received any <code>DLL_<\/code><code>THREAD_<\/code><code>ATTACH<\/code> notification for these pre-existing threads, because your DLL wasn&#8217;t even loaded at the time. Even if your DLL did receive those notifications, you couldn&#8217;t do anything to initialize a thread-local storage slot that didn&#8217;t exist.<\/p>\n<p>In practice, what happens is that thread-local storage is lazily allocated. The DLL ignores the <code>DLL_<\/code><code>THREAD_<\/code><code>ATTACH<\/code> notification and allocates the storage on demand the first time a thread does something that requires it.<\/p>\n<p>Another problem is with threads that remain in existence at the time the thread-local storage slot is deallocated. You have to free the memory at deallocation time because even if you get a subsequent <code>DLL_<\/code><code>THREAD_<\/code><code>DETACH<\/code>, the slot is already gone, so you lost track of the memory you wanted to free.<\/p>\n<p>A common workaround for this is to keep your own data structure that remembers all the data that has been allocated for thread-local storage, and free that data structure when the slot is deallocated. But if you&#8217;re going to do this, then you really didn&#8217;t need the thread-local storage slot in the first place. When a thread needs to access per-thread storage, you can just look it up in that data structure you&#8217;re using to keep track of them!<\/p>\n<p>A third problem is with code that doesn&#8217;t have access to the <code>Dll\u00adMain<\/code> function. Executables do not receive DLL notifications, so they never learn about thread creation or destruction. Static libraries do not have access to the <code>Dll\u00adMain<\/code> function of their host.<\/p>\n<p>A common workaround for this is to <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20160930-00\/?p=94425\"> hire a lackey<\/a>. Executables may have a lackey DLL whose purpose is to forward the notification back to the executable. Static libraries may require the host to forwared the notifications into a special function in the static library.<\/p>\n<p>Even though workarounds for all these problems exist, they&#8217;re still annoying problems.<\/p>\n<p>An alternate workaround is to abandon thread-local storage and use fiber-local storage instead. But not because you care about fibers. Rather, it&#8217;s because fiber-local storage <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/ms682664(v=vs.85).aspx\"> has this nifty callback<\/a>.<\/p>\n<pre>DWORD WINAPI FlsAlloc(\r\n    _In_ PFLS_CALLBACK_FUNCTION lpCallback\r\n);\r\n<\/pre>\n<p>The callback function is called when a fiber is destroyed. This is the fiber equivalent of <code>DLL_<\/code><code>THREAD_<\/code><code>DETACH<\/code>, except that it&#8217;s not just for DLLs. Executables can use it too.<\/p>\n<p>Even better: When you deallocate the fiber-local storage slot, the callback is invoked for every extant fiber. This lets you clean up all your per-fiber data before it&#8217;s too late.<\/p>\n<p>As a bonus you support fibers, in case anybody uses your code with fibers.<\/p>\n<p>The new fancy pattern is now this:<\/p>\n<ul>\n<li>You allocate a filber-local storage slot with a callback function.<\/li>\n<li>The fiber-local storage is allocated on demand the first time a fiber needs access to it.<\/li>\n<li>The callback function frees the fiber-local storage, if it had been allocated.<\/li>\n<li>When finished, you free the thread-local storage slot. This will call the callback for each fiber still in existence, so you can clean up the data.<\/li>\n<\/ul>\n<p>Even though fibers are basically dead, you can use fiber-local storage to get an improved version of thread-local storage.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A better way to clean up.<\/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-102989","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A better way to clean up.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102989","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=102989"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102989\/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=102989"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=102989"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=102989"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}