Debugging event leaks
Events can be lame. Wonderful, but lame. One thing that bugs me about the subscribe model is that in most of our code we really are just thinking in terms of one subscriber. But the event as a tool encourages thinking only of many subscribers. Into this hole we fall...
The bug is that you have one subscriber to an event, who goes away and comes back later, possibly as a different instance of the same object. Of course he's forgotten to unsubscribe from the event. Meanwhile, the event source is some kind of singleton in your system (in our case it's an expensive-to-create object, lazily initialized and then kept around).
Later, after crash reports come in, you find dozens of zombie consumer objects held in the crypt of the event handler. This wouldn't be so bad, but the zombies clutch in their hands very valuable resources that consume a lot of memory elsewhere.
The object array at 0x3b110b38 is the "crypt," let's see who is in there:
Taking one at random:
A few nice links on the topic are here, which is a nice rundown of strategies to deal with the drawbacks of events, and a class from Jeff Richter here.
The bug is that you have one subscriber to an event, who goes away and comes back later, possibly as a different instance of the same object. Of course he's forgotten to unsubscribe from the event. Meanwhile, the event source is some kind of singleton in your system (in our case it's an expensive-to-create object, lazily initialized and then kept around).
Later, after crash reports come in, you find dozens of zombie consumer objects held in the crypt of the event handler. This wouldn't be so bad, but the zombies clutch in their hands very valuable resources that consume a lot of memory elsewhere.
(output from !gcroot) ... 01acbce4(HistoScanning.Visualization.VizController.VizController)-> 14e13e0c(System.EventHandler`1[[HistoScanning.VizInterfaces.PushToolEventArgs, HistoScanning.VizInterfaces]])-> 3b110b38(System.Object[])
The object array at 0x3b110b38 is the "crypt," let's see who is in there:
0:000> !dumparray 3b110b38 Name: System.Object[] MethodTable: 793042f4 EEClass: 790eda64 Size: 144(0x90) bytes Array: Rank 1, Number of elements 32, Type CLASS Element Methodtable: 79330740 [0] 01acdb9c [1] 0e153f30 [2] 02024884 [3] 0203ddc0 [4] 02056444 [5] 31b35dac [6] 0e51b69c [7] 323258cc [8] 3233c5bc [9] 32644078 [10] 32657a84 [11] 32794b64 [12] 31f88780 [13] 3aa3491c [14] 3aa7c034 [15] 3adda58c [16] 3b110b18 [17] 3b127c80 [18] 3b42a008 [19] 149f03fc [20] 14b0f778 [21] 14e13dec [22] null [23] null [24] null [25] null [26] null [27] null [28] null [29] null [30] null [31] null
Taking one at random:
0:000> !do 0203ddc0
Name: System.EventHandler`1[[HistoScanning.VizInterfaces.PushToolEventArgs, HistoScanning.VizInterfaces]]
MethodTable: 08cadb2c
EEClass: 790c3518
Size: 32(0x20) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
79330740 40000ff 4 System.Object 0 instance 0203dcdc _target
7932ff98 4000100 8 ...ection.MethodBase 0 instance 00000000 _methodBase
793333ec 4000101 c System.IntPtr 1 instance 33801b8 _methodPtr
793333ec 4000102 10 System.IntPtr 1 instance 0 _methodPtrAux
79330740 400010c 14 System.Object 0 instance 00000000 _invocationList
793333ec 400010d 18 System.IntPtr 1 instance 0 _invocationCount
0:000> !do 0203dcdc
Name: HistoScanning.Prost8.ViewModels.PushToolsViewModel
MethodTable: 251f55b0
EEClass: 2306cf38
Size: 68(0x44) bytes
(C:\Program Files\AMD\HistoScanning\Prost8.ViewModels.dll)
Fields:
MT Field Offset Type VT Attr Value Name
7932ca34 400007e 8 System.Guid 1 instance 0203dce4 uniqueId
793045f0 400007f 4 System.Boolean 1 instance 1 disposed__BackingField
...
Ah. A disposed PushToolsViewModel, the very definition of zombie, along with 20 friends. Back to the original event handler:
0:000> !do 14e13e0c
Name: System.EventHandler`1[[HistoScanning.VizInterfaces.PushToolEventArgs, HistoScanning.VizInterfaces]]
MethodTable: 08cadb2c
EEClass: 790c3518
Size: 32(0x20) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
79330740 40000ff 4 System.Object 0 instance 14e13e0c _target
7932ff98 4000100 8 ...ection.MethodBase 0 instance 00000000 _methodBase
793333ec 4000101 c System.IntPtr 1 instance 95b1e8 _methodPtr
793333ec 4000102 10 System.IntPtr 1 instance 790fe7b0 _methodPtrAux
79330740 400010c 14 System.Object 0 instance 3b110b38 _invocationList
793333ec 400010d 18 System.IntPtr 1 instance 16 _invocationCount
Examine the _invocationList fields of your events to find these characters. Either make sure consumers unsubscribe, or figure out a time when you can blank out the consumers en masse with a statement like
...
ToolChanged = delegate { };
...
It's good to do that anyway on dispose of the source object, but in my case we can't dispose of it because of cost.A few nice links on the topic are here, which is a nice rundown of strategies to deal with the drawbacks of events, and a class from Jeff Richter here.


0 Comments:
Post a Comment
Subscribe to Post Comments [Atom]
<< Home