Microsoft Edge not responding after start-up

After upgrading Microsoft Edge, some users in a Citrix Server 2016 environment had the problem that Microsoft Edge hangs after starting.
microsoft edge not responding

The behavior can vary (sometimes the whole page is blank). However, because the problem has affected some users, it is worth taking a closer look.

It was found relatively quickly that a new Edge profile (deleting C:\Users\%username%\AppData\Local\Microsoft\Edge\User Data\Default) solves the problem.
Usually, this is not a problem, but the loss of the bookmarks is of course not pleasant.

The good thing is, we know it’s related to the profile. We don’t need to troubleshoot anything at the machine level or look at registry keys. The problem is somewhere in the profile.

I created a process monitor log and stopped recording when the title bar froze. I filtered to msedge.exe and counted the results:
microsoft edge not responding procmon log

Lots of events. ACCESS DENIED is always interesting to me. But what do we know until now? Only file activity is interested and something inside C:\users\%username%\AppData\Local\Microsoft\Edge\User Data. So we filter our Log further.
microsoft edge not responding procmon log

No ACCESS DENIED and some others disappears. To sum up, everything seems okayish. No further hints here.

I thought it’s a good idea to capture a memory dump. Because the title bar says “not responding”, I captured a memory dump with: procdump -ma {procid}. We load the .dmp file into WinDbg.

Tip: find the right stack
Usually a process contains several threads. How many? Type: ~ into the WinDbg. In my case the memory dump had 45 threads.
You have to know that most threads are WorkerThreads waiting for work. We don’t need to check these.
A characteristic property of these WorkerThreads are there length. They’re usually short (depth is about ~8).

An amazing way is to use the PDE extension by Andrew Richards.
You can load the extension .load C:\path\x64\winext\PDE.dll.
After that you can run: !deep 15. The command will prompt all call stacks with a depth 15 or higher.

Now we want to check the stack:

0:000> k
 # Child-SP          RetAddr               Call Site
