A: background
1. Tell a story
Last week, a friend of mine came to me, saying that his program CPU and handle are increasing, there is no turning back trend. After several days of checking, there is no progress, Tejia WX asked for help, the screenshots are as follows:
I plan to use a separate article to investigate and read the CPU explosion problem. This article will first talk about handle leakage. After all, I have written more than 20 articles, and this is the first time to talk about handle leakage.
2. What is a handle
A managed layer holds a reference to an unmanaged resource. With this reference, we can forcibly reclaim an unmanaged resource. What is an unmanaged resource? My personal understanding is that the places that gc can’t handle are unmanaged resources.
Usually include this type of handle class: FileStream, Socket, etc., if you have this front base, then you can use WinDBG to analyze!
Two: WinDBG analysis
1. Look at symptoms
Handle =8770 =8770 =8770 =8770 =8770 =8770 Before saying this, we have not encountered this phenomenon, is no matter how the program leaks, as long as we exit the EXE, then all the resources will be magically released, whether it is managed resources or unmanaged resources, so say I believe there are very curious friends this is how to achieve?? You can think about it for 10 seconds.
Reveal the answer! In short, the CLR maintains a handle table internally. When the program is closed, the CLR forces the release of all handles in the handle table. Then the problem is simple. Gchandles command.
2. View the handle table
Here is a reminder! Gchandles is scoped to AppDomain, not Process, so let’s look at the command output:
0:000> !gchandles -stat
Statistics:
MT Count TotalSize Class Name
...
00007ffccc1d2360 3 262280 System.Byte[]
00007ffccc116610 72 313224 System.Object[]
00007ffccc3814a0 8246 593712 System.Threading.OverlappedData
Total 10738 objects
Handles:
Strong Handles: 312
Pinned Handles: 18
Async Pinned Handles: 8246
Ref Count Handles: 1
Weak Long Handles: 2080
Weak Short Handles: 59
Dependent Handles: 22
Copy the code
Look from the output, there are special dazzling, a set of data that is: Async Pinned Handles = 8246 [System. The Threading. OverlappedData], what is the meaning of this? This is an OverlappedData handle associated with asynchronous IO. If an ASYNCHRONOUS IO is pinned, one byte[] is pinned, and the OverlappedData context object of the asynchronous IO is overlapped.
The next question is: since it is asynchronous IO, what type of handle is it, FileStream or Socket? To find out, we need to dig deep into the OverlappedData object with the associated command:! dumpheap -mt xxx & ! do … , please refer to the following:
0:000> !DumpHeap /d -mt 00007ffccc3814a0
Address MT Size
000001aa2acb39c8 00007ffccc3814a0 72
000001aa2acb3fd8 00007ffccc3814a0 72
000001aa2ad323d0 00007ffccc3814a0 72.0:000>!do 000001aa2acb39c8
Name: System.Threading.OverlappedData
MethodTable: 00007ffccc3814a0
EEClass: 00007ffccc37ca18
Size: 72(0x48) bytes
File: C:\xxx\xxx\vms_210819\System.Private.CoreLib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffccc21f508 40006b2 8 System.IAsyncResult 0 instance 0000000000000000 _asyncResult
00007ffccc110ae8 40006b3 10 System.Object 0 instance 000001aa2acb4020 _callback
00007ffccc381150 40006b4 18. eading.Overlapped0 instance 000001aa2acb3980 _overlapped
00007ffccc110ae8 40006b5 20 System.Object 0 instance 000001aa2acb9fe8 _userObject
00007ffccc11f130 40006b6 28 PTR 0 instance 000001aa2a9bd830 _pNativeOverlapped
00007ffccc11ecc0 40006b7 30 System.IntPtr 1 instance 0000000000000000 _eventHandle
0:000> !DumpObj /d 000001aa2acb3980
Name: System.Threading.ThreadPoolBoundHandleOverlapped
MethodTable: 00007ffccc3812a0
EEClass: 00007ffccc37c9a0
Size: 72(0x48) bytes
File: C:\xxx\xxx\vms_210819\System.Private.CoreLib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffccc3814a0 40006ba 8. ng.OverlappedData0 instance 000001aa2acb39c8 _overlappedData
00007ffccc34fcd0 40006a4 10. ompletionCallback0 instance 000001aa2acb3920 _userCallback
00007ffccc110ae8 40006a5 18 System.Object 0 instance 000001aa2acb38c8 _userState
00007ffccc380120 40006a6 20. locatedOverlapped0 instance 000001aa2acb3960 _preAllocated
00007ffccc11f130 40006a7 30 PTR 0 instance 000001aa2a9bd830 _nativeOverlapped
00007ffccc380eb8 40006a8 28. adPoolBoundHandle0 instance 000001aa2acb3900 _boundHandle
00007ffccc1171c8 40006a9 38 System.Boolean 1 instance 0 _completed
00007ffccc34fcd0 40006a3 458. ompletionCallback0 static 000001aa2acb4020 s_completionCallback
0:000> !DumpObj /d 000001aa2acb3900
Name: System.Threading.ThreadPoolBoundHandle
MethodTable: 00007ffccc380eb8
EEClass: 00007ffccc37c870
Size: 32(0x20) bytes
File: C:\xxx\xxx\vms_210819\System.Private.CoreLib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffccc1d76b0 40006a1 8. rvices.SafeHandle0 instance 000001aa2acb1d30 _handle
00007ffccc1171c8 40006a2 10 System.Boolean 1 instance 0 _isDisposed
0:000> !DumpObj /d 000001aa2acb1d30
Name: Microsoft.Win32.SafeHandles.SafeFileHandle
MethodTable: 00007ffccc3807c8
EEClass: 00007ffccc37c548
Size: 48(0x30) bytes
File: C:\xxx\xxx\xxx\System.Private.CoreLib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffccc11ecc0 4000bb4 8 System.IntPtr 1 instance 0000000000000428 handle
00007ffccc11b1e8 4000bb5 10 System.Int32 1 instance 4 _state
00007ffccc1171c8 4000bb6 14 System.Boolean 1 instance 1 _ownsHandle
00007ffccc1171c8 4000bb7 15 System.Boolean 1 instance 1 _fullyInitialized
00007ffccc2f1ae0 4001c3d 20. Private.CoreLib]]1 instance 000001aa2acb1d50 _isAsync
00007ffccc380eb8 4001c3e 18. adPoolBoundHandle0 instance 0000000000000000 <ThreadPoolBinding>k__BackingField
Copy the code
The handle value 0000000000000428 on the fifth row from the bottom is the specified handle value, which can be used next. The handle command displays detailed information about its value.
0:000> !handle 0000000000000428 7
Handle 428
Type File
Attributes 0
GrantedAccess 0x100081:
Synch
Read/List,ReadAttr
HandleCount 2
PointerCount 65489
Copy the code
Type: File Type: File Type: File
😪😪😪, although I dug up some information, this information is not enough for me to find the root cause of the problem. In terms of reference chain, these objects in Gchandles are at the top of the reference chain. In other words, I need to find some data objects downstream of the reference chain. A good entry point is to dig into the heap.
3. Find the descendants of managed heap
First we use! Dumpheap-stat take a look at the managed heap.
0:000> !dumpheap -stat
Statistics:
MT Count TotalSize Class Name
...
00007ffccc3c5e18 939360 52604160 System.Collections.Generic.SortedSet`1+Node[[System.Collections.Generic.KeyValuePair`2[[System.String, System.Private.CoreLib],[System.String, System.Private.CoreLib]], System.Private.CoreLib]]
00007ffccc1d2360 16492 69081162 System.Byte[]
000001aa2a99af00 10365 76689384 Free
00007ffccc1d1e18 1904987 116290870 System.String
Copy the code
Byte[] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] : [] From the output of the script I pulled several addresses to look at! Gcroot, it’s probably something like this.
0:000> !gcroot 000001aa47a0c030
HandleTable:
000001AA4469C090 (async pinned handle)
-> 000001AA491EB908 System.Threading.OverlappedData
-> 000001AA491EB8C0 System.Threading.ThreadPoolBoundHandleOverlapped
-> 000001AA491EB860 System.Threading.IOCompletionCallback
-> 000001AA491EAF30 System.IO.FileSystemWatcher
-> 000001AA491EB458 System.IO.FileSystemEventHandler
...
-> 000001AA47A0C030 System.String
0:000> !gcroot 000001aa2d3ea480
HandleTable:
000001AA28FE9930 (async pinned handle)
-> 000001AA2DD68220 System.Threading.OverlappedData
-> 000001AA2DD681D8 System.Threading.ThreadPoolBoundHandleOverlapped
-> 000001AA2DD68178 System.Threading.IOCompletionCallback
-> 000001AA2DD67848 System.IO.FileSystemWatcher
...
-> 000001AA2D3EA480 System.String
Copy the code
From the point of the entire chain of references, there is a System. IO. FileSystemWatcher, this and the previous analysis handle = File is consistent, and then export the string, found that most of them are associated with appSettings, as shown below:
string: appSettings:RabbitMQLogQueue
string: appSettings:MedicalMediaServerIP
string: appSettings:UseHttps
...
Copy the code
And then use! The strings command performs a fuzzy match and finds that such strings are up to 61W…
Appsettings is being watched, but there is a problem with the way it is done…
4. Look for the final answer
After giving the survey results to my friend, let my friend focus on whether there is a problem with the way of watching appsetting? A few hours later, my friend was finally found.
Appsetings are already monitored by setting reloadOnChange=true, but the writer is not familiar with this area and polling the AppSetings every 10 seconds.
Three:
In fact, the main reason for this accident is that I am not familiar with how to sense the latest data in appSettings in real time. I used netcore’s own reloadOnChange monitoring, and also used polling method for data perception. Therefore, the foundation is very important, do not take it for granted to write! 😁 😁 😁
For more high-quality dry goods: See my GitHub:dotnetfly