来週の水曜に FSIJ の月例会があるのだが、そこで Ruby のメモリ削減の話をする。
LC2007 の後にやった話が主になるかな。
POSIX は Portable Character Set というものを定義しているが、これは端的にいえば ASCII printable にいくつかの制御文字を加えた文字集合である。
これは純粋に文字の集合であって具体的な符号は定義されていないが、符号についていくつかの制約が定義されている。
その制約は NUL は全部のビットが 0 であるとか、<zero> から <nine> までは連続した値であることとかであるが、Portable Character Set にある全ての文字は 1byte で表現され、C の char で (NUL 以外は) 正になるという制約もあるそうな。
そうすると Shift_JIS は明確にまずいよなぁ。
検索してみる。
EBCDIC はどうか。
文字が足りないという点はまぁてきとうに足すとして、アルファベット・数字が 8bit なのが引っかかるか? それは、char を unsigned にすればいい?
大きなファイル (OS インストール用の ISO イメージ) をあるマシンから他のマシンへ転送する必要が生じた。両方のマシンはひとつの LAN につながっている、というかひとつの hub につながっている。
なんとなく scp する気になれなかったので、もっと速い方法を考える。
まず考えたのが ftp であるが、両方とも ftp サーバが動いていない。
要するに TCP でファイルの内容を流せばいいだけなのだから ruby で数行書くか、とも思ったが、netpipes を思い出した。
調べてみると、やはり netpipes で可能である。
ファイルを送るホスト: HostA% faucet Port --once --out cat filename ファイルを受け取るホスト: HostB% hose HostA Port --netslave > filename
サーバからクライアントじゃなくて、クライアントからサーバへ送れるか試してみると、次のようにすればできるようだ。
ファイルを受け取るホスト: HostA% faucet Port --shutdown w --in --once sh -c 'cat > filename' ファイルを送るホスト: HostB% hose HostA Port --out cat filename
明日の「Ruby のメモリ管理とその消費量削減」の資料を作る... はずが、現実逃避かマシンのインストールに時間を使ってしまう。
http://www.tdiary.org/20071215.html で脆弱性が公開されたので、とりあえず修正を取り込んだ。
バージョンアップもしないとな。
どうも bootstraptest/test_thread.rb に時間がかかりすぎることがある。
縮めてみるとこうである。
% ./ruby -ve ' p $$ begin; t = Thread.new{ loop { } } p 100 Thread.pass p 101 t.raise t.join end' ruby 1.9.0 (2007-12-27 revision 0) [i686-linux] 8447 100
Thread.pass からすぐに戻ってこない。
gdb で attach しても、main thread は Thread.pass 内の rb_thread_schedule でロックを獲得しているところで止まっている loop {} なほうでは繰り返しロックを開放・獲得している感じがある。
いろいろ試していると、どうも他の端末でなんか作業をしたときに main thread に遷移するような感じである。
native thread の scheduling がおかしいのだろうか、と思って Linux kernel を新しくしたり Preemption Model を変えたりしてみるが症状が変わらない。
試しに nosmp をつけると症状がおさまった。
SMP でのスレッドスケジューリングの問題か? うぅむ。
ちょうど SCHED_OTHER で sched_yield という話題が Linux Kernel Watch で出ている。
なんか、そもそも SCHED_OTHER で sched_yield を使うのが間違いというニュアンス?
ちなみに、/proc/sys/kernel/sched_compat_yield が 0, 1 どちらでもこちらの症状は発生した。
もしかして、readline は setlocale する?
% LANG=ja_JP.EUC-JP strace -e open ruby-1.8 -rreadline -e 'Readline.readline("> ")' open("/etc/ld.so.cache", O_RDONLY) = 3 open("/lib/tls/i686/cmov/libdl.so.2", O_RDONLY) = 3 open("/lib/tls/i686/cmov/libcrypt.so.1", O_RDONLY) = 3 open("/lib/tls/i686/cmov/libm.so.6", O_RDONLY) = 3 open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3 open("/home/akr/ruby/18/lib/ruby/1.8/i686-linux/readline.so", O_RDONLY|O_LARGEFILE) = 3 open("/home/akr/ruby/18/lib/ruby/1.8/i686-linux/readline.so", O_RDONLY|O_LARGEFILE) = 3 open("/home/akr/ruby/18/lib/ruby/1.8/i686-linux/readline.so", O_RDONLY) = 3 open("/etc/ld.so.cache", O_RDONLY) = 3 open("/lib/libreadline.so.5", O_RDONLY) = 3 open("/lib/libncurses.so.5", O_RDONLY) = 3 open("/usr/share/terminfo/k/kterm", O_RDONLY|O_LARGEFILE) = 3 open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3 open("/etc/inputrc", O_RDONLY) = 3 open("/usr/lib/gconv/gconv-modules.cache", O_RDONLY) = 3 open("/usr/lib/gconv/EUC-JP.so", O_RDONLY) = 3 open("/usr/lib/gconv/tls/i686/sse2/cmov/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/tls/i686/sse2/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/tls/i686/cmov/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/tls/i686/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/tls/sse2/cmov/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/tls/sse2/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/tls/cmov/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/tls/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/i686/sse2/cmov/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/i686/sse2/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/i686/cmov/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/i686/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/sse2/cmov/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/sse2/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/cmov/libJIS.so", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/lib/gconv/libJIS.so", O_RDONLY) = 3 >
なんか、/usr/lib/locale/locale-archive とか読んでる。
しかし、そういえば、readline は多バイト文字を正しく扱えている気がする。カーソル移動もちゃんと一文字単位で動くし。
gdb で試す。
% gdb ruby-1.8 GNU gdb 6.4.90-debian Copyright (C) 2006 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1". (gdb) break setlocale Function "setlocale" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (setlocale) pending. (gdb) run -rreadline -e 'Readline.readline("> ")' Starting program: /home/akr/bin/ruby-1.8 -rreadline -e 'Readline.readline("> ")' Failed to read a valid object file image from memory. Breakpoint 2 at 0xb7df4fd6 Pending breakpoint "setlocale" resolved Breakpoint 2, 0xb7df4fd6 in setlocale () from /lib/tls/i686/cmov/libc.so.6 (gdb) bt #0 0xb7df4fd6 in setlocale () from /lib/tls/i686/cmov/libc.so.6 #1 0xb7d8a741 in _rl_init_eightbit () from /lib/libreadline.so.5 #2 0xb7d731e3 in rl_initialize () from /lib/libreadline.so.5 #3 0xb7d740b2 in readline () from /lib/libreadline.so.5 #4 0x08057239 in rb_protect (proc=0xb7d74080 <readline>, data=135476472, state=0xbfe27940) at eval.c:5471 #5 0xb7f6a149 in readline_readline (argc=1, argv=0xbfe27bb0, self=3084663620) at readline.c:83 #6 0x080557de in call_cfunc (func=0xb7f6a030 <readline_readline>, recv=3084663620, len=-1210511455, argc=dwarf2_read_address: Corrupted DWARF expression. ) at eval.c:5701 #7 0x0805e272 in rb_call0 (klass=<value optimized out>, recv=3084663620, id=7441, oid=7441, argc=135477768, argv=0xbfe27bb0, body=0xb7dc3af4, flags=<value optimized out>) at eval.c:5857 #8 0x0805ef51 in rb_call (klass=3084663560, recv=3084663620, mid=7441, argc=1, argv=0xbfe27bb0, scope=0, self=3084728740) at eval.c:6104 #9 0x0805c73a in rb_eval (self=3084728740, n=<value optimized out>) at eval.c:3482 #10 0x08069c9e in ruby_exec_internal () at eval.c:1640 #11 0x08069cd6 in ruby_exec () at eval.c:1660 #12 0x08069d01 in ruby_run () at eval.c:1670 #13 0x080528bf in main (argc=1196310860, argv=0x534f5000, envp=0x28005849) at main.c:48 (gdb)
やっぱ呼んでやがる。
boron で test_flush(TestIONonblock) が終わらないのをどうにか手元で再現できた。以下が終わらないことがある。(終わることもある)
% ./miniruby -v -rio/nonblock -e ' p $$ r, w = IO.pipe w.write_nonblock "a"*100000 w.nonblock = false w.sync = false Thread.new { loop { sleep 1; p r.sysread(100000).length } } w.write "b"*1 ts = (1..30).map { Thread.new { w.flush } } w.close ts.each {|t| t.join } p :ok ' ruby 1.9.0 (2007-12-29 revision 0) [i686-linux] 15946 65536 1
さて、誰が悪いのか?
再現できれば、だいたいの理由もわかるわけで、次は Ruby 抜きで再現させてみる。
% uname -a Linux nute 2.6.18-5-686 #1 SMP Sat Dec 1 22:58:58 UTC 2007 i686 GNU/Linux % cat t.c #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/select.h> #include <pthread.h> #include <errno.h> int fildes[2]; void *func(void *arg) { int ret; int fd = fildes[1]; fd_set ws, es; FD_ZERO(&ws); FD_SET(fd, &ws); FD_ZERO(&es); FD_SET(fd, &es); fprintf(stderr, "before select ws[%d]=%d es[%d]=%d\n", fd, FD_ISSET(fd, &ws)!=0, fd, FD_ISSET(fd, &es)!=0); ret = select(fd+1, NULL, &ws, &es, NULL); if (ret == -1) { perror("select"); exit(1); } fprintf(stderr, "after select ws[%d]=%d es[%d]=%d\n", fd, FD_ISSET(fd, &ws)!=0, fd, FD_ISSET(fd, &es)!=0); return NULL; } static char bigbuf[100000]; int main(int argc, char **argv) { pthread_t th; int ret; ssize_t ssize; ret = pipe(fildes); if (ret == -1) { perror("pipe"); exit(1); } ret = fcntl(fildes[1], F_SETFL, O_NONBLOCK); if (ret == -1) { perror("fcntl"); exit(1); } ssize = write(fildes[1], bigbuf, sizeof(bigbuf)); if (ssize == -1) { perror("write"); exit(1); } fprintf(stderr, "after write ssize=%ld\n", (long)ssize); ret = pthread_create(&th, NULL, func, NULL); if (ret != 0) { errno = ret; perror("pthread_create"); exit(1); } sleep(1); fprintf(stderr, "before close\n"); ret = close(fildes[1]); if (ret == -1) { perror("close"); exit(1); } fprintf(stderr, "after close\n"); fprintf(stderr, "before read\n"); ssize = read(fildes[0], bigbuf, sizeof(bigbuf)); if (ssize == -1) { perror("read"); exit(1); } fprintf(stderr, "after read ssize=%ld\n", (long)ssize); ret = pthread_join(th, NULL); if (ret != 0) { errno = ret; perror("pthread_join"); exit(1); } return 0; } % gcc t.c -lpthread % ./a.out after write ssize=65536 before select ws[4]=1 es[4]=1 before close after close before read after read ssize=65536 (ここから進まない)
つまり、select で pipe が writable になるのを待っている最中にほかのスレッドがその fd を close し、その後で、pipe に空きができた場合、select がいつまでも帰ってこない。
たしかに空きができてもすでに close されてるから writable ではないといえばそうなのだが...
[latest]