Skip to content

Commit 4370474

Browse files
alexey-zakharovnoahfalkjkotas
authored
Notify profiler about unloads of all class instances when module is unloaded (#114107)
* Notify profiler about unloads of all class instances when module is unloaded * Apply suggestions from code review Co-authored-by: Noah Falk <noahfalk@users.noreply.github.com> * Move NotifyUnload method * Apply suggestions from code review Co-authored-by: Jan Kotas <jkotas@microsoft.com> * Removed notification profiler code from the test --------- Co-authored-by: Noah Falk <noahfalk@users.noreply.github.com> Co-authored-by: Jan Kotas <jkotas@microsoft.com>
1 parent 1c54b13 commit 4370474

File tree

12 files changed

+293
-74
lines changed

12 files changed

+293
-74
lines changed

src/coreclr/vm/ceeload.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,7 +1389,9 @@ void Module::FreeClassTables()
13891389
MethodTable * pMT = typeDefIter.GetElement();
13901390
if (pMT != NULL)
13911391
{
1392-
pMT->GetClass()->Destruct(pMT);
1392+
ClassLoader::NotifyUnload(pMT, true);
1393+
pMT->GetClass()->Destruct();
1394+
ClassLoader::NotifyUnload(pMT, false);
13931395
}
13941396
}
13951397

@@ -1405,14 +1407,20 @@ void Module::FreeClassTables()
14051407
{
14061408
TypeHandle th = pEntry->GetTypeHandle();
14071409

1410+
// Array EEClass doesn't need notification and there is no work for Destruct()
1411+
if (th.IsTypeDesc())
1412+
continue;
1413+
1414+
MethodTable * pMT = th.AsMethodTable();
1415+
ClassLoader::NotifyUnload(pMT, true);
1416+
14081417
// We need to call destruct on instances of EEClass whose "canonical" dependent lives in this table
1409-
// There is nothing interesting to destruct on array EEClass
1410-
if (!th.IsTypeDesc())
1418+
if (pMT->IsCanonicalMethodTable())
14111419
{
1412-
MethodTable * pMT = th.AsMethodTable();
1413-
if (pMT->IsCanonicalMethodTable())
1414-
pMT->GetClass()->Destruct(pMT);
1420+
pMT->GetClass()->Destruct();
14151421
}
1422+
1423+
ClassLoader::NotifyUnload(pMT, false);
14161424
}
14171425
}
14181426
}

src/coreclr/vm/class.cpp

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -54,55 +54,16 @@ void *EEClass::operator new(
5454
}
5555

5656
//*******************************************************************************
57-
void EEClass::Destruct(MethodTable * pOwningMT)
57+
void EEClass::Destruct()
5858
{
5959
CONTRACTL
6060
{
6161
NOTHROW;
6262
GC_TRIGGERS;
6363
FORBID_FAULT;
64-
PRECONDITION(pOwningMT != NULL);
6564
}
6665
CONTRACTL_END
6766

68-
#ifdef PROFILING_SUPPORTED
69-
// If profiling, then notify the class is getting unloaded.
70-
{
71-
BEGIN_PROFILER_CALLBACK(CORProfilerTrackClasses());
72-
{
73-
// Calls to the profiler callback may throw, or otherwise fail, if
74-
// the profiler AVs/throws an unhandled exception/etc. We don't want
75-
// those failures to affect the runtime, so we'll ignore them.
76-
//
77-
// Note that the profiler callback may turn around and make calls into
78-
// the profiling runtime that may throw. This try/catch block doesn't
79-
// protect the profiler against such failures. To protect the profiler
80-
// against that, we will need try/catch blocks around all calls into the
81-
// profiling API.
82-
//
83-
// (Bug #26467)
84-
//
85-
86-
FAULT_NOT_FATAL();
87-
88-
EX_TRY
89-
{
90-
GCX_PREEMP();
91-
92-
(&g_profControlBlock)->ClassUnloadStarted((ClassID) pOwningMT);
93-
}
94-
EX_CATCH
95-
{
96-
// The exception here came from the profiler itself. We'll just
97-
// swallow the exception, since we don't want the profiler to bring
98-
// down the runtime.
99-
}
100-
EX_END_CATCH(RethrowTerminalExceptions);
101-
}
102-
END_PROFILER_CALLBACK();
103-
}
104-
#endif // PROFILING_SUPPORTED
105-
10667
#ifdef FEATURE_COMINTEROP
10768
// clean up any COM Data
10869
if (m_pccwTemplate)
@@ -147,29 +108,6 @@ void EEClass::Destruct(MethodTable * pOwningMT)
147108
if (GetSparseCOMInteropVTableMap() != NULL)
148109
delete GetSparseCOMInteropVTableMap();
149110
#endif // FEATURE_COMINTEROP
150-
151-
#ifdef PROFILING_SUPPORTED
152-
// If profiling, then notify the class is getting unloaded.
153-
{
154-
BEGIN_PROFILER_CALLBACK(CORProfilerTrackClasses());
155-
{
156-
// See comments in the call to ClassUnloadStarted for details on this
157-
// FAULT_NOT_FATAL marker and exception swallowing.
158-
FAULT_NOT_FATAL();
159-
EX_TRY
160-
{
161-
GCX_PREEMP();
162-
(&g_profControlBlock)->ClassUnloadFinished((ClassID) pOwningMT, S_OK);
163-
}
164-
EX_CATCH
165-
{
166-
}
167-
EX_END_CATCH(RethrowTerminalExceptions);
168-
}
169-
END_PROFILER_CALLBACK();
170-
}
171-
#endif // PROFILING_SUPPORTED
172-
173111
}
174112

