Skip to content

Commit 981ab62

Browse files
Notify profiler about unloads of all class instances when module is unloaded
1 parent 4c642aa commit 981ab62

File tree

10 files changed

+284
-47
lines changed

10 files changed

+284
-47
lines changed

src/coreclr/vm/ceeload.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,7 +1957,9 @@ void Module::FreeClassTables()
19571957

19581958
if (pMT != NULL && pMT->IsRestored())
19591959
{
1960+
EEClass::NotifyUnload(pMT, true);
19601961
pMT->GetClass()->Destruct(pMT);
1962+
EEClass::NotifyUnload(pMT, false);
19611963
}
19621964
}
19631965

@@ -1973,17 +1975,25 @@ void Module::FreeClassTables()
19731975
{
19741976
TypeHandle th = pEntry->GetTypeHandle();
19751977

1978+
// If class is not fully loaded there is nothing to destroy or notify about
19761979
if (!th.IsRestored())
19771980
continue;
1981+
1982+
// Type desc is handled differently
1983+
if (th.IsTypeDesc())
1984+
continue;
1985+
1986+
MethodTable * pMT = th.AsMethodTable();
1987+
EEClass::NotifyUnload(pMT, true);
19781988

19791989
// We need to call destruct on instances of EEClass whose "canonical" dependent lives in this table
19801990
// There is nothing interesting to destruct on array EEClass
1981-
if (!th.IsTypeDesc())
1991+
if (pMT->IsCanonicalMethodTable())
19821992
{
1983-
MethodTable * pMT = th.AsMethodTable();
1984-
if (pMT->IsCanonicalMethodTable())
1985-
pMT->GetClass()->Destruct(pMT);
1993+
pMT->GetClass()->Destruct(pMT);
19861994
}
1995+
1996+
EEClass::NotifyUnload(pMT, false);
19871997
}
19881998
}
19891999
}

src/coreclr/vm/class.cpp

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -84,44 +84,6 @@ void EEClass::Destruct(MethodTable * pOwningMT)
8484
}
8585
CONTRACTL_END
8686

87-
#ifdef PROFILING_SUPPORTED
88-
// If profiling, then notify the class is getting unloaded.
89-
{
90-
BEGIN_PROFILER_CALLBACK(CORProfilerTrackClasses());
91-
{
92-
// Calls to the profiler callback may throw, or otherwise fail, if
93-
// the profiler AVs/throws an unhandled exception/etc. We don't want
94-
// those failures to affect the runtime, so we'll ignore them.
95-
//
96-
// Note that the profiler callback may turn around and make calls into
97-
// the profiling runtime that may throw. This try/catch block doesn't
98-
// protect the profiler against such failures. To protect the profiler
99-
// against that, we will need try/catch blocks around all calls into the
100-
// profiling API.
101-
//
102-
// (Bug #26467)
103-
//
104-
105-
FAULT_NOT_FATAL();
106-
107-
EX_TRY
108-
{
109-
GCX_PREEMP();
110-
111-
(&g_profControlBlock)->ClassUnloadStarted((ClassID) pOwningMT);
112-
}
113-
EX_CATCH
114-
{
115-
// The exception here came from the profiler itself. We'll just
116-
// swallow the exception, since we don't want the profiler to bring
117-
// down the runtime.
118-
}
119-
EX_END_CATCH(RethrowTerminalExceptions);
120-
}
121-
END_PROFILER_CALLBACK();
122-
}
123-
#endif // PROFILING_SUPPORTED
124-
12587
#ifdef FEATURE_COMINTEROP
12688
// clean up any COM Data
12789
if (m_pccwTemplate)
@@ -173,29 +135,68 @@ void EEClass::Destruct(MethodTable * pOwningMT)
173135
if (GetSparseCOMInteropVTableMap() != NULL)
174136
delete GetSparseCOMInteropVTableMap();
175137
#endif // FEATURE_COMINTEROP
138+
}
139+
140+
//*******************************************************************************
141+
void EEClass::NotifyUnload(MethodTable* pMT, bool unloadStarted)
142+
{
143+
CONTRACTL
144+
{
145+
NOTHROW;
146+
GC_TRIGGERS;
147+
MODE_ANY;
148+
FORBID_FAULT;
149+
PRECONDITION(pMT != NULL);
150+
}
151+
CONTRACTL_END
176152

177153
#ifdef PROFILING_SUPPORTED
178154
// If profiling, then notify the class is getting unloaded.
179155
{
180156
BEGIN_PROFILER_CALLBACK(CORProfilerTrackClasses());
181157
{
182-
// See comments in the call to ClassUnloadStarted for details on this
183-
// FAULT_NOT_FATAL marker and exception swallowing.
158+
if (pMT->ContainsGenericVariables() || pMT->IsArray())
159+
{
160+
// Don't notify the profiler about types with unbound variables or arrays.
161+
// See ClassLoadStarted callback for more details.
162+
return;
163+
}
164+
165+
// Calls to the profiler callback may throw, or otherwise fail, if
166+
// the profiler AVs/throws an unhandled exception/etc. We don't want
167+
// those failures to affect the runtime, so we'll ignore them.
168+
//
169+
// Note that the profiler callback may turn around and make calls into
170+
// the profiling runtime that may throw. This try/catch block doesn't
171+
// protect the profiler against such failures. To protect the profiler
172+
// against that, we will need try/catch blocks around all calls into the
173+
// profiling API.
174+
//
175+
// (Bug #26467)
176+
//
177+
184178
FAULT_NOT_FATAL();
179+
185180
EX_TRY
186181
{
187182
GCX_PREEMP();
188-
(&g_profControlBlock)->ClassUnloadFinished((ClassID) pOwningMT, S_OK);
183+
184+
if (unloadStarted)
185+
(&g_profControlBlock)->ClassUnloadStarted((ClassID) pMT);
186+
else
187+
(&g_profControlBlock)->ClassUnloadFinished((ClassID) pMT, S_OK);
189188
}
190189
EX_CATCH
191190
{
191+
// The exception here came from the profiler itself. We'll just
192+
// swallow the exception, since we don't want the profiler to bring
193+
// down the runtime.
192194
}
193195
EX_END_CATCH(RethrowTerminalExceptions);
194196
}
195197
END_PROFILER_CALLBACK();
196198
}
197199
#endif // PROFILING_SUPPORTED
198-
199200
}
200201

