Skip to content

Commit

Permalink
Don't run finalizers on shutdown, provide Unloading event
Browse files Browse the repository at this point in the history
API review: dotnet/corefx#5205

This change implements the proposal in the API review above:
- Don't block threads for shutdown
- Don't run finalizers on shutdown (for both reachable and unreachable objects)
- Provide a public AssemblyLoadContext.Unloading event that can be handled to clean up global resources in an assembly
  - As unloading an AssemblyLoadContext is not yet implemented, the event will for the time being be raised shortly prior to shutdown
  • Loading branch information
kouvel committed Jan 27, 2016
1 parent 82e5fba commit 81ab7eb
Show file tree
Hide file tree
Showing 8 changed files with 3,693 additions and 37 deletions.
8 changes: 8 additions & 0 deletions src/inc/clrconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ CONFIG_DWORD_INFO(INTERNAL_AppDomainNoUnload, W("AppDomainNoUnload"), 0, "Not us
RETAIL_CONFIG_STRING_INFO_EX(INTERNAL_TargetFrameworkMoniker, W("TargetFrameworkMoniker"), "Allows the test team to specify what TargetFrameworkMoniker to use.", CLRConfig::IgnoreHKLM | CLRConfig::IgnoreHKCU | CLRConfig::IgnoreConfigFiles | CLRConfig::IgnoreWindowsQuirkDB)
RETAIL_CONFIG_STRING_INFO_EX(INTERNAL_AppContextSwitchOverrides, W("AppContextSwitchOverrides"), "Allows default switch values defined in AppContext to be overwritten by values in the Config", CLRConfig::IgnoreEnv | CLRConfig::IgnoreHKLM | CLRConfig::IgnoreHKCU | CLRConfig::IgnoreWindowsQuirkDB | CLRConfig::ConfigFile_ApplicationFirst)

#ifdef FEATURE_CORECLR
// For the proposal and discussion on why finalizers are not run on shutdown by default anymore in CoreCLR, see the API review:
// https://github.com/dotnet/corefx/issues/5205
#define DEFAULT_FinalizeOnShutdown (0)
#else
#define DEFAULT_FinalizeOnShutdown (1)
#endif
CONFIG_DWORD_INFO(INTERNAL_FinalizeOnShutdown, W("FinalizeOnShutdown"), DEFAULT_FinalizeOnShutdown, "When enabled, on shutdown, blocks all user threads and calls finalizers for all finalizable objects, including live objects")

//
// ARM
Expand Down
39 changes: 20 additions & 19 deletions src/mscorlib/model.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1005,25 +1005,26 @@
<Member Name="get_IsCompatibilityBehaviorDefined" />
</Type>
<Type Name="System.Runtime.Loader.AssemblyLoadContext" condition="FEATURE_HOST_ASSEMBLY_RESOLVER">
<Member Name="#ctor" />
<Member MemberType="Property" Name="Default" />
<Member Name="GetAssemblyName(System.String)" />
<Member Name="GetLoadContext(System.Reflection.Assembly)" />
<Member Name="InitializeDefaultContext(System.Runtime.Loader.AssemblyLoadContext)" />
<Member Name="LoadFromAssemblyName(System.Reflection.AssemblyName)" />
<Member Name="Load(System.Reflection.AssemblyName)" />
<Member Name="LoadFromAssemblyPath(System.String)" />
<Member Name="LoadFromNativeImagePath(System.String,System.String)" />
<Member Name="LoadFromStream(System.IO.Stream)" />
<Member Name="LoadFromStream(System.IO.Stream,System.IO.Stream)" />
<Member Name="Resolve(System.IntPtr,System.Reflection.AssemblyName)" />
<Member Name="ResolveUnmanagedDll(System.String,System.IntPtr)" />
<Member Name="LoadUnmanagedDll(System.String)" />
<Member Name="LoadUnmanagedDllFromPath(System.String)" />
<Member Name="get_Default" />
<Member Name="SetProfileOptimizationRoot(System.String)" />
<Member Name="StartProfileOptimization(System.String)" />
<Member MemberType="Event" Name="Resolving" />
<Member Name="#ctor" />
<Member MemberType="Property" Name="Default" />
<Member Name="GetAssemblyName(System.String)" />
<Member Name="GetLoadContext(System.Reflection.Assembly)" />
<Member Name="InitializeDefaultContext(System.Runtime.Loader.AssemblyLoadContext)" />
<Member Name="LoadFromAssemblyName(System.Reflection.AssemblyName)" />
<Member Name="Load(System.Reflection.AssemblyName)" />
<Member Name="LoadFromAssemblyPath(System.String)" />
<Member Name="LoadFromNativeImagePath(System.String,System.String)" />
<Member Name="LoadFromStream(System.IO.Stream)" />
<Member Name="LoadFromStream(System.IO.Stream,System.IO.Stream)" />
<Member Name="Resolve(System.IntPtr,System.Reflection.AssemblyName)" />
<Member Name="ResolveUnmanagedDll(System.String,System.IntPtr)" />
<Member Name="LoadUnmanagedDll(System.String)" />
<Member Name="LoadUnmanagedDllFromPath(System.String)" />
<Member Name="get_Default" />
<Member Name="SetProfileOptimizationRoot(System.String)" />
<Member Name="StartProfileOptimization(System.String)" />
<Member MemberType="Event" Name="Resolving" />
<Member MemberType="Event" Name="Unloading" />
</Type>
<Type Name="System.Reflection.Metadata.AssemblyExtensions">
<Member Name="TryGetRawMetadata(System.Reflection.Assembly,System.Byte*@,System.Int32@)"/>
Expand Down
20 changes: 18 additions & 2 deletions src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,14 @@ void InitializeLoadContext(bool fRepresentsTPALoadContext)
IntPtr ptrALC = GCHandle.ToIntPtr(gchALC);
m_pNativeAssemblyLoadContext = InitializeAssemblyLoadContext(ptrALC, fRepresentsTPALoadContext);

// Initialize the resolve event handler to be null by default
// Initialize event handlers to be null by default
Resolving = null;
Unloading = null;

// Since unloading an AssemblyLoadContext is not yet implemented, this is a temporary solution to raise the
// Unloading event on process exit. Register for the current AppDomain's ProcessExit event, and the handler will in
// turn raise the Unloading event.
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
}

