2010年02月09日(火)
■[%ruby]観察日記 2010-02-09

NetBSDにかまけていてサボっていましたが、まぁぼちぼち。
make clean
(mame) make clean してもえらいいろいろ残るな
(mame) parse.c とか prelude.c とか
(eban) parse.cはtarballでいしょに配布してるので消すわけにはいかない
(mame) tarball のパッケージではどのみち make clean 使うなってスタンスではなかったっけ
(mame) 見つからないから気のせいか (注: 以前 IRC でそのような話がでていた)
(mrkn) そもそも make clean は中途半端で、
(mrkn) ビルドを途中で止めてmake clean したりすると enc 以下の clean でエラーでたりする
(eban) 昔はmake cleanがminirubyに依存してたりしたので、そのあたりの名残りで不整合があるんだろうな
M17N の教訓
(mame) Ruby の M17N 化を通して得られた経験は
(mame) まあ、CSI 方式は難しいから避けよう、という教訓にしかならないかもしれない
(kosaki) スクリプト言語だと、文字列以外も stringでやる伝統があるのでCSIと相性が悪くて云々というスジ
で
Perl は UCS 正規化方式だけれど、やっぱり文字列とバイト列絡みでは色々踏んだ記憶がある。
BigDecimal
あらすじ――RubySpec 修正に燃える mame、しかし、BigDecimal という強大な敵が立ちはだかるのであった― ―
(mame) BigDecimal のターゲットユーザってどんなんだろう
経理とか金融とか Rails とか諸説ありますが、実際の所どうなんでしょうね。
なお、BigDecimalシリーズはしばらく続きます
2010年02月07日(日)
■NetBSD 5.0 における pthread と fork