175113
//*******************************************************************************

src/coreclr/vm/class.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW!
726726

727727
#ifndef DACCESS_COMPILE
728728
void *operator new(size_t size, LoaderHeap* pHeap, AllocMemTracker *pamTracker);
729-
void Destruct(MethodTable * pMT);
729+
void Destruct();
730730

731731
static EEClass * CreateMinimalClass(LoaderHeap *pHeap, AllocMemTracker *pamTracker);
732732
#endif // !DACCESS_COMPILE

src/coreclr/vm/clsload.cpp

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2614,7 +2614,7 @@ TypeHandle ClassLoader::DoIncrementalLoad(const TypeKey *pTypeKey, TypeHandle ty
26142614

26152615
if (typeHnd.GetLoadLevel() >= CLASS_LOAD_EXACTPARENTS)
26162616
{
2617-
Notify(typeHnd);
2617+
NotifyLoad(typeHnd);
26182618
}
26192619

26202620
return typeHnd;
@@ -2828,7 +2828,7 @@ TypeHandle ClassLoader::PublishType(const TypeKey *pTypeKey, TypeHandle typeHnd)
28282828
// Notify profiler and debugger that a type load has completed
28292829
// Also adjust perf counters
28302830
/*static*/
2831-
void ClassLoader::Notify(TypeHandle typeHnd)
2831+
void ClassLoader::NotifyLoad(TypeHandle typeHnd)
28322832
{
28332833
CONTRACTL
28342834
{
@@ -2896,6 +2896,66 @@ void ClassLoader::Notify(TypeHandle typeHnd)
28962896
}
28972897
}
28982898

2899+
// Notify profiler that a MethodTable is being unloaded
2900+
/*static*/
2901+
void ClassLoader::NotifyUnload(MethodTable* pMT, bool unloadStarted)
2902+
{
2903+
CONTRACTL
2904+
{
2905+
NOTHROW;
2906+
GC_TRIGGERS;
2907+
MODE_ANY;
2908+
FORBID_FAULT;
2909+
PRECONDITION(pMT != NULL);
2910+
}
2911+
CONTRACTL_END
2912+
2913+
#ifdef PROFILING_SUPPORTED
2914+
// If profiling, then notify the class is getting unloaded.
2915+
{
2916+
BEGIN_PROFILER_CALLBACK(CORProfilerTrackClasses());
2917+
{
2918+
if (pMT->ContainsGenericVariables() || pMT->IsArray())
2919+
{
2920+
// Don't notify the profiler about types with unbound variables or arrays.
2921+
// See ClassLoadStarted callback for more details.
2922+
return;
2923+
}
2924+
2925+
// Calls to the profiler callback may throw, or otherwise fail, if
2926+
// the profiler AVs/throws an unhandled exception/etc. We don't want
2927+
// those failures to affect the runtime, so we'll ignore them.
2928+
//
2929+
// Note that the profiler callback may turn around and make calls into
2930+
// the profiling runtime that may throw. This try/catch block doesn't
2931+
// protect the profiler against such failures. To protect the profiler
2932+
// against that, we will need try/catch blocks around all calls into the
2933+
// profiling API.
2934+
//
2935+
2936+
FAULT_NOT_FATAL();
2937+
2938+
EX_TRY
2939+
{
2940+
GCX_PREEMP();
2941+
2942+
if (unloadStarted)
2943+
(&g_profControlBlock)->ClassUnloadStarted((ClassID) pMT);
2944+
else
2945+
(&g_profControlBlock)->ClassUnloadFinished((ClassID) pMT, S_OK);
2946+
}
2947+
EX_CATCH
2948+
{
2949+
// The exception here came from the profiler itself. We'll just
2950+
// swallow the exception, since we don't want the profiler to bring
2951+
// down the runtime.
2952+
}
2953+
EX_END_CATCH(RethrowTerminalExceptions);
2954+
}
2955+
END_PROFILER_CALLBACK();
2956+
}
2957+
#endif // PROFILING_SUPPORTED
2958+
}
28992959

29002960
//-----------------------------------------------------------------------------
29012961
// Common helper for LoadTypeHandleForTypeKey and LoadTypeHandleForTypeKeyNoLock.

src/coreclr/vm/clsload.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,9 @@ class ClassLoader
910910

911911
// Notify profiler and debugger that a type load has completed
912912
// Also update perf counters
913-
static void Notify(TypeHandle typeHnd);
913+
static void NotifyLoad(TypeHandle typeHnd);
914+
// Notify profiler that a MethodTable is being unloaded
915+
static void NotifyUnload(MethodTable* pMT, bool unloadStarted);
914916

915917
// Phase CLASS_LOAD_EXACTPARENTS of class loading
916918
// Load exact parents and interfaces and dependent structures (generics dictionary, vtable fixes)

src/tests/profiler/native/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ project(Profiler)
44

55
set(SOURCES
66
assemblyprofiler/assemblyprofiler.cpp
7+
classload/classload.cpp
78
eltprofiler/slowpatheltprofiler.cpp
89
enumthreadsprofiler/enumthreadsprofiler.cpp
910
eventpipeprofiler/eventpipereadingprofiler.cpp

src/tests/profiler/native/classfactory.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "inlining/inlining.h"
2323
#include "moduleload/moduleload.h"
2424
#include "assemblyprofiler/assemblyprofiler.h"
25+
#include "classload/classload.h"
2526

2627
ClassFactory::ClassFactory(REFCLSID clsid) : refCount(0), clsid(clsid)
2728
{
@@ -149,6 +150,10 @@ HRESULT STDMETHODCALLTYPE ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFI
149150
{
150151
profiler = new EnumThreadsProfiler();
151152
}
153+
else if (clsid == ClassLoad::GetClsid())
154+
{
155+
profiler = new ClassLoad();
156+
}
152157
else
153158
{
154159
printf("No profiler found in ClassFactory::CreateInstance. Did you add your profiler to the list?\n");
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#include "classload.h"
5+
6+
GUID ClassLoad::GetClsid()
7+
{
8+
// {A1B2C3D4-E5F6-7890-1234-56789ABCDEF0}
9+
GUID clsid = {0xa1b2c3d4, 0xe5f6, 0x7890, {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}};
10+
return clsid;
11+
}
12+
13+
HRESULT ClassLoad::Initialize(IUnknown* pICorProfilerInfoUnk)
14+
{
15+
Profiler::Initialize(pICorProfilerInfoUnk);
16+
17+
HRESULT hr = S_OK;
18+
printf("Setting COR_PRF_MONITOR_CLASS_LOADS mask\n");
19+
if (FAILED(hr = pCorProfilerInfo->SetEventMask2(COR_PRF_MONITOR_CLASS_LOADS, 0)))
20+
{
21+
_failures++;
22+
printf("FAIL: ICorProfilerInfo::SetEventMask2() failed hr=0x%x", hr);
23+
return hr;
24+
}
25+
26+
return S_OK;
27+
}
28+
29+
HRESULT ClassLoad::ClassLoadStarted(ClassID classId)
30+
{
31+
_classLoadStartedCount++;
32+
return S_OK;
33+
}
34+
35+
HRESULT ClassLoad::ClassLoadFinished(ClassID classId, HRESULT hrStatus)
36+
{
37+
_classLoadFinishedCount++;
38+
return S_OK;
39+
}
40+
41+
HRESULT ClassLoad::ClassUnloadStarted(ClassID classId)
42+
{
43+
_classUnloadStartedCount++;
44+
wprintf(L"ClassUnloadStarted: %s\n", GetClassIDName(classId).ToCStr());
45+
46+
return S_OK;
47+
}
48+
49+
HRESULT ClassLoad::ClassUnloadFinished(ClassID classID, HRESULT hrStatus)
50+
{
51+
_classUnloadFinishedCount++;
52+
return S_OK;
53+
}
54+
55+
56+
HRESULT ClassLoad::Shutdown()
57+
{
58+
Profiler::Shutdown();
59+
60+
if(_failures == 0
61+
&& (_classLoadStartedCount != 0)
62+
// Expect unloading of UnloadLibrary.TestClass and
63+
// List<UnloadLibrary.TestClass> with all its base classes with everything used in List constructor:
64+
// - UnloadLibrary.TestClass
65+
// - System.Collections.Generic.IEnumerable`1<UnloadLibrary.TestClass>
66+
// - System.Collections.Generic.IList`1<UnloadLibrary.TestClass>
67+
// - System.Collections.Generic.IReadOnlyCollection`1<UnloadLibrary.TestClass>
68+
// - System.Collections.Generic.IReadOnlyList`1<UnloadLibrary.TestClass>
69+
// - System.Collections.Generic.List`1<UnloadLibrary.TestClass>
70+
// - System.Collections.Generic.ICollection`1<UnloadLibrary.TestClass>
71+
&& (_classUnloadStartedCount == 7)
72+
&& (_classLoadStartedCount == _classLoadFinishedCount)
73+
&& (_classUnloadStartedCount == _classUnloadFinishedCount))
74+
{
75+
printf("PROFILER TEST PASSES\n");
76+
}
77+
else
78+
{
79+
printf("PROFILER TEST FAILED, failures=%d classLoadStartedCount=%d classLoadFinishedCount=%d classUnloadStartedCount=%d classUnloadFinishedCount=%d\n",
80+
_failures.load(),
81+
_classLoadStartedCount.load(),
82+
_classLoadFinishedCount.load(),
83+
_classUnloadStartedCount.load(),
84+
_classUnloadFinishedCount.load());
85+
}
86+
87+
fflush(stdout);
88+
89+
return S_OK;
90+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#pragma once
5+
6+
#include "../profiler.h"
7+
8+
class ClassLoad : public Profiler
9+
{
10+
public:
11+
12+
ClassLoad() :
13+
Profiler(),
14+
_classLoadStartedCount(0),
15+
_classLoadFinishedCount(0),
16+
_classUnloadStartedCount(0),
17+
_classUnloadFinishedCount(0),
18+
_failures(0)
19+
{
20+
}
21+
22+
static GUID GetClsid();
23+
virtual HRESULT STDMETHODCALLTYPE Initialize(IUnknown* pICorProfilerInfoUnk);
24+
virtual HRESULT STDMETHODCALLTYPE Shutdown();
25+
26+
virtual HRESULT STDMETHODCALLTYPE ClassLoadStarted(ClassID classId);
27+
virtual HRESULT STDMETHODCALLTYPE ClassLoadFinished(ClassID classId, HRESULT hrStatus);
28+
virtual HRESULT STDMETHODCALLTYPE ClassUnloadStarted(ClassID classId);
29+
virtual HRESULT STDMETHODCALLTYPE ClassUnloadFinished(ClassID classId, HRESULT hrStatus);
30+
31+
private:
32+
std::atomic<int> _classLoadStartedCount;
33+
std::atomic<int> _classLoadFinishedCount;
34+
std::atomic<int> _classUnloadStartedCount;
35+
std::atomic<int> _classUnloadFinishedCount;
36+
std::atomic<int> _failures;
37+
};

0 commit comments

Comments
 (0)