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 でも発生するのかはあとで調べておきます。