概要
NetBSD 5.0 において、複数の pthread が生きている状態で fork するとおかしくなる。
再現
再現プログラムではfork前に一度3つのpthreadを作り、その後1つ殺して lwpid 1 と 3 が残っている。この状態で3からforkすると子では3が残り、これにlwpid 1が割り当てられるのだが、どこかに3の情報が残っていて、その後呼びに行ってしまう
22612 2 a.out CALL _lwp_park(0xbb7ffd94,3,0x8049198,0x8049198)
22612 2 a.out RET _lwp_park -1 errno 3 No such process
詳細
NetBSD 5.0 ではカーネルスレッドに当たる lwp (light weight process) とユーザレベルを司る pthread が 1:1 対応している。プロセス内に存在する lwp の情報は、以下のあたりに保存されている。
// /usr/include/sys/lwp.h struct lwp { ... /* Process level and global state, misc. */ LIST_ENTRY(lwp) l_list; /* a: entry on list of all LWPs */ void *l_ctxlink; /* p: uc_link {get,set}context */ struct proc *l_proc; /* p: parent process */ LIST_ENTRY(lwp) l_sibling; /* p: entry on proc's list of LWPs */ ... };
// /usr/include/sys/lwp.h struct proc { ... pid_t p_pid; /* :: Process identifier. */ LIST_ENTRY(proc) p_pglist; /* l: List of processes in pgrp. */ struct proc *p_pptr; /* l: Pointer to parent process. */ LIST_ENTRY(proc) p_sibling; /* l: List of sibling processes. */ LIST_HEAD(, proc) p_children; /* l: List of children. */ LIST_HEAD(, lwp) p_lwps; /* p: List of LWPs. */ struct ras *p_raslist; /* a: List of RAS entries */ /* The following fields are all zeroed upon creation in fork. */ #define p_startzero p_nlwps int p_nlwps; /* p: Number of LWPs */ int p_nzlwps; /* p: Number of zombie LWPs */ int p_nrlwps; /* p: Number running/sleeping LWPs */ int p_nlwpwait; /* p: Number of LWPs in lwp_wait1() */ int p_ndlwps; /* p: Number of detached LWPs */ int p_nlwpid; /* p: Next LWP ID */ ... };
fork は実際には /sys/kern/kern_fork.c の fork1 で行われていて、ここでプロセスの複製と initial thread の作成を行っている模様。initial thread は fork を実行した lwp をテンプレートにして作られる。
/*
* Finish creating the child process.
* It will return through a different path later.
*/
lwp_create(l1, p2, uaddr, inmem, (flags & FORK_PPWAIT) ? LWP_VFORK : 0,
stack, stacksize, (func != NULL) ? func : child_return, arg, &l2,
l1->l_class);
/*
* It's now safe for the scheduler and other processes to see the
* child process.
*/
mutex_enter(proc_lock);
if (p1->p_session->s_ttyvp != NULL && p1->p_lflag & PL_CONTROLT)
p2->p_lflag |= PL_CONTROLT;
LIST_INSERT_HEAD(&parent->p_children, p2, p_sibling);
p2->p_exitsig = exitsig; /* signal for parent on exit */
LIST_INSERT_AFTER(p1, p2, p_pglist);
LIST_INSERT_HEAD(&allproc, p2, p_list);
この lwp_create は /usr/src/sys/kern/kern_lwp.c にある。しかし、引用部の後半にある、プロセスの lwp リストに新しい lwp を追加するコードは、別のプロセスの lwp をテンプレートにした場合を考慮しているように見えない。
/* * Create a new LWP within process 'p2', using LWP 'l1' as a template. * The new LWP is created in state LSIDL and must be set running, * suspended, or stopped by the caller. */ int lwp_create(lwp_t *l1, proc_t *p2, vaddr_t uaddr, bool inmem, int flags, void *stack, size_t stacksize, void (*func)(void *), void *arg, lwp_t **rnewlwpp, int sclass) { ... if (isfree == NULL) { l2 = pool_cache_get(lwp_cache, PR_WAITOK); memset(l2, 0, sizeof(*l2)); l2->l_ts = pool_cache_get(turnstile_cache, PR_WAITOK); SLIST_INIT(&l2->l_pi_lenders); } else { l2 = isfree; ts = l2->l_ts; KASSERT(l2->l_inheritedprio == -1); KASSERT(SLIST_EMPTY(&l2->l_pi_lenders)); memset(l2, 0, sizeof(*l2)); l2->l_ts = ts; } ... p2->p_nlwpid++; if (p2->p_nlwpid == 0) p2->p_nlwpid++; l2->l_lid = p2->p_nlwpid; LIST_INSERT_HEAD(&p2->p_lwps, l2, l_sibling); p2->p_nlwps++; }
つまり推測するに、LIST_INSERT_HEAD(&p2->p_lwps, l2, l_sibling) で、l2 の next が 3 を指したまま insert してしまっているのではあるまいか。その結果、あとで pthread_cond_wait した時に存在しない lwpid 3 を見に行って困る、と。
ちなみに、回避法はあって、pthread_createを十分な数作って捨てることで、欠番になっている lwpid を使った上で消せばよい(ぉ
workaround
Bug #2724 に書いたパッチを Ruby 1.9 に当てることで回避できます。
send-pr
http://www.netbsd.org/cgi-bin/query-pr-single.pl?number=42772 How to Repeat と Fix を使いそこねた
謝辞
この問題の解決には @_enami さんの助けがありました。
2010年02月04日(木)
■スレッドのスタック領域情報の取得

Unix 系でもそれぞれ異なるので調べた。
スタックオーバーフローのハンドリング (Stack Overflow Handling)
Linux
manpage
header
- #define _GNU_SOURCE
- pthread.h
API
int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr);
main thread
getrlimit(2) 可、LinuxThreads では pthread_*_np 不可。
FreeBSD, DragonFly BSD
manpage
header
- pthread_np.h
API
- int pthread_attr_get_np(pthread_t pid, pthread_attr_t *dst);
main thread
pthread_*_np のみ。getrlimit(2) 不可。
NOTE
Initial thread stack size (Was: postgresql port, link with libc_ror not?)
NetBSD
manpage
なし
header
- pthread.h
API
- int pthread_attr_get_np(pthread_t pid, pthread_attr_t *dst);
main thread
pthread_*_np、getrlimit 共に可。
OpenBSD
manpage
header
- pthread_np.h
API
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
main thread
未調査
Mac OS X
manpage
なし
header
- pthread.h
API
size_t pthread_get_stacksize_np(pthread_t);
void * pthread_get_stackaddr_np(pthread_t);
main thread
getrlimit(2) を用いる。pthread_*_np 不可。
2010年02月03日(水)
■[%ruby]観察日記 2010-02-03

