假设:
IIS中托管的ASP.net Web api应用程序。 该应用程序为一个插件生成大约30个应用程序域,这些插件可以执行一些外部工作。
该应用程序为很多用户提供服务,并且大部分时间都在运行,但有时(几天甚至几周后)它会突然挂起。
问题:
一个Web应用程序有时会“挂起”,导致需要重新启动w3wp.exe。
在这种状态下对转储进行一些检查后,我们发现在这个时刻有很多线程(有时约为15.000)。
在正常情况下,我们从不会观察超过一百个线程。
DebugDiag说有一个线程阻塞其他线程
现在我们已经在线程44(以及许多其他人;约90%)中看到,最后有相同的电话:
Method本身没有任何锁定或线程行为。但是它的静态构造函数有一个不常见的东西。 ctor看起来像这样:
static TimeZoneHelper()
{
using (StringReader reader = new StringReader(Resources.TimeZones))
{
string line;
while ((line = reader.ReadLine()) != null)
{
string[] parts = line.Split(';');
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById(parts[1]);
timeZones[parts[0]] = timeZone;
}
}
}
此外,调试分析表明应用程序处于活动状态gc(正如您可能会问的那样:我们永远不会手动触发gc.collect)
问题 是否有证据表明此类代码在静态ctor中存在问题?即使没有任务或线程代码?也许与GC进展本身有关的东西(因为对象是一次性的,即使没有处置代码?)
TimeZoneHelper
我创建了一个gist,其中包含了这个类的主要方法,其中包括ctor和名为TimeZoneHelper.ToTimeZoneOffset
的方法:
https://gist.github.com/Gentlehag/9d564555261da0e73366
该方法导致的主要内容是Dictionary.TryGet(在ctor中创建)
编辑 顺便说一下,我还想在每个appdomain中添加一个程序集解析事件。 代码可以在这里看到:
https://gist.github.com/Gentlehag/4726b6d888adb149684d
重要更新 我是同事,只想添加更多信息。我们还发现了另一个非常相似的场景。我有来自拥有该块的线程的堆栈跟踪:
000000c898897560 00007ff8855b7e5d System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].FindEntry(System.__Canon)
000000c8988975d0 00007ff8855b7d34 System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].TryGetValue(System.__Canon, System.__Canon ByRef)
000000c898897610 00007ff88f6152b3 GP.Components.Extensions.AppDomains.RemotingRunner.CurrentDomain_AssemblyResolve(System.Object, System.ResolveEventArgs)
000000c8988978a0 00007ff886f7276c System.AppDomain.OnAssemblyResolveEvent(System.Reflection.RuntimeAssembly, System.String)
000000c898897bd0 00007ff8e4b2a7f3 [GCFrame: 000000c898897bd0]
000000c898899b78 00007ff8e4b2a7f3 [HelperMethodFrame_PROTECTOBJ: 000000c898899b78] System.Reflection.RuntimeAssembly._nLoad(System.Reflection.AssemblyName, System.String, System.Security.Policy.Evidence, System.Reflection.RuntimeAssembly, System.Threading.StackCrawlMark ByRef, IntPtr, Boolean, Boolean, Boolean)
000000c898899c80 00007ff886f7224e System.Reflection.RuntimeAssembly.InternalGetSatelliteAssembly(System.String, System.Globalization.CultureInfo, System.Version, Boolean, System.Threading.StackCrawlMark ByRef)
000000c898899d60 00007ff886f716c8 System.Resources.ManifestBasedResourceGroveler.GetSatelliteAssembly(System.Globalization.CultureInfo, System.Threading.StackCrawlMark ByRef)
000000c898899df0 00007ff885b932fb System.Resources.ManifestBasedResourceGroveler.GrovelForResourceSet(System.Globalization.CultureInfo, System.Collections.Generic.Dictionary`2, Boolean, Boolean, System.Threading.StackCrawlMark ByRef)
000000c898899eb0 00007ff885b92ecb System.Resources.ResourceManager.InternalGetResourceSet(System.Globalization.CultureInfo, Boolean, Boolean, System.Threading.StackCrawlMark ByRef)
000000c898899fa0 00007ff885b92b73 System.Resources.ResourceManager.InternalGetResourceSet(System.Globalization.CultureInfo, Boolean, Boolean)
000000c898899ff0 00007ff885b92014 System.Resources.ResourceManager.GetString(System.String, System.Globalization.CultureInfo)
000000c89889a0a0 00007ff89914aa62 NewRelic.Agent.Core.Config.ConfigurationLoader.InitializeFromXml(System.String, System.String)
000000c89889a140 00007ff89914a838 NewRelic.Agent.Core.Config.ConfigurationLoader.Initialize(System.String)
000000c89889a1a0 00007ff899143be9 NewRelic.Agent.Core.Config.ConfigurationLoader.Initialize()
000000c89889a210 00007ff899123a27 NewRelic.Agent.Core.Agent+AgentSingleton.CreateInstance()
000000c89889a280 00007ff8991239c2 NewRelic.Agent.Core.Singleton`1[[System.__Canon, mscorlib]]..ctor(System.__Canon)
000000c89889a2c0 00007ff89912388b NewRelic.Agent.Core.Agent..cctor()
000000c89889a700 00007ff8e4b2a7f3 [GCFrame: 000000c89889a700]
000000c89889ce88 00007ff8e4b2a7f3 [PrestubMethodFrame: 000000c89889ce88] NewRelic.Agent.Core.Agent.get_Instance()
000000c89889cef0 00007ff89912358c NewRelic.Agent.Core.AgentShim.GetTracer(System.String, UInt32, System.String, System.String, System.Type, System.String, System.String, System.String, System.Object, System.Object[])
000000c89889d280 00007ff8e4b2a7f3 [DebuggerU2MCatchHandlerFrame: 000000c89889d280]
它不是关于TimeZoneHelper类,但有趣的是有一个共同的方面:两个类都在其静态构造函数中加载资源(NewRelic的配置文件或带有TimeZones的文件)。因此情况似乎如下:
答案 0 :(得分:1)
以下是我对发生的事情的猜测。
更新: 我认为这是AssemblyResolve事件的递归问题。 基于注释,没有发生堆栈溢出,但仍然可能存在递归问题,因此答案仍然适用。
有迹象表明此错误取决于访问资源的顺序。很可能这种情况发生在第一件事是访问你提到的静态类之一时。
第一次访问资源时,AssemblyResolve事件会多次触发。后续资源请求不会导致AssemblyResolve事件。这可以通过以下代码来证明:
AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) =>
{
Console.WriteLine("Resolve {0}", eventArgs.Name);
return null;
};
Console.WriteLine(Resource1.String1);
Console.WriteLine(Resource1.String1);
结果:
Resolve ConsoleApplication1.resources, Version=1.0.0.0, Culture=ru-RU, PublicKeyToken=null
Resolve ConsoleApplication1.resources, Version=1.0.0.0, Culture=ru-RU, PublicKeyToken=null
Resolve ConsoleApplication1.resources, Version=1.0.0.0, Culture=ru, PublicKeyToken=null
Resolve ConsoleApplication1.resources, Version=1.0.0.0, Culture=ru, PublicKeyToken=null
Value from resource
Value from resource
记录器正在访问资源,这表示为:
000000c898899ff0 00007ff885b92014 System.Resources.ResourceManager.GetString(System.String, System.Globalization.CultureInfo)
000000c89889a0a0 00007ff89914aa62 NewRelic.Agent.Core.Config.ConfigurationLoader.InitializeFromXml(System.String, System.String)
000000c89889a140 00007ff89914a838 NewRelic.Agent.Core.Config.ConfigurationLoader.Initialize(System.String)
000000c89889a1a0 00007ff899143be9 NewRelic.Agent.Core.Config.ConfigurationLoader.Initialize()
000000c89889a210 00007ff899123a27 NewRelic.Agent.Core.Agent+AgentSingleton.CreateInstance()
000000c89889a280 00007ff8991239c2 NewRelic.Agent.Core.Singleton`1[[System.__Canon, mscorlib]]..ctor(System.__Canon)
000000c89889a2c0 00007ff89912388b NewRelic.Agent.Core.Agent..cctor()
000000c89889a700 00007ff8e4b2a7f3 [GCFrame: 000000c89889a700]
000000c89889ce88 00007ff8e4b2a7f3 [PrestubMethodFrame: 000000c89889ce88] NewRelic.Agent.Core.Agent.get_Instance()
000000c89889cef0 00007ff89912358c NewRelic.Agent.Core.AgentShim.GetTracer(System.String, UInt32, System.String, System.String, System.Type, System.String, System.String, System.String, System.Object, System.Object[])
我的结论是,记录器可以在没有AssemblyResolve的情况下成功运行任何第一次绑定的事件,并且如果第一次以这种方式运行,则不会导致AssemblyResolve事件。
如果您是第一次从AssemblyResolve访问资源,则会发生递归调用,从而导致StackOverflowException。这很容易建模:
AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) =>
{
Console.WriteLine("Resolve {0}", eventArgs.Name);
Console.WriteLine(Resource1.String1);
return null;
};
Console.WriteLine(Resource1.String1);
有一个对Logger的调用:
catch
{
context.RunnerLog.Error(string.Format(CultureInfo.InvariantCulture, "Failed to load assembly {0}.", args.Name));
result = null;
}
如果在绑定AssemblyResolve事件之前初始化了logger,或者存在另一个不会导致记录器触发失败的AssemblyResolve事件的情况,则可能存在差异。
当您开始调用静态类并在AssemblyResolve中有异常,并且您应该捕获并记录它时,对logger的调用会导致对资源的访问,而这会导致另一个程序集解析递归会导致堆栈溢出。
当第一个请求对静态类构造函数有锁定时,如果该操作在StackOverflowException之前持续了很长时间,则其他请求被阻止,但这并不重要,因为它们会因TypeInitializationException而失败。后者永远不会发生,因为无论如何域都会在StackOverflowException之后开始卸载。
它在顶部显示一些字典Find方法的事实也无关紧要 - 它可能是导致堆栈溢出的最后一滴。
我建议在AssemblyResolve事件处理程序中使用另一种记录器。
另一件事是我会尝试避免静态构造函数中的任何阻塞IO请求,例如资源访问或手动程序集加载。只需初始化内部的基本内容,并在公共方法本身中使用另一种并发机制进行延迟初始化。
但是,我不认为可疑stackoverflow的原因与静态构造函数有关。
此外,如果递归过慢导致stackoverflow发生,则可能没有可疑的堆栈溢出。这样,域可以通过其他原因开始卸载 - 例如,通过IIS的某些资源消耗保护,例如线程数量或一般内存消耗。如果请求长时间阻止,可能会发生这种情况。