すごくレアケースですがハマったのでメモ。
やったこと
メモリリークの疑いがあったので調査の過程でデストラクタに警告ログを出した。
public class SomeClass : IDisposable {
// GC発生時にこちらで回収された
~SomeClass() {
// ログ出力の自作クラス、内部で自作のロガーに警告ログを転送している
FinalizeLog.Warning(nameof(SomeClass));
Dispose(false);
}
// ロジックのバグで呼ばれなかった
public void Dispose() => Dispose(true);
void Dispose(bool disposing) {
...
}
}
何が起きたか
-
FinalizeLog.Warning
の呼び出しでInvalidOperationException
で落ちていた。- ロガー内でログの改行整理のために
Regex.Replace
を利用していた。
- ロガー内でログの改行整理のために
場所 System.WeakReference.set_Target(Object value)
場所 System.Text.RegularExpressions.Regex.Replace(String input, String replacement, Int32 count, Int32 startat)
場所 System.Text.RegularExpressions.Regex.Replace(String input, String replacement)
場所 System.Text.RegularExpressions.Regex.Replace(String input, String pattern, String replacement)
原因
-
Regex.Replace
内で通るSharedReference
では内部でWeakReference
を使用している。- Replace処理のパーサーキャッシュを保持
-
ファイナライザでWeakReference内のインスタンスが破棄されているので、新しいReplaceのパーサーを作成してキャッシュする。
-
WeakReference.set_Target
が呼ばれる。
-
-
- 例外
InvalidOperationException
- 例外
The reference to the target object is invalid. This exception can be thrown while setting this property if the value is a null reference or if the object has been finalized during the set operation.
ちゃんと読めば書いてあるけど、RegexがWeakReference使ってるとか普段意識しないですよね(ㆁωㆁ;)