TCPSocketがガベコレされるとTCP接続がcloseされるのかの件

なぜこんなことを調べているのかの発端はhttp://d.hatena.ne.jp/komamitsu/20090422/1240413644.

http://www.ruby-lang.org/ja/man/html/trap_IO.htmlとかをみると、IOクラスではGC時にファイルをクローズしてくれているらしい(まぁGCまかせはイクナイと思う)のでその関係なのかなぁと思ったりするのだけど、はっきりとした情報が見つからなかったので、もうちょい調べてみる。

まず、サンプルを作ってみる(手抜きでTCPサーバーとしてMemcached:port=11211立ち上げ済み)。

komamitsu@potato:~/svn/misc/lab/ruby$ cat gctest.rb
require 'socket'

class Gctest
  def initialize
    @str = "a" * 1024 * 1024
    @sock = TCPSocket.new '127.0.0.1', 11211
  end
end

1.upto(10) do
  p Time.now
  Gctest.new
  sleep 2
end

これを動かすと6回目のGctest.newでRuby側からTCP closeが発生する.


とりあえず、ruby-1.8.7-p160.tar.gzを落としてきて、ext/socket/socket.c, io.c, gc.cなどを眺めて見た. すると幾つか怪しげ(GCの際のcleanup的な処理)な箇所があったので、gdbで追ってみる.

aptitudeでいれているRubyはstrippedなので、上記のtarballで調査用のRubyコンパイルし、それを用いてサンプルRubyコードを実行する.

komamitsu@potato:~/svn/misc/lab/ruby$ gdb ~/local/bin/ruby
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb) b gc.c:1321
Breakpoint 1 at 0x80774b7: file gc.c, line 1321.
(gdb) b gc.c:1946
Breakpoint 2 at 0x8077fd7: file gc.c, line 1946.
(gdb) run gctest.rb

といろいろ動かしているうちに、時々ローカル変数を見ようとすると

1321                RDATA(obj)->dfree = (void (*)(void*))rb_io_fptr_finalize;
(gdb) p obj
No symbol "obj" in current context.

と言われることに気がついた。

おそらくgccの最適化の影響かしらと思い、MakefileのCFLAGSから-O2を削除して再コンパイル. 今度は見れた。

gdbで遊んでいたら色々わかった.

  • 文字列の生成(String.new)の際にメモリ不足の場合、gc.cのobj_free()が呼ばれる.
    • TCPSocketの祖先はIOなのでBUILTIN_TYPE(obj)==T_FILEと判定され、
      • Filalize用の関数(io.c:rb_io_fptr_finalize())が登録され
      • T_DEFERREDフラグが付与される.
  • スレッド切り替えのrb_thread_schedule()が呼ばれる際に, gc.cのrb_gc_finalize_deferred()->finalize_list()->run_final()が呼ばれる.
    • その際に上記obj_free()で登録されたFinalize関数が実行される.
      • io.c:rb_io_fptr_finalize()->rb_io_fptr_cleanup()->fptr_finalize()ときて、TCP socketがcloseされる.

Finalize時にclose(コード上はfclose())するソケットはext/socket/socket.cのinit_sock()で設定しているのかなぁ.

static VALUE
init_sock(sock, fd)
    VALUE sock;
    int fd;
{   
    rb_io_t *fp;

    MakeOpenFile(sock, fp);
    fp->f = rb_fdopen(fd, "r");
    fp->f2 = rb_fdopen(fd, "w");
       :


ひとまず、今回の挙動は、MemCache::Serverインスタンスが生成しているTCPSocketインスタンスGCによるもの、と自分の中で勝手に結論付けられたので、これで安心して忘れ去ることができる.