{"id":2713,"date":"2013-11-07T07:00:00","date_gmt":"2013-11-07T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/11\/07\/partially-eliminating-the-need-for-setthreadpoolcallbacklibrary-and-reducing-the-cost-of-freelibraryandexitthread\/"},"modified":"2013-11-07T07:00:00","modified_gmt":"2013-11-07T07:00:00","slug":"partially-eliminating-the-need-for-setthreadpoolcallbacklibrary-and-reducing-the-cost-of-freelibraryandexitthread","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20131107-00\/?p=2713","title":{"rendered":"Partially eliminating the need for SetThreadpoolCallbackLibrary and reducing the cost of FreeLibraryAndExitThread"},"content":{"rendered":"<p>\n<b>Update<\/b>:\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/11\/07\/10464408.aspx#10464658\">Daniel points out<\/a>\nthat there is still a race condition here, so this trick won&#8217;t work. Rats.\n<\/p>\n<p>\nThe documentation for the\n<code>Set&shy;Threadpool&shy;Callback&shy;Library<\/code>\nsays\n<\/p>\n<blockquote CLASS=\"q\">\n<p>\nThis prevents a deadlock from occurring when one thread in\nDllMain is waiting for the callback to end,\nand another thread that is executing the callback\nattempts to acquire the loader lock.\n<\/p>\n<p>\nIf the DLL containing the callback might be unloaded,\nthe cleanup code in DllMain must cancel outstanding\ncallbacks before releasing the object.\n<\/p>\n<p>\nManaging callbacks created with a\n<code>TP_CALLBACK_ENVIRON<\/code>\nthat specifies a callback library is somewhat processor-intensive.\nYou should consider other options for ensuring that the library\nis not unloaded while callbacks are executing,\nor to guarantee that callbacks which may be executing\ndo not acquire the loader lock.\n<\/p>\n<\/blockquote>\n<p>\nI&#8217;m not going to help you with the DllMain cleanup issues.\n(My plan is to simply avoid the issue by preventing the DLL\nfrom unloading while a callback is still pending.\nThat way, you never have to cancel the callback from DllMain.)\nBut I am going to help with the\n&#8220;consider other options for ensuring that the library is not\nunloaded while callbacks are executing.&#8221;\n<\/p>\n<p>\nThe first-pass solution is to use the same trick we use\nwhen creating worker threads:\nWe\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/11\/05\/10463645.aspx\">\nbump the DLL reference count when queueing the work item<\/a>\nand use\n<code>Free&shy;Library&shy;When&shy;Callback&shy;Returns<\/code>\nto decrement the reference count after the callback finishes.\n(We can&#8217;t use\n<code>Free&shy;Library&shy;And&shy;Exit&shy;Thread<\/code>,\nof course, since we&#8217;re running on a thread on loan to us\nfrom the thread pool.\nExiting the thread from a thread pool callback is like\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2010\/04\/30\/10004931.aspx\">\ndemolishing the house you&#8217;re renting<\/a>.)\n<\/p>\n<p>\nThe second-pass solution is to\nmanage the DLL reference count manually.\n(Don&#8217;t go down this route unless your profiling suggests that\nDLL reference count management is a performance bottleneck.)\nThe rule is still that the DLL reference count is prevented\nfrom dropping to zero while a callback is pending,\nbut instead of incrementing the reference count each time\nwe scheduled a callback,\nwe&#8217;ll increment it only when the number of callbacks goes\nfrom zero to nonzero.\nConversely, we decrement the reference count only when the\nnumber of callbacks drops from nonzero to zero.\n<\/p>\n<p>\nYou can think of this as proxying the reference count,\nsimilar to how COM creates proxies that collapse\n<code>Add&shy;Ref<\/code> and <code>Release<\/code>\ncalls and signal the remote object only when the reference\ncount transitions from zero to nonzero or vice versa.\n<\/p>\n<p>\nThis optimization works for\n<code>Free&shy;Library&shy;And&shy;Exit&shy;Thread<\/code>,\ntoo,\nso let&#8217;s fold that in while we&#8217;re there.\n<\/p>\n<pre>\nLONG g_lProxyRefCount = 0;\nBOOL ProxyAddRefThisDll()\n{\n if (InterlockedIncrement(&amp;g_lProxyRefCount) == 1) {\n  HMODULE hmod;\n  return GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,\n                           reinterpret_cast&lt;LPCTSTR&gt;(g_hinstSelf),\n                           &amp;hmod);\n }\n return TRUE;\n}\nDECLSPEC_NORETURN\nvoid ProxyFreeLibraryAndExitThread(DWORD dwExitCode)\n{\n if (InterlockedDecrement(&amp;g_lProxyRefCount) == 0) {\n  FreeLibraryAndExitThread(g_hinstSelf, dwExitCode);\n } else {\n  ExitThread(dwExitCode);\n }\n}\nvoid ProxyFreeLibraryWhenCallbackReturns(PTP_CALLBACK_INSTANCE pci)\n{\n if (InterlockedDecrement(&amp;g_lProxyRefCount) == 0) {\n  FreeLibraryWhenCallbackReturns(pci, g_hinstSelf);\n }\n}\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Update: Daniel points out that there is still a race condition here, so this trick won&#8217;t work. Rats. The documentation for the Set&shy;Threadpool&shy;Callback&shy;Library says This prevents a deadlock from occurring when one thread in DllMain is waiting for the callback to end, and another thread that is executing the callback attempts to acquire the loader [&hellip;]<\/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-2713","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Update: Daniel points out that there is still a race condition here, so this trick won&#8217;t work. Rats. The documentation for the Set&shy;Threadpool&shy;Callback&shy;Library says This prevents a deadlock from occurring when one thread in DllMain is waiting for the callback to end, and another thread that is executing the callback attempts to acquire the loader [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/2713","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=2713"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/2713\/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=2713"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=2713"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=2713"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}