2015-10-07 11 views
6

を考える:IISでホストされてThreadLocksと静的コンストラクタ

A ASP.netのWeb APIアプリケーション。 アプリケーションは、外部作業を行うプラグイン用に約30のアプリドメインを生成します。

のユーザーはのロットを提供していますが、ほとんどの時間は非常にうまく動作しますが、何日か(数日後または数週間後)に突然ハングします。

問題:

一つのwebアプリケーションは、w3wp.exeのを再起動する必要が生じ、時には「ハング」を持っています。

この状態でいくつかの検査を行った結果、この時点で多くのスレッド(時には約15.000)があることがわかりました。

通常のケースでは、スレッド数が100を超えることはありません。 、最後に同じコールを持っています。

ザ・DebugDiagは

enter image description here

は、今、私たちは、スレッド44(約90%と多くの他の多く)にすることを見てきた他の人をブロックつのスレッドがあることを述べています:それはその静的コンストラクタに関する1つの珍しいものがありbehavior.but

enter image description here

方法自体は、任意のロックまたはスレッドを持っていません。 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を誘発することはありません) enter image description here

質問 スタティックctorでこの種のコードに問題があることを示す証拠はありますか?タスクやスレッドコードがない場合でも? (オブジェクトが処分コードを持っていない場合でも、使い捨てであると?)GCの進行自体に関連おそらく何か

TimeZoneHelper

私はctorのをinlcudingこのクラスの主なメソッドを含む要旨を作成し、 TimeZoneHelper.ToTimeZoneOffsetと呼ばれていた方法:

https://gist.github.com/Gentlehag/9d564555261da0e73366

主なものでメソッドの結果は、(ctorの中で作成された)Dictionary.TryGetある


編集 Btwまた、アセンブリ解決イベントがバインドされている各appdomainに追加したいと思います。 コードはここで見ることができます:

https://gist.github.com/Gentlehag/4726b6d888adb149684d


重要な更新 私は同僚だし、ちょうどいくつかのより多くの情報を追加したいです。また、非常に似ている別のシナリオも見つかりました。それはTimeZoneHelperクラスに関するものではありませんが、一般的な側面があることは興味深いです

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] 

:私はブロックを所有するスレッドからのスタックトレースを持っている両方のクラスは、その静的コンストラクタでリソースをロードする(どちらかNewRelicの場合は設定ファイル、TimeZonesの場合はFile)。

  1. 複数のスレッドが最初のスレッドが静的コンストラクタのためにロックを取得し、このコンストラクタ
  2. リソースがロードされ、.NETを実行し、クラス
  3. を使用しよう:だからシナリオでは、次のしているようですランタイムはリソースアセンブリをロードしようとします。
  4. AssemblyResolveイベントを捕捉してリソースアセンブリをロードし、何らかの方法でデッドロックが発生する場合は、どのように問題がありますか?
+0

このメソッドにはロックやスレッドがありませんが、それでも投稿してください。それは、カバーの下でロックを使用するフレームワークメソッドを呼び出すかもしれません。一般的に、スタックトレースによって特定のメソッドがブロックしていることがわかった場合、そのメソッドが実際にブロックしていると仮定するのは安全です*。 (静的コンストラクタは、独自の名前( '.cctor')を持つ別個のメソッドです)。また、スタックトレース全体が最後のフレームよりも役立ちます。 –

+0

私は私の悪いコード –

+1

と要旨をリンクさ - 私は、コールがメソッド本体からのものではないという事実を逃したが、関与しているCLR内部ロックを示唆し 'PrestubMethodFrame'、。さらに関連情報を追加することができます:アンマネージコールスタック( 'kb'で返される)、特にロックを所有するスレッド(上記の例では44)です。コールスタック*がロックを所有しているスレッドの1つである場合(ここでは「44」がスレッドIDであるかどうかわかりません)、実際にそのスレッドが何を待っているのか把握する必要があります'ZwWaitForSingleObject')。スタックトレースはそこに役立つはずです。 –

答えて

1

これは私の推測です。

更新: AssemblyResolveイベントの再帰問題だと思います。 コメントに基づいて、スタックオーバーフローは発生しませんでしたが、依然として再帰の問題がある可能性があります。

このバグは、リソースにアクセスする順序によって異なります。おそらく、これは最初に言及した静的クラスの1つにアクセスしたときに発生します。

初めてリソースにアクセスすると、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 

Loggerがリソースにアクセスしている、これはで示されます。ここでの私の結論は、ロガーができたということです

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); 

をロガーへの呼び出しがあります:

catch 
{ 
    context.RunnerLog.Error(string.Format(CultureInfo.InvariantCulture, "Failed to load assembly {0}.", args.Name)); 

    result = null; 
} 

ロガーが結合していたに、AssemblyResolveイベントの前に初期化された、または別の条件があった場合には、違いがあるかもしれませんロガーが失敗したAssemblyResolveイベントを発生させることはありませんでした。

静的クラスへの呼び出しを開始し、AssemblyResolveで例外が発生し、それをキャッチしてログ記録すると仮定すると、loggerの呼び出しによってリソースへのアクセスが発生し、別のアセンブリ解決が発生し、この再帰はスタックオーバーフローを引き起こします。

最初の要求では、その操作がStackOverflowExceptionの前に長時間保持されていた場合、他の要求はブロックされますが、TypeInitializationExceptionで失敗するため問題ありません。後者は、ドメインがStackOverflowExceptionの後にアンロードを開始するため、決して起こりません。

それが上にいくつかの辞書検索方法を見せているという事実も重要ではありません - それはおそらくスタックオーバーフローに貢献して最後の一滴です。

一つのこと、私はに、AssemblyResolveイベントハンドラ内でロガーの別の種類を使用することをお勧めします。

もう一つは、私は、このようなリソースへのアクセスまたは手動アセンブリの読み込みなどの静的コンストラクタ、のいずれかのブロックIO要求を回避しようということです。内部の基本的なものを初期化するだけで、公開メソッド自体の遅延初期化のための別の並行処理メカニズムを使用します。

しかし、私は疑いのstackoverflowの原因は、静的コンストラクタとしなければならないと思います。再帰があまりにもゆっくりstackoverflowのが発生するために行った場合にも

は、何の疑いのスタックオーバーフローであることができませんでした。このようにして、ドメインはスレッドの量や一般的なメモリ消費量など、IISのリソース消費量を監視するなどの理由で、アンロードを開始する可能性があります。これは、リクエストが長時間ブロックされた場合に発生する可能性があります。

+0

アセンブリ解決メソッドを削除することができました。あなたが正しい場合、このエラーはもう表示されないはずです。 –

+0

問題の再現から始めます。 –

+0

私の提案されたデモのように小さなアプリケーションを作成し、あなたのロガーでリソースアクセスを置き換えます(あなたのprodアプリケーションに似た設定で)。 RunnerLog.Error();でリソースアクセス(Console.WriteLine(Resource1.String1);)を置き換えます。スタックオーバーフローを経験する必要があります。それからあなたは確信します。そうでない場合は、原因が疑わしいため、ロガーのセットアップでAssemblyResolveイベントが発生することを確認してください。 –

関連する問題