00 000000fd`b5bfd5b0 00007ffd`d1fd189b     msedge!std::Cr::__tree<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> >,std::Cr::less<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> > >,std::Cr::allocator<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> > > >::__find_equal<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> > >+0xa2
01 000000fd`b5bfd620 00007ffd`d1fd1694     msedge!std::Cr::__tree<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> >,std::Cr::less<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> > >,std::Cr::allocator<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> > > >::__find_equal<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> > >+0x17f
02 000000fd`b5bfd6a0 00007ffd`d2e44eb8     msedge!std::Cr::__tree<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> >,std::Cr::less<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> > >,std::Cr::allocator<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> > > >::__emplace_hint_unique_key_args<std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> >,std::Cr::basic_string<char16_t,std::Cr::char_traits<char16_t>,std::Cr::allocator<char16_t> > const & __ptr64>+0x50
03 000000fd`b5bfd710 00007ffd`d2e44618     msedge!autofill::AutofillProfileComparator::GetNamePartVariants+0x480
04 000000fd`b5bfd8c0 00007ffd`d2e43fd8     msedge!autofill::AutofillProfileComparator::IsNameVariantOf+0xaa
05 000000fd`b5bfdae0 00007ffd`d2e43910     msedge!autofill::AutofillProfileComparator::HaveMergeableNames+0x298
06 000000fd`b5bfdc20 00007ffd`d2e3aec5     msedge!autofill::AutofillProfileComparator::AreMergeable+0x8e
07 000000fd`b5bfdc60 00007ffd`ccddac54     msedge!autofill::PersonalDataManagerCleaner::DedupeProfiles+0x1fd
08 000000fd`b5bfdd60 00007ffd`ccdd7ced     msedge!autofill::PersonalDataManagerCleaner::ApplyDedupingRoutine+0x1e8
09 000000fd`b5bfdea0 00007ffd`ccdd7c40     msedge!autofill::PersonalDataManagerCleaner::ApplyAddressFixesAndCleanups+0x15
0a 000000fd`b5bfded0 00007ffd`ccdd90c0     msedge!autofill::PersonalDataManagerCleaner::CleanupDataAndNotifyPersonalDataObservers+0x120
0b 000000fd`b5bfdf40 00007ffd`ccdd8a1c     msedge!autofill::AlternativeStateNameMapUpdater::LoadStatesData+0xcc
0c 000000fd`b5bfe070 00007ffd`ccdd7c02     msedge!autofill::AlternativeStateNameMapUpdater::PopulateAlternativeStateNameMap+0xd0
0d 000000fd`b5bfe220 00007ffd`ccdd6eec     msedge!autofill::PersonalDataManagerCleaner::CleanupDataAndNotifyPersonalDataObservers+0xe2
0e 000000fd`b5bfe290 00007ffd`cc918780     msedge!autofill::PersonalDataManager::OnWebDataServiceRequestDone+0x43c
0f 000000fd`b5bfe390 00007ffd`cdf911e7     msedge!WebDataRequestManager::RequestCompletedOnThread+0x70
10 000000fd`b5bfe3e0 00007ffd`cdfc8142     msedge!base::internal::Invoker<base::internal::BindState<void (WebDataRequestManager::*)(std::Cr::unique_ptr<WebDataRequest,std::Cr::default_delete<WebDataRequest> >, std::Cr::unique_ptr<WDTypedResult,std::Cr::default_delete<WDTypedResult> >),scoped_refptr<WebDataRequestManager>,std::Cr::unique_ptr<WebDataRequest,std::Cr::default_delete<WebDataRequest> >,std::Cr::unique_ptr<WDTypedResult,std::Cr::default_delete<WDTypedResult> > >,void ()>::RunOnce+0x47
11 000000fd`b5bfe420 00007ffd`ca23669f     msedge!base::TaskAnnotator::RunTaskImpl+0x122
12 000000fd`b5bfe560 00007ffd`ca782a81     msedge!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl+0x58f
13 000000fd`b5bfe870 00007ffd`cbaca689     msedge!base::MessagePumpForUI::DoRunLoop+0xcc1
14 000000fd`b5bfea40 00007ffd`cc403cfe     msedge!base::MessagePumpWin::Run+0x79
15 000000fd`b5bfeaa0 00007ffd`cc40b2ac     msedge!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run+0x11e
16 000000fd`b5bfeb50 00007ffd`cc2e98f0     msedge!base::RunLoop::Run+0xfc
17 000000fd`b5bfec50 00007ffd`cc2e95cc     msedge!content::BrowserMainLoop::RunMainMessageLoop+0x9a
18 000000fd`b5bfecc0 00007ffd`cc2e8caa     msedge!content::BrowserMain+0xa4
19 000000fd`b5bfed70 00007ffd`cc2e7e42     msedge!content::RunBrowserProcessMain+0xe0
1a 000000fd`b5bfee90 00007ffd`cc2a651e     msedge!content::ContentMainRunnerImpl::RunBrowser+0x46e
1b 000000fd`b5bfeff0 00007ffd`cc1cf536     msedge!content::ContentMainRunnerImpl::Run+0x31e
1c 000000fd`b5bff140 00007ffd`cc1ce2f4     msedge!content::RunContentProcess+0x2e3
1d 000000fd`b5bff360 00007ffd`cc1cd6be     msedge!content::ContentMain+0x64
1e 000000fd`b5bff3f0 00007ff6`2ff25626     msedge!ChromeMain+0x27e
1f 000000fd`b5bff710 00007ff6`30013be2     msedge_exe!MainDllLoader::Launch+0x386
20 000000fd`b5bff9a0 00007ff6`2ffceca2     msedge_exe!wWinMain+0xf2c
21 000000fd`b5bffec0 00007ffe`4a2384d4     msedge_exe!__scrt_common_main_seh+0x106
22 000000fd`b5bfff00 00007ffe`4a9a1791     kernel32!BaseThreadInitThunk+0x14
23 000000fd`b5bfff30 00000000`00000000     ntdll!RtlUserThreadStart+0x21

