Skip to content

Commit

Permalink
[.NET Core] Support Assembly Context Unloading (#913)
Browse files Browse the repository at this point in the history
  • Loading branch information
Wraith2 authored Feb 24, 2021
1 parent 21804d9 commit 9bbaf02
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -474,5 +474,10 @@ protected virtual DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal i
abstract internal bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from);

abstract internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to);

virtual internal void Unload()
{
_pruningTimer.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@
<Compile Include="Microsoft\Data\SqlClient\SqlDelegatedTransaction.NetCoreApp.cs" />
<Compile Include="Microsoft\Data\SqlClient\TdsParser.NetCoreApp.cs" />
<Compile Include="Microsoft\Data\SqlClient\SNI\SslOverTdsStream.NetCoreApp.cs" />
<Compile Include="Microsoft\Data\SqlClient\SqlConnectionFactory.AssemblyLoadContext.cs" />
<Compile Include="Microsoft\Data\SqlClient\SqlDependencyUtils.AssemblyLoadContext.cs" />
</ItemGroup>
<ItemGroup Condition="'$(OSGroup)' != 'AnyOS' AND '$(TargetGroup)' == 'netcoreapp' AND '$(BuildSimulator)' == 'true'">
<Compile Include="Microsoft\Data\SqlClient\SimulatorEnclaveProvider.NetCoreApp.cs" />
Expand Down Expand Up @@ -778,7 +780,7 @@
<PackageReference Include="System.Buffers" Version="$(SystemBuffersVersion)" />
<PackageReference Include="System.Runtime.Caching" Version="$(SystemRuntimeCachingVersion)" />
<PackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngVersion)" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="$(MicrosoftSourceLinkGitHubVersion)" PrivateAssets="All"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="$(MicrosoftSourceLinkGitHubVersion)" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Strings.resx">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Reflection;
using System.Runtime.Loader;

namespace Microsoft.Data.SqlClient
{
sealed internal partial class SqlConnectionFactory
{
partial void SubscribeToAssemblyLoadContextUnload()
{
AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()).Unloading += SqlConnectionFactoryAssemblyLoadContext_Unloading;
}

private void SqlConnectionFactoryAssemblyLoadContext_Unloading(AssemblyLoadContext obj)
{
Unload(obj, EventArgs.Empty);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@

namespace Microsoft.Data.SqlClient
{
sealed internal class SqlConnectionFactory : DbConnectionFactory
sealed internal partial class SqlConnectionFactory : DbConnectionFactory
{

private const string _metaDataXml = "MetaDataXml";

private SqlConnectionFactory() : base() { }
private SqlConnectionFactory() : base()
{
SubscribeToAssemblyLoadContextUnload();
}

public static readonly SqlConnectionFactory SingletonInstance = new SqlConnectionFactory();

Expand Down Expand Up @@ -306,6 +309,20 @@ protected override DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal
internalConnection.ServerVersion,
internalConnection.ServerVersion);
}

private void Unload(object sender, EventArgs e)
{
try
{
Unload();
}
finally
{
ClearAllPools();
}
}

partial void SubscribeToAssemblyLoadContextUnload();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,10 @@ namespace Microsoft.Data.SqlClient
// for example, some mobile profiles on mono
partial class SqlDependencyPerAppDomainDispatcher
{
private void SubscribeToAppDomainUnload()
partial void SubscribeToAppDomainUnload()
{
// If rude abort - we'll leak. This is acceptable for now.
AppDomain.CurrentDomain.DomainUnload += new EventHandler(UnloadEventHandler);
}

private void UnloadEventHandler(object sender, EventArgs e)
{
long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent("SqlDependencyPerAppDomainDispatcher.UnloadEventHandler | DEP | Object Id {0}", ObjectID);
try
{
// Make non-blocking call to ProcessDispatcher to ThreadPool.QueueUserWorkItem to complete
// stopping of all start calls in this AppDomain. For containers shared among various AppDomains,
// this will just be a ref-count subtract. For non-shared containers, we will close the container
// and clean-up.
var dispatcher = SqlDependency.ProcessDispatcher;
dispatcher?.QueueAppDomainUnloading(SqlDependency.AppDomainKey);
}
finally
{
SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Reflection;
using System.Runtime.Loader;

namespace Microsoft.Data.SqlClient
{
// these members were moved to a separate file in order
// to be able to skip them on platforms where AssemblyLoadContext members are not supported
// for example, netstandard
partial class SqlDependencyPerAppDomainDispatcher
{
partial void SubscribeToAssemblyLoadContextUnload()
{
AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()).Unloading += SqlDependencyPerAppDomainDispatcher_Unloading;
}

private void SqlDependencyPerAppDomainDispatcher_Unloading(AssemblyLoadContext obj)
{
UnloadEventHandler(null, EventArgs.Empty);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,29 @@ private SqlDependencyPerAppDomainDispatcher()
Timeout.Infinite);

SubscribeToAppDomainUnload();
SubscribeToAssemblyLoadContextUnload();
}
finally
{
SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
}
}

partial void SubscribeToAppDomainUnload();

partial void SubscribeToAssemblyLoadContextUnload();

private void UnloadEventHandler(object sender, EventArgs e)
{
long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent("SqlDependencyPerAppDomainDispatcher.UnloadEventHandler | DEP | Object Id {0}", ObjectID);
try
{
// Make non-blocking call to ProcessDispatcher to ThreadPool.QueueUserWorkItem to complete
// stopping of all start calls in this AppDomain. For containers shared among various AppDomains,
// this will just be a ref-count subtract. For non-shared containers, we will close the container
// and clean-up.
var dispatcher = SqlDependency.ProcessDispatcher;
dispatcher?.QueueAppDomainUnloading(SqlDependency.AppDomainKey);
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Reflection;
using System.Runtime.Loader;

namespace Microsoft.Data.SqlClient
{
internal sealed class SqlDiagnosticListener : DiagnosticListener
{
public SqlDiagnosticListener(string name) : base(name)
{
AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()).Unloading += SqlDiagnosticListener_Unloading;
}

private void SqlDiagnosticListener_Unloading(AssemblyLoadContext obj)
{
Dispose();
}
}
}

0 comments on commit 9bbaf02

Please sign in to comment.