RubySpecの人に怒られた
あまりに頭の悪いミスなのでぐうの音も出ない。
library/socket/tcpserver/gets_spec.rb で止まる
Thread.new { sleep } TCPServer.new(0).gets
mameさんが追跡中
FreeBSDでGC中にSEGV
e = [1,2,3].each 10000.times { e = [e].each } Thread.new { GC.start }.join
がFreeBSDで落ちる。FreeBSD はメインスレッドのマシンスタックのサイズの取得方法が違うらしい。DragonFlyBSDも同様のようだ。r26549,r26550 スタックオーバーフローのハンドリング (Stack Overflow Handling)
dlがlibffiで置き換えられた
Solaris (Sun Studio 11) や mswin32/mswin64 で dl が使えなくなってしまった。LLVM/clangでは動いた。
まつもとさんは「mswin32 が解決すれば」「まぁ,いいんじゃない」だそうなので、mswin32/mswin64 対策が行われれば、1.9.2 に入ると思われる。
2010年02月02日(火)
■[%ruby]観察日記 2010-02-01

RubySpecのバグの潰し方
(mame) http://regional.rubykaigi.org/tokyo03 豪華講師陣だな
(hermit_) 東京Ruby会議03 - Regional RubyKaigi [text/html; charset=utf-8]
(ko1_ndk) mameさん講師やればいいのに
(mame) 話すことないし
(ko1_ndk) RubySpecのバグの潰し方
(mame) 1. rubyspec を変える
(mame) 以上
わたしも今日数十個ほどバグを潰したんですが、全てRubySpec側の修正でした。というわけで、現在RubySpecの失敗は36個です。
RubyVMのソースコードの見方
(kosaki) すると、RubyVMのソースコードの見方が分かるんですね。わかります
(ko1_ndk) なん,だと
(shyouhei) まず1.3くらいのソースを、一回読んで、基礎知識をたくわえて、
(shyouhei) それでもちょとまだむずい。
(mame) dfp とか lfp とかの不変条件をすぐに忘れるのと
(mame) 全体的に何のためにその処理をしているのかわからないところが多くて困る
(ko1_ndk) なるほど
(shyouhei) 全体理解のためにはinsns.defからなにがどうなってvm.oまで到達するかがMakefile読まんと分からんのが。
(ko1_ndk) そのネタで yarv maniacs を書くか
(mame) vm_exec が難しい
(shyouhei) 「1時間で読むmatzruby」
(mame) 今は 30% くらいはわかってるけど
(ko1_ndk) 1.8 よりも簡単だと思うんだがなあ>VM
(mame) 1.8 は読んだことないのでしらない
(shyouhei) 1.8は読み始めるハードルは低いが、読んだからといって理解はできない。
(kosaki) 意図が謎すぎるということ?
(shyouhei) ruby 1.8はsetjmp/longjmpが乱舞してどっからどこにいくのかまったく分からない素敵コードです。
YARV ManiacsはRuby 1.9の実行系を読む人にはバイブルと言ってもいいのではないのでしょうか。まず、全容が分からないと部分を読んでもさっぱり分からないので。まぁ、そんな難解なYARVですが、eval.cやthread.cがVMから分離したのは福音だと思っています。あ、もちろんわたしはさっぱりです。
互換性〜ブラウザ編
IEにあわせて の話。HTML5とか最近の仕様はこの手の互換性維持のためのアルゴリズムが仕様注に記載されていて、もう。
Enumerable#interleave
Enumerable#interleave というメソッドを実装してみた。flattenしたzipという感じだが、欲しい人いる?
http://twitter.com/yukihiro_matz/status/8504330947
そういうのが欲しい人にはチャンスだと思います。
