最初はコードがありました
そして、そのコードは何年も前にドットネット(まだバージョン1.1)で書かれていました。 コードはシンプルでオークでした-プロジェクトの荒野のどこかに、 Interop *。*。DLLのスタックを配置して、さらに古いTLBを作成しました。 明らかに、3つ半のメソッドを実装するインターフェイスが実装され、実装のセットは発掘時に苦inして生まれました-それらの16(!)がありました。 ファクトリーおよびその他のシングルトーンが含まれています。
従来のアプリケーションがそのコードを作成し、興味のある場所の16の実装すべてについて、コードはコピーアンドペーストで同一でした。 相互運用機能の 名前空間のみが異なりました 。
このようなもの:
Type apptype = Type.GetTypeFromProgID("CoolAppID", false); var app = Activator.CreateInstance(apptype) as Cool.Application; var lib = app.Open(file, ... /* many flags */) as Cool.Library; foreach(var asset in lib.Assets) { /* some long operations */ }
それ以来、コードは多くのことを経験しました-サブネット2.0、3.5、4.0などに移行しました。2から16までのこれらの相互運用性をサポートし始めました-しかし、コードは同じであり、変更されません。
そして、邪悪な魔術師はすでにハンスとグレーテルを食べたかった
そして、悪名高いE_RPC_TIMEOUTがこの恐竜を訪れました。 また、そのCOMサーバーの異なるバージョンでは、ポイントは明らかにコードにあります。
でたらめな質問! まあ、それは非常に長い間機能しますよね? そのため、ループの終わりを見るためにイテレーターは生きていません! -私の前に修正した人を考えた。
変更:
... var lib = app.Open(file, ... /* many flags */) as Cool.Library; var list = new List<CoolAsset>(); foreach(var asset in lib.Assets) { list.Add(asset); } foreach(var asset in list) { /* some long operations */ } /* some other stuff */ /* end of function */
うーん 良くない。
まったく叩かないと言ってはいけない-しかし、ボールを叩かない
E_RPC_TIMEOUTが消えたという意味で-それは助けたようです。 しかし、その代わりにさらに邪悪なE_RPC_DISCONNECTが来ました 。 そしてその瞬間、このピアノは私に転がされました。
ツールと材料:
- Windows 8.1-一つのこと
- 奇妙なプロジェクト-1個
-
Adoubi IndizAhどの外部comサーバー-6個、すべてで再生可能 - 秘密のタンバリン-1個
始めましょう。
ゼブラ攻撃
まず第一に、私たちは遠い暗い過去の方法で自分自身を武装します。つまり、ログへの出力を2行ごとに突きます(そう、デバッガーについて知っています)。 このようなもの:
... var lib = app.Open(file, ... /* many flags */) as Cool.Library; var list = new List<CoolAsset>(); foreach(var asset in lib.Assets) { list.Add(asset); } try { log(">> foreach"); foreach(var asset in list) { log(">> foreach got " + asset); /* some long operations */ log(">> foreach asset " + asset + " is OK"); } log("<< foreach"); } catch (Exception e) { log(">> foreach failed due to " + e + "\n" + e.StackTrace); } /* some other stuff */ /* end of function */
私たちは始め、 瞑想し 、何を見ますか?
ゴミ
>> foreach >> foreach got foo ... >> foreach asset foo is OK ... >> foreach got bar ... >> foreach asset bar is OK >> foreach failed due to COMException ... E_RPC_DISCONNECT ...
つまり、コードを見て-
log(">> foreach asset " + asset + " is OK"); } log("<< foreach");
閉じブレースにドロップしました。
これはどのようにできますか?
書きます
... try { log(">> foreach"); var itr = list.GetEnumerator(); for(;;) { log(">> foreach new cycle..."); if(!itr.MoveNext()) break; log(">> foreach new cycle and it have extra elements to iterate..."); var asset = itr.Current; log(">> foreach got " + asset); /* some long operations */ log(">> foreach asset " + asset + " is OK"); } log("<< foreach"); } catch (Exception e) { log(">> foreach failed due to " + e + "\n" + e.StackTrace); } /* some other stuff */ /* end of function */
もちろんログでこれを取得します
さらに詳細なでたらめ。 不健康
>> foreach >> foreach new cycle... >> foreach new cycle and it have extra elements to iterate... >> foreach got foo ... >> foreach asset foo is OK ... >> foreach got bar ... >> foreach asset bar is OK >> foreach new cycle... >> foreach failed due to COMException ... E_RPC_DISCONNECT ...
つまり、MoveNext()で落ちます。
しかし、ちょっと待ってください! これは、純粋な.NETリスト<T>からの純粋な.NETイテレータです! どう? 実際、私たちは何も見つけることなく、自分の尻尾のために走りました。
尾は犬をくるくる
コーヒーを取り、マニュアルを開いて、両社のインド人を大声でscりながら、私は一般にすべての実験を捨て、元の形に戻し
... for(int i=0; i < lib.Assets.Count; ++i) { var asset = lib.Assets[i]; /* some long operations */ } ...
コードがエラーなしで機能したときの驚きは何でしたか(そしてそれだけではありません) -E_RPC_TIMEOUTとE_RPC_DISCONNECTの両方を喜んで回避しました ! さらに、Windows 8.1では、問題の再現が100%でした。
回避策は見つかったようですが、それは何も説明しません。 はい、彼が見つかったのは、私が後輩だった当時、foreachの構造がなく、意図的な行動の代わりに、 下劣な古い学校の習慣を軽くたたいただけでした...
夕方はもう長続きしません
元のforeachに戻りますが、まだここにあります。 結局のところ、comオブジェクトには何かが正しくないという仮説を立てます。 いくつかの装飾的な行を追加します-デバッグの便宜のために:
... var assetsCollection = lib.Assets; foreach(var asset in assetsCollection) { /* some long operations */ } assetsCollection = null; ...
これらの2行が明らかです。
... var assetsCollection = lib.Assets; ... assetsCollection = null; ...
サイクルの周りには何も影響しませんよね? しかし、ブレークポイントを設定すると非常に便利です。
私は入れて、bryak-bryak、ローンチ、tynts-tynts、それらの資産を噛みながらそれらの20分間のhabrを読みました。 落ちませんでした。 うん
そして、おそらく、デバッガが干渉したと、Vigilant Eyeは推測した。 何も変更せずに、デバッグなしで起動します。 落ちませんでした。 すみません 各実装および各インターロープでさらに6つの起動-古代のマンモスコードが以前と同じように再び動作することを示します-どこでも。 はい、16回修正しました;-)
そして今-せむし!
それは思われる-と違いは何ですか?
この一連の事実を思い出してみましょう。
- 直接私たちの病気のコードに加えて、ガベージコレクターもあります。
- COMの周りにはプロキシがあります。
- このプロキシはAddRef()/ Release()を隠します。
- リリース()の従来の実装では、通常、(count == 0)が削除されます。 COMサーバー側:
- comプロキシ(MarshalByRefObjectから継承されたもの)の場合、 ハンチバックコレクターが到着すると、Dispose()が呼び出され、Release()がプルされます。
これで、すでに何が間違っているのかを推測できます。
明らかに、lib.Assetsは一度計算され、他の場所では使用されません。 これは、注意深いコンパイラーが、アセットのコレクションをシートに追加する最初のサイクルの直後にこの事実に注意することを意味します。
さらに、この方法では、コレクターはいつでもそのリンクを取得できます。また、この方法が非常に長い間機能するという事実-この確率はほぼ100%に増加します。 しかし、子アイテムは明らかです-遅延初期化を伴う値オブジェクトであり、それらが何であり、どのように内部に格納されているかを推測することしかできません。 結局のところ、すべての項目が取得時にAddRef()を呼び出すということはまったくありません。 私もお勧めします-これは保証されていません。 サーバーcom(どこからでも呼び出される)を記述する場合、マスターコレクションがRelease()と言い、子要素を使用し続けることを期待します。子要素の一部は初期化されずに残ります...奇妙なパターン。
しかし、2つの「重要でない」行を追加します。これは、宣言の最初から関数の終わりまで存続するローカル変数であり、そのアセンブリは「最後の使用後」より前に開始されることが保証されています。
そして、Windows 8.1はそれと何の関係がありますか? そしてバージョン4.5で。 わずかに積極的なデフォルトのガベージコレクション-そしてここにあります。
この仮説をテストしました。AWSt2.microインスタンスを検証に使用して、Windows 2012R2 / .NET 4.5 64bitで同じ効果を繰り返しました。 さらに、E_RPC_DISCONNECTは準備されたシステムよりもはるかに高速に実行されたため、そこに何かがあります。
しかし、それにもかかわらず、これらは死後の世界からの知的化であり 、おそらく他の要因があります。