FireScratch を C# で作っていた時に発覚したメモリーリックっぽい現象に当たったので、珍しく issue https://github.com/dotnet/corefx/issues/32454 を立てました。
サーバーのほうで、.NET Core か .NET Framework を使って以下のような簡易 HTTP サーバーを作ります。
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(); } } } }
これに対して、テスト用にアクセスするクライアントを作っておきます。
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 が閉じられるようにします。
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(); // ★
あるいは、
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 でも発生するのかはあとで調べておきます。