[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
Expand Down Expand Up @@ -386,8 +392,18 @@ public void StartProfileOptimization(string profile)
InternalStartProfile(profile, m_pNativeAssemblyLoadContext);
#endif // FEATURE_MULTICOREJI
}


private void OnProcessExit(object sender, EventArgs e)
{
var unloading = Unloading;
if (unloading != null)
{
unloading(this);
}
}

public event Func<AssemblyLoadContext, AssemblyName, Assembly> Resolving;
public event Action<AssemblyLoadContext> Unloading;

// Contains the reference to VM's representation of the AssemblyLoadContext
private IntPtr m_pNativeAssemblyLoadContext;
Expand Down
50 changes: 34 additions & 16 deletions src/vm/finalizerthread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -860,16 +860,23 @@ DWORD __stdcall FinalizerThread::FinalizerThreadStart(void *args)
hEventShutDownToFinalizer->Wait(INFINITE,FALSE);
GetFinalizerThread()->DisablePreemptiveGC();

GCHeap::GetGCHeap()->SetFinalizeQueueForShutdown (FALSE);

// Finalize all registered objects during shutdown, even they are still reachable.
// we have been asked to quit, so must be shutting down
// We have been asked to quit, so must be shutting down
_ASSERTE(g_fEEShutDown);
_ASSERTE(GetFinalizerThread()->PreemptiveGCDisabled());

// This will apply any policy for swallowing exceptions during normal
// processing, without allowing the finalizer thread to disappear on us.
ManagedThreadBase::FinalizerBase(FinalizeObjectsOnShutdown);
bool finalizeOnShutdown = DEFAULT_FinalizeOnShutdown != 0;
#ifdef _DEBUG
finalizeOnShutdown = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_FinalizeOnShutdown) != 0;
#endif
if (finalizeOnShutdown)
{
// Finalize all registered objects during shutdown, even they are still reachable.
GCHeap::GetGCHeap()->SetFinalizeQueueForShutdown(FALSE);

// This will apply any policy for swallowing exceptions during normal
// processing, without allowing the finalizer thread to disappear on us.
ManagedThreadBase::FinalizerBase(FinalizeObjectsOnShutdown);
}

_ASSERTE(GetFinalizerThread()->GetDomain()->IsDefaultDomain());

Expand Down Expand Up @@ -1206,17 +1213,28 @@ BOOL FinalizerThread::FinalizerThreadWatchDog()
pGenGCHeap->background_gc_wait();
#endif //BACKGROUND_GC

_ASSERTE ((g_fEEShutDown & ShutDown_Finalize1) || g_fFastExitProcess);
ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_SHUTDOWN);
_ASSERTE((g_fEEShutDown & ShutDown_Finalize1) || g_fFastExitProcess);

g_fSuspendOnShutdown = TRUE;

// Do not balance the trap returning threads.
// We are shutting down CLR. Only Finalizer/Shutdown threads can
// return from DisablePreemptiveGC.
ThreadStore::TrapReturningThreads(TRUE);
bool finalizeOnShutdown = DEFAULT_FinalizeOnShutdown != 0;
#ifdef _DEBUG
finalizeOnShutdown = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_FinalizeOnShutdown) != 0;
#endif
if (finalizeOnShutdown)
{
// When running finalizers on shutdown (including for reachable objects), suspend threads for shutdown before
// running finalizers, so that the reachable objects will not be used after they are finalized.

ThreadSuspend::RestartEE(FALSE, TRUE);
ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_SHUTDOWN);

g_fSuspendOnShutdown = TRUE;

// Do not balance the trap returning threads.
// We are shutting down CLR. Only Finalizer/Shutdown threads can
// return from DisablePreemptiveGC.
ThreadStore::TrapReturningThreads(TRUE);

