FireScratch を C# で作っていた時に発覚したメモリーリックっぽい現象に当たったので、珍しく issue https://github.com/dotnet/corefx/issues/32454 を立てました。
サーバーのほうで、.NET Core か .NET Framework を使って以下のような簡易 HTTP サーバーを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | namespace CheckHttpListener { class Program { static void Main( string [] args) { Console.WriteLine( "test server .net framework" ); var listener = new System.Net.HttpListener(); listener.Prefixes.Add( "http://127.0.0.1:5411/" ); listener.Start(); while ( true ) { var context = listener.GetContext(); var res = context.Response; res.StatusCode = 200; var sw = new System.IO.StreamWriter(res.OutputStream); sw.Write( string .Format( "text {0}" , DateTime.Now.ToString())); sw.Close(); } } } } |
これに対して、テスト用にアクセスするクライアントを作っておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | namespace CheckClient { class Program { static void Main( string [] args) { HttpClient client = new HttpClient(); int cnt = 0; while ( true ) { var res = client.GetAsync( "http://localhost:5411" ).Result; var text = res.Content.ReadAsStringAsync().Result; Console.WriteLine($ "{cnt} {text}" ); cnt++; System.Threading.Thread.Sleep(20); } } } } |
これを動かしておくと、.NET Framework の時にはサーバーのメモリが程よい大きさで止まるのですが、.NET Core のほうは、がんがんとメモリを食い潰してしまって最後にサーバーが倒れます。だいたい1時間ぐらい放置しておくと落ちます。
同じコードで、.NET Framework と .NET Core の挙動が異なるので、.NET Core の HttpListener のバグか?とも思ったのですが、どうやら HttpListener.GetContext が HttpListnerContonet が持つ Response オブジェクトの挙動が異なるので、一概にバグとは言えないような感じです。
Response を明示的に Close するか using を使う
解決策としては、Response に対して明示的に Close メソッドを呼び出すか、using を使って暗黙に Response が閉じられるようにします。
1 2 3 4 5 6 7 | var context = listener.GetContext(); var res = context.Response; res.StatusCode = 200; var sw = new System.IO.StreamWriter(res.OutputStream); sw.Write( string .Format( "text {0}" , DateTime.Now.ToString())); sw.Close(); res.Close(); // ★ |
あるいは、
1 2 3 4 5 6 7 8 9 | var context = listener.GetContext(); using ( var res = context.Response) // ★ { res.StatusCode = 200; using ( var sw = new System.IO.StreamWriter(res.OutputStream)) { sw.Write( string .Format( "text {0}" , DateTime.Now.ToString())); } } |
のように書きます。この Close 処理は .NET Framework でも有効なので、これで同じコードで .NET Core でも .NET Framework でもメモリリークが出ないようになります。
大抵のサンプルは HttpListener をたくさん回さないので、この問題にあたることはないのですが、実際に作ってサーバー化すると数時間後に落ちたりするので注意が必要ですね。これ、Windows + .NET Core だけの現象なのか、それとも Linux + .NET Core でも発生するのかはあとで調べておきます。