How can .Net code detect when SQL server has decided to unload an AppDomain?
SQL server sometimes decides to unload an AppDomain for various reasons, changing its state as listed in sys.dm_clr_appdomains
to E_APPDOMAIN_DOOMED
:
The AppDomain is scheduled to be unloaded, but there are currently threads executing in it.
Is there an event that is raised in the CLR code when "The AppDomain is scheduled to be unloaded" or a way to detect this status in CLR code?
I could poll the status periodically from sys.dm_clr_appdomains
if I knew which AppDomain the code was executing as, but this isn't exposed in the SqlContext object. I'm not sure if sys.dm_clr_appdomains.assembly_name
is the same as AppDomain.FriendlyName
.
I have stored procedures that run forever if possible (they push data to the client when data becomes available). Since these threads are still running when SQL server decides to unload the AppDomain, the AppDomain is never unloaded, the threads never stop executing, and connections from the clients never close.
Edit:
The appdomain_id
and appdomain_name
in sys.dm_clr_appdomains
match System.AppDomain.Id
and System.AppDomain.FriendlyName
, which I checked with the following CLR sproc:
public static class Status
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void GetAppDomain()
{
var record = new SqlDataRecord(
new SqlMetaData("ID", SqlDbType.Int),
new SqlMetaData("FriendlyName", SqlDbType.VarChar, 386));
SqlContext.Pipe.SendResultsStart(record);
record.SetInt32(0, AppDomain.CurrentDomain.Id);
record.SetString(1, AppDomain.CurrentDomain.FriendlyName);
SqlContext.Pipe.SendResultsRow(record);
SqlContext.Pipe.SendResultsEnd();
}
}
So polling the status after a timeout waiting for the next event is definitely a possibility, but it leaves an undesirable amount of latency related the maximum polling frequency of the assembly status.
Edit 2:
The AppDomain.DomainUnload
event is not raised when altering the assembly an AppDomain was created for. I don't know if it'd be raised when Sql Server decides to unload the AppDomain.
private static readonly WaitHandle unloadEvent = MakeUnloadEvent();
private static WaitHandle MakeUnloadEvent() {
var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
AppDomain.CurrentDomain.DomainUnload += (sender, args) => {
waitHandle.Set();
};
return waitHandle;
}
[Microsoft.SqlServer.Server.SqlProcedure]
public static void OnUnload()
{
unloadEvent.WaitOne();
var record = new SqlDataRecord(
new SqlMetaData("Done", SqlDbType.Bit));
SqlContext.Pipe.SendResultsStart(record);
record.SetBoolean(0, true);
SqlContext.Pipe.SendResultsRow(record);
SqlContext.Pipe.SendResultsEnd();
}
Running the OnUnload
sproc and then altering the assembly results in either the ALTER ASSEMBLY
command hanging or the AppDomain becomes doomed without the event being raised.
I would assume that sys.dm_clr_appdomains.appdomain_id
maps directly to System.AppDomain.ID
. Have you tried using this?
Another possibility would be the AppDomain.DomainUnloaded
event. I do not have the ability to test if this is fired when SQL Server unloads a domain.