201202
//*******************************************************************************

src/coreclr/vm/class.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,9 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW!
775775
void *operator new(size_t size, LoaderHeap* pHeap, AllocMemTracker *pamTracker);
776776
void Destruct(MethodTable * pMT);
777777

778+
// Notify profiler about class unload
779+
static void NotifyUnload(MethodTable* pMT, bool unloadStarted);
780+
778781
static EEClass * CreateMinimalClass(LoaderHeap *pHeap, AllocMemTracker *pamTracker);
779782
#endif // !DACCESS_COMPILE
780783

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
eventpipeprofiler/eventpipereadingprofiler.cpp
910
eventpipeprofiler/eventpipewritingprofiler.cpp

src/tests/profiler/native/classfactory.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "inlining/inlining.h"
2121
#include "moduleload/moduleload.h"
2222
#include "assemblyprofiler/assemblyprofiler.h"
23+
#include "classload/classload.h"
2324

2425
ClassFactory::ClassFactory(REFCLSID clsid) : refCount(0), clsid(clsid)
2526
{
@@ -139,6 +140,10 @@ HRESULT STDMETHODCALLTYPE ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFI
139140
{
140141
profiler = new AssemblyProfiler();
141142
}
143+
else if (clsid == ClassLoad::GetClsid())
144+
{
145+
profiler = new ClassLoad();
146+
}
142147
else
143148
{
144149
printf("No profiler found in ClassFactory::CreateInstance. Did you add your profiler to the list?\n");
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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::InitializeCommon(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::Initialize(IUnknown* pICorProfilerInfoUnk)
30+
{
31+
return InitializeCommon(pICorProfilerInfoUnk);
32+
}
33+
34+
HRESULT ClassLoad::InitializeForAttach(IUnknown* pICorProfilerInfoUnk, void* pvClientData, UINT cbClientData)
35+
{
36+
return InitializeCommon(pICorProfilerInfoUnk);
37+
}
38+
39+
HRESULT ClassLoad::LoadAsNotificationOnly(BOOL *pbNotificationOnly)
40+
{
41+
*pbNotificationOnly = TRUE;
42+
return S_OK;
43+
}
44+
45+
HRESULT ClassLoad::ClassLoadStarted(ClassID classId)
46+
{
47+
_classLoadStartedCount++;
48+
return S_OK;
49+
}
50+
51+
HRESULT ClassLoad::ClassLoadFinished(ClassID classId, HRESULT hrStatus)
52+
{
53+
_classLoadFinishedCount++;
54+
return S_OK;
55+
}
56+
57+
HRESULT ClassLoad::ClassUnloadStarted(ClassID classId)
58+
{
59+
_classUnloadStartedCount++;
60+
wprintf(L"ClassUnloadStarted: %s\n", GetClassIDName(classId).ToCStr());
61+
62+
return S_OK;
63+
}
64+
65+
HRESULT ClassLoad::ClassUnloadFinished(ClassID classID, HRESULT hrStatus)
66+
{
67+
_classUnloadFinishedCount++;
68+
return S_OK;
69+
}
70+
71+
72+
HRESULT ClassLoad::Shutdown()
73+
{
74+
Profiler::Shutdown();
75+
76+
if(_failures == 0
77+
&& (_classLoadStartedCount != 0)
78+
// Expect unloading of UnloadLibrary.TestClass and
79+
// List<UnloadLibrary.TestClass> with all its base classes with everything used in List constructor.
80+
&& (_classUnloadStartedCount == 7)
81+
&& (_classLoadStartedCount == _classLoadFinishedCount)
82+
&& (_classUnloadStartedCount == _classUnloadFinishedCount))
83+
{
84+
printf("PROFILER TEST PASSES\n");
85+
}
86+
else
87+
{
88+
printf("PROFILER TEST FAILED, failures=%d classLoadStartedCount=%d classLoadFinishedCount=%d classUnloadStartedCount=%d classUnloadFinishedCount=%d\n",
89+
_failures.load(),
90+
_classLoadStartedCount.load(),
91+
_classLoadFinishedCount.load(),
92+
_classUnloadStartedCount.load(),
93+
_classUnloadFinishedCount.load());
94+
}
95+
96+
fflush(stdout);
97+
98+
return S_OK;
99+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 InitializeForAttach(IUnknown* pICorProfilerInfoUnk, void* pvClientData, UINT cbClientData);
25+
virtual HRESULT STDMETHODCALLTYPE Shutdown();
26+
virtual HRESULT STDMETHODCALLTYPE LoadAsNotificationOnly(BOOL *pbNotificationOnly);
27+
28+
virtual HRESULT STDMETHODCALLTYPE ClassLoadStarted(ClassID classId);
29+
virtual HRESULT STDMETHODCALLTYPE ClassLoadFinished(ClassID classId, HRESULT hrStatus);
30+
virtual HRESULT STDMETHODCALLTYPE ClassUnloadStarted(ClassID classId);
31+
virtual HRESULT STDMETHODCALLTYPE ClassUnloadFinished(ClassID classId, HRESULT hrStatus);
32+
33+
private:
34+
std::atomic<int> _classLoadStartedCount;
35+
std::atomic<int> _classLoadFinishedCount;
36+
std::atomic<int> _classUnloadStartedCount;
37+
std::atomic<int> _classUnloadFinishedCount;
38+
std::atomic<int> _failures;
39+
40+
HRESULT InitializeCommon(IUnknown* pCorProfilerInfoUnk);
41+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
using System;
5+
using System.IO;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
8+
using System.Runtime.Loader;
9+
using System.Threading;
10+
using System.Reflection;
11+
using System.Reflection.Emit;
12+
13+
namespace Profiler.Tests
14+
{
15+
class ClassLoadTest
16+
{
17+
private static readonly Guid ClassLoadGuid = new Guid("A1B2C3D4-E5F6-7890-1234-56789ABCDEF0");
18+
19+
static int Main(string[] args)
20+
{
21+
if (args.Length == 1 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase))
22+
{
23+
return RunTest();
24+
}
25+
26+
return ProfilerTestRunner.Run(profileePath: System.Reflection.Assembly.GetExecutingAssembly().Location,
27+
testName: "UnitTestClassLoad",
28+
profilerClsid: ClassLoadGuid);
29+
}
30+
31+
static int RunTest()
32+
{
33+
LoadCollectibleAssembly();
34+
GC.Collect();
35+
GC.WaitForPendingFinalizers();
36+
GC.Collect();
37+
38+
return 100;
39+
}
40+
41+
private static void LoadCollectibleAssembly()
42+
{
43+
var collectibleContext = new AssemblyLoadContext("Collectible", true);
44+
45+
var asmDir = Path.GetDirectoryName(typeof(ClassLoadTest).Assembly.Location);
46+
var dynamicLibrary = collectibleContext.LoadFromAssemblyPath(Path.Combine(asmDir, "unloadlibrary.dll"));
47+
var testType = dynamicLibrary.GetType("UnloadLibrary.TestClass");
48+
49+
object instance = Activator.CreateInstance(testType);
50+
51+
Console.WriteLine(instance.GetHashCode());
52+
GC.Collect();
53+
collectibleContext.Unload();
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)