ThreadSuspend::RestartEE(FALSE, TRUE);
}

if (g_fFastExitProcess)
{
Expand Down
75 changes: 75 additions & 0 deletions tests/src/GC/Scenarios/FinalizeTimeout/FinalizeTimeout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Threading;

public class FinalizeTimeout
{
public static int Main(string[] args)
{
Console.WriteLine("Main start");

// Run the finalizer at least once to have its code be jitted
BlockingFinalizerOnShutdown finalizableObject;
do
{
finalizableObject = new BlockingFinalizerOnShutdown();
} while (!BlockingFinalizerOnShutdown.finalizerCompletedOnce);

// Start a bunch of threads that allocate continuously, to increase the chance that when Main returns, one of the
// threads will be blocked for shutdown while holding one of the GC locks
for (int i = 0; i < Environment.ProcessorCount; ++i)
{
var t = new Thread(ThreadMain);
t.IsBackground = true;
t.Start();
}

// Wait a second to give the threads a chance to actually start running
Thread.Sleep(1000);

Console.WriteLine("Main end");

// Create another finalizable object, and immediately return from Main to have finalization occur during shutdown
finalizableObject = new BlockingFinalizerOnShutdown() { isLastObject = true };
return 100;
}

private static void ThreadMain()
{
byte[] b;
while (true)
b = new byte[1024];
}

private class BlockingFinalizerOnShutdown
{
public static bool finalizerCompletedOnce = false;
public bool isLastObject = false;

~BlockingFinalizerOnShutdown()
{
if (finalizerCompletedOnce && !isLastObject)
return;

Console.WriteLine("Finalizer start");

// Allocate in the finalizer for long enough to try allocation after one of the background threads blocks for
// shutdown while holding one of the GC locks, to deadlock the finalizer. The main thread should eventually time
// out waiting for the finalizer thread to complete, and the process should exit cleanly.
TimeSpan timeout = isLastObject ? TimeSpan.FromMilliseconds(500) : TimeSpan.Zero;
TimeSpan elapsed = TimeSpan.Zero;
var start = DateTime.Now;
int i = -1;
object o;
do
{
o = new object();
} while ((++i & 0xff) != 0 || (elapsed = DateTime.Now - start) < timeout);

Console.WriteLine("Finalizer end");
finalizerCompletedOnce = true;
}
}
}
25 changes: 25 additions & 0 deletions tests/src/GC/Scenarios/FinalizeTimeout/FinalizeTimeout.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<AssemblyName>FinalizeTimeout</AssemblyName>
<ProjectGuid>{3B0E8096-79D4-413F-9010-F68DF90073B5}</ProjectGuid>
<OutputType>Exe</OutputType>
<FileAlignment>512</FileAlignment>
<CLRTestPriority>2</CLRTestPriority>
</PropertyGroup>
<!-- Default configurations to help VS understand the configurations -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
</PropertyGroup>
<ItemGroup>
<Compile Include="FinalizeTimeout.cs" />
</ItemGroup>
<ItemGroup>
<None Include="project.json" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
33 changes: 33 additions & 0 deletions tests/src/GC/Scenarios/FinalizeTimeout/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"dependencies": {
"System.Diagnostics.Process": "4.0.0-beta-23302",
"System.IO": "4.0.10-beta-23302",
"System.IO.FileSystem": "4.0.0-beta-23302",
"System.IO.FileSystem.Primitives": "4.0.0-beta-23302",
"System.Runtime": "4.0.20-beta-23302",
"System.Runtime.Extensions": "4.0.10-beta-23302",
"System.Runtime.Handles": "4.0.0-beta-23302",
"System.Runtime.Loader": "4.0.0-beta-23302",
"System.Threading": "4.0.10-beta-23302",
"System.Globalization.Calendars": "4.0.0-beta-23302",
"System.Globalization": "4.0.10-beta-23302",
"System.Text.Encoding": "4.0.10-beta-23302",
"System.Runtime.InteropServices": "4.0.20-beta-23302",
"System.Collections": "4.0.10-beta-23302",
"System.Console": "4.0.0-beta-23302",
"System.Reflection": "4.0.10-beta-23302",
"System.Reflection.Primitives": "4.0.0-beta-23302",
"System.ComponentModel": "4.0.1-beta-23302",
"System.Xml.ReaderWriter": "4.0.11-beta-23302",
"System.Collections.NonGeneric": "4.0.1-beta-23302",
"System.Collections.Specialized": "4.0.1-beta-23302",
"System.Linq": "4.0.1-beta-23302",
"System.Linq.Queryable": "4.0.1-beta-23302",
"System.Xml.XmlSerializer": "4.0.11-beta-23302",
"System.Xml.XmlDocument": "4.0.1-beta-23302",
"System.Xml.XDocument": "4.0.11-beta-23302"
},
"frameworks": {
"dnxcore50": {}
}
}
Loading

0 comments on commit 81ab7eb

Please sign in to comment.