You have to read the stack from bottom to top. The first few lines seem the normal “startup”-routine. The part seems interesting:

03 000000fd`b5bfd710 00007ffd`d2e44618     msedge!autofill::AutofillProfileComparator::GetNamePartVariants+0x480
04 000000fd`b5bfd8c0 00007ffd`d2e43fd8     msedge!autofill::AutofillProfileComparator::IsNameVariantOf+0xaa
05 000000fd`b5bfdae0 00007ffd`d2e43910     msedge!autofill::AutofillProfileComparator::HaveMergeableNames+0x298
06 000000fd`b5bfdc20 00007ffd`d2e3aec5     msedge!autofill::AutofillProfileComparator::AreMergeable+0x8e
07 000000fd`b5bfdc60 00007ffd`ccddac54     msedge!autofill::PersonalDataManagerCleaner::DedupeProfiles+0x1fd
08 000000fd`b5bfdd60 00007ffd`ccdd7ced     msedge!autofill::PersonalDataManagerCleaner::ApplyDedupingRoutine+0x1e8
09 000000fd`b5bfdea0 00007ffd`ccdd7c40     msedge!autofill::PersonalDataManagerCleaner::ApplyAddressFixesAndCleanups+0x15
0a 000000fd`b5bfded0 00007ffd`ccdd90c0     msedge!autofill::PersonalDataManagerCleaner::CleanupDataAndNotifyPersonalDataObservers+0x120
0b 000000fd`b5bfdf40 00007ffd`ccdd8a1c     msedge!autofill::AlternativeStateNameMapUpdater::LoadStatesData+0xcc
0c 000000fd`b5bfe070 00007ffd`ccdd7c02     msedge!autofill::AlternativeStateNameMapUpdater::PopulateAlternativeStateNameMap+0xd0
0d 000000fd`b5bfe220 00007ffd`ccdd6eec     msedge!autofill::PersonalDataManagerCleaner::CleanupDataAndNotifyPersonalDataObservers+0xe2
0e 000000fd`b5bfe290 00007ffd`cc918780     msedge!autofill::PersonalDataManager::OnWebDataServiceRequestDone+0x43c
0f 000000fd`b5bfe390 00007ffd`cdf911e7     msedge!WebDataRequestManager::RequestCompletedOnThread+0x70

The WebDataRequestManager leads to all the autofill stuff. I captured a memory dump a few seconds later, and we see the same stack. You can google some functions and learn a bit about what things do (without reading the source code).

Okay, what can we say right now? It seems that the stack is related to Autofill. And we also know, that there is some “corrupt” file in our browser profile.
A next search on the internet could be: “chromium autofill file location” and we would then find the following superuser.com article. We learn that the “autofill file” is {AppData}\Microsoft\Edge\User Data\Default\Web Data. Interesting.

I’m not going to lie, it wasn’t like that … and I used another tool to find the file.

Because ProcMon leads to nowhere, I thought a ETL trace would be a good idea. You can use the Windows Performance Recorder or UIforETW. After I captured the needed .ETL file, I opened it with Windows Performance Analyzer - you can get it via Microsoft Store.

WPA msedge.exe

We see something we would expect. Usually something like that is called CPU saturation and in our case one core is working hard for msedge.exe (100/16 = 6,25).

WPA msedge.exe stack

Wee see that the stack traces contains all these autofill stuff, we already know from the memory dump. But we want the file.

We take the disk usage to our analysis window and select Utilization by Process, Path Name, Stack.

WPA msedge.exe file activity

Interesting our friend Web Data is back. No completion time, so it’s still a outstanding file access.

When we delete the file, MS Edge opens again without hangs.

We could find the problematic file relatively easily. ProcMon didn’t help but WinDbg had the right hints and WPA showed the relevant file.
The issue itself was a little bit odd. Sometimes I restored the old Web Data (for repro) .. but it worked. I had also a case, when I waited very long (>1h?) it repaired itself and worked again.

I could have found the solution much quicker with WinDbg or WPA or another tool? Let me know!

Happy troubleshooting.