{"id":94035,"date":"2016-08-05T07:00:00","date_gmt":"2016-08-05T21:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=94035"},"modified":"2019-03-13T11:05:08","modified_gmt":"2019-03-13T18:05:08","slug":"20160805-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20160805-00\/?p=94035","title":{"rendered":"The case of the hung Explorer window"},"content":{"rendered":"<p>A Windows Insider reported that Explorer stopped responding whenever they opened their Downloads folder. <\/p>\n<p>We were able to obtain a memory dump during the hang, and observed that most threads were waiting for the loader lock. The loader lock was being held by this thread: <\/p>\n<pre>\nntdll!RtlpWaitOnCriticalSection\nntdll!RtlpEnterCriticalSectionContended\nGdiPlus!GdiplusStartupCriticalSection::{ctor}\nGdiPlus!GdiplusStartup\nShellExtension+...\nShellExtension+...\nShellExtension+...\nntdll!LdrpCallInitRoutine\nntdll!LdrpInitializeNode\nntdll!LdrpInitializeGraphRecurse\nntdll!LdrpInitializeGraph\nntdll!LdrpPrepareModuleForExecution\nntdll!LdrpLoadDllInternal\nntdll!LdrpLoadDll\nntdll!LdrLoadDll\nKERNELBASE!LoadLibraryExW\n[...]\ncombase!CoCreateInstanceEx\ncombase!CoCreateInstance\nwindows_storage!_SHCoCreateInstance\nwindows_storage!CRegFolder::_CreateCachedRegFolder\nwindows_storage!CRegFolder::_CreateCachedRegFolder\nwindows_storage!CRegFolder::_BindToItem\nwindows_storage!CRegFolder::BindToObject\nwindows_storage!CShellItem::_BindToHandlerLegacy\nwindows_storage!CShellItem::BindToHandler\n[...]\nexplorerframe!CNscEnumTask::InternalResumeRT\nexplorerframe!CRunnableTask::Run\n<\/pre>\n<p>This thread was waiting on a GDI+ critical section, which was being held here: <\/p>\n<pre>\nKERNELBASE!WaitForSingleObjectEx\nGdiPlus!BackgroundThreadShutdown\nGdiPlus!InternalGdiplusShutdown\nGdiPlus!GdiplusShutdown\nshell32!CGraphicsInit::~CGraphicsInit\nshell32!CImageFactory::{dtor}\nshell32!CImageFactory::`scalar deleting destructor'\nshell32!CImageFactory::Release\nshell32!IsImageSizeSufficientForRequestedSize\nshell32!_ExtactIconFromImage\nshell32!_ExtractIconsFromImage\nshell32!ExtractIconsUsingResourceManager\nshell32!_ExtractIcons\nshell32!SHDefExtractIconW\n[...]\nwindows_storage!CLoadSystemIconTask::InternalResumeRT\nwindows_storage!CRunnableTask::Run\nwindows_storage!CShellTask::TT_Run\nwindows_storage!CShellTaskThread::ThreadProc\nwindows_storage!CShellTaskThread::s_ThreadProc\n<\/pre>\n<p>It should now be clear what the problem is. <\/p>\n<p>On the second thread, GDI+ is shutting down because its last client decided to uninitialize it. (In this case, the last client was the system image list, which extracting the icon for a Store app, and Store app icons are PNG files, which is why GDI+ entered the picture.) <\/p>\n<p>GDI+ is waiting for its worker thread to exit so it can finish cleaning up. <\/p>\n<p>Just at this moment, the folder tree was populating itself on the first thread, and it found a third party shell extension. It dutifully loaded the third party shell extension (because that&#8217;s what shell extensions are for), and that shell extension, as part of its <code>DLL_PROCESS_ATTACH<\/code> tried to initialize GDI+. <\/p>\n<p>Here comes the deadlock. <\/p>\n<p>GDI+ was prepared for this possibility that somebody would try to initialize GDI+ while GDI+ was already in the process of shutting itself down. It solves this problem by making the shutdown run to completion (seeing as it already started), and then starting a new initialization pass. <\/p>\n<p>That shutdown is waiting for a worker thread to finish up and exit. But the thread cannot exit until it sends out its <code>DLL_THREAD_DETACH<\/code> notifications. And since DLL notifications are serialized, the <code>DLL_THREAD_DETACH<\/code> cannot be sent until the <code>DLL_PROCESS_ATTACH<\/code> completes. But the <code>DLL_PROCESS_ATTACH<\/code> for the third party shell extension is waiting for GDI+. There&#8217;s our deadlock. <\/p>\n<p>The root cause for this is that the third party shell extension is initializing GDI+ inside its <code>DLL_PROCESS_ATTACH<\/code>. This is already highly suspect even without any special insight into GDI+, and the suspicious are confirmed in the documentation for <code>GdiplusStartup<\/code>: <\/p>\n<blockquote CLASS=\"q\"><p>Do not call <b>GdiplusStartup<\/b> or <b>GdiplusShutdown<\/b> in <code>DllMain<\/code> or in any function that is called by DllMain. <\/p><\/blockquote>\n<p>My guess is that the vendor who wrote this shell extension thinks that the rule doesn&#8217;t apply to them because they passed <code>SuppressBackgroundThread = true<\/code>, thinking that by removing the background thread, they successfully avoided any deadlocks with another thread. It didn&#8217;t occur to them that the other thread might not be the GDI+ background thread. <\/p>\n<p>It also didn&#8217;t occur to them that GDI+ might <i>already be initialized<\/i> with a background thread. Furthermore, suppose the component that initialized GDI+ first (with a background thread) uninitialized GDI+ first. That call to <code>GdiplusShutdown<\/code> will not shut down GDI+ because there is still an outstanding client. And then when their DLL unloads, they call <code>GdiplusShutdown<\/code>, and that will cause a true shutdown of GDI+, which includes shutting down that background thread that they thought they had suppressed.&sup1; <\/p>\n<p>So basically it was a bad idea all around. <\/p>\n<p>I transferred this issue to the application compatibility team for outreach to the vendor, who happens to be a major corporation, so hopefully they can spare some developers to fix the deadlock. <\/p>\n<p><b>Bonus chatter<\/b>: Identifying the vendor was a bit tricky because of the extremely vague DLL name. <\/p>\n<p><b>Bonus chatter<\/b>: When I originally composed the email with my analysis of the bug, I wrote <i>application compatibility outrage<\/i> instead of <i>application compatibility outreach<\/i>. Unfortunately, I caught the mistake before hitting Send. <\/p>\n<p>&sup1;Closer investigation shows that my guess was incorrect. The code that calls <code>GdiplusStartup<\/code> leaves the background thread enabled, so I have no idea how this ever worked in isolation. It &#8220;works&#8221; only because the calls to <code>GdiplusStartup<\/code> and <code>GdiplusShutdown<\/code> are no-op because somebody else initialized GDI+ first, and is still using GDI+ at the time they unload. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Decoding the deadlock.<\/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-94035","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Decoding the deadlock.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/94035","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=94035"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/94035\/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=94035"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=94035"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=94035"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}