[ruby-dev:43901] ThreadGroup#make_local_space! (Re: ThreadGroup の強化案)
From:
Hidetoshi NAGAI <nagai@...>
Date:
2011-06-25 15:46:14 UTC
List:
ruby-dev #43901
永井@知能.九工大です.少々長いメールで失礼します.
ThreadGroup の強化案の改定版です.
まだ未完成ですが,試験的に実装した部分 (rev.32226 との差分) を添付します.
このサンプルで試していただける重要な要素は
ThreadGroup#make_local_space! です.
これはメソッド等に ThreadGroup 固有の空間を作ります.
local space を持つ ThreadGroup 内で定義されたメソッドは
その ThreadGroup 内でのみ有効となります.
他の ThreadGroup では元々のメソッドが見えており,影響を受けません.
# メソッドの undef への対応も必要ですが,そこは未実装です.
# メソッド以外の local space はまだ実装できていません.
ThreadGroup 固有の空間ですから,sub thread を作成されても大丈夫です.
ThreadGroup#enclose と組み合わせることで,
sandbox 作成などにも役立つのではないかと思います.
新しい ThreadGroup を作成したときには,それを作成した thread の
ThreadGroup が持つ local space を共有するようにしています.
その新しい ThreadGroup で make_local_space! を呼ぶと
現在の local space のコピーを作成して独立するという仕様です.
# この部分は未テストです.
以下がサンプルスクリプトです.
どの ThreadGroup でどのメソッド定義が見えているかに注目ください.
御意見などいただけますと幸いです.
---<thgrp_local_space-sample.rb>------------------------------------
thgrp = ThreadGroup.new
class A
def A.bar
p [Thread.current.group, :BAR]
end
def A.foo
p [Thread.current.group, :HOGE]
end
def foo
p [Thread.current.group, :hoge]
end
end
a = A.new
A.bar; A.foo; a.foo
# => [#<ThreadGroup:0x0000000087a988>, :BAR]
# [#<ThreadGroup:0x0000000087a988>, :HOGE]
# [#<ThreadGroup:0x0000000087a988>, :hoge]
thgrp.new_thread{ A.bar; A.foo; a.foo }.join
# => [#<ThreadGroup:0x00000000b89778>, :BAR]
# [#<ThreadGroup:0x00000000b89778>, :HOGE]
# [#<ThreadGroup:0x00000000b89778>, :hoge]
# Make local space on thgrp, and define on the local space
thgrp.make_local_space!
thgrp.new_thread{
def A.foo
p [Thread.current.group, :FUGA]
end
class A
def foo
p [Thread.current.group, :fuga]
end
end
}
thgrp.new_thread{ A.bar; A.foo; a.foo }.join
# => [#<ThreadGroup:0x00000000b89778>, :BAR]
# [#<ThreadGroup:0x00000000b89778>, :FUGA]
# [#<ThreadGroup:0x00000000b89778>, :fuga]
A.bar; A.foo; a.foo
# => [#<ThreadGroup:0x0000000087a988>, :BAR]
# [#<ThreadGroup:0x0000000087a988>, :HOGE]
# [#<ThreadGroup:0x0000000087a988>, :hoge]
Thread.new{
A.bar; A.foo; a.foo
# => [#<ThreadGroup:0x0000000087a988>, :BAR]
# [#<ThreadGroup:0x0000000087a988>, :HOGE]
# [#<ThreadGroup:0x0000000087a988>, :hoge]
thgrp.add Thread.current
A.bar; A.foo; a.foo
# => [#<ThreadGroup:0x00000000b89778>, :BAR]
# [#<ThreadGroup:0x00000000b89778>, :FUGA]
# [#<ThreadGroup:0x00000000b89778>, :fuga]
ThreadGroup::Default.add Thread.current
A.bar; A.foo; a.foo
# => [#<ThreadGroup:0x0000000087a988>, :BAR]
# [#<ThreadGroup:0x0000000087a988>, :HOGE]
# [#<ThreadGroup:0x0000000087a988>, :hoge]
}.join
--------------------------------------------------------------------
============================================================================
[ ThreadGroup の強化プラン ]
*** はじめに ***
Ruby の ThreadGroup は,単なる Thread 集合の制御だけではなく,
Thread 生成後に ThreadGroup を変更できるという性質により,
手続きや Thread (生成される sub-thread を含む) に操作可能な文脈を与えたり,
簡易 sandbox 作成を支援したりすることも可能である.
しかしながら,現在の ThreadGroup のメソッド群 (と機能) は,
そうした利用をサポートするには十分とは言えない.
例えば,thread 群に処理を分散させて結果を得る場合を考える.
thread 群を ThreadGroup にまとめたとしても,
現在は ThreadGroup 全体に対するメソッドに欠けるため,
結局は thread 群のリストを得て個別にメソッドを呼ぶ必要がある.
これではせっかく ThreadGroup を使用していても,その価値が低い.
また,ネットワークを介しているなどで thread が処理途中で例外終了する可能性があり,
そのような場合はすぐに代替 thread を起動する必要があるとすると,
システムが滞りなく動作するには多数の thread の実行状況を監視し続ける必要がある.
現在の Ruby でこれを行うには polling で状態を追う必要があり,効率が悪い
(いつどの thread がどういう理由で終了するかわからないので join では待てないため).
あるいは,待つべき thread を指定して別 thread で warp することで
終了順に serialize する方法 (対象 thread の明示が必要であったり,
thread 数が倍増することになる点が嬉しくない) を取る必要がある.
ThreadGroup の強化プランでは,そうしたケースでの処理効率改善も含める.
なお,添付ライブラリの thwait (TheadsWait クラス) が類似した機能を持つ存在だが,
同ライブラリの機能は,本来,ThreadGroup が担うべきものである.
その意味では,ThreadsWait と同じインスタンスメソッドも用意しておくべきであろう.
ThreadsWait のインスタンスメソッド群は次の通り.
--------------------------------------------
ThreadsWait#all_waits
ThreadsWait#all_waits{|thread| ...}
ThreadsWait#empty?
ThreadsWait#finished?
ThreadsWait#join(*threads)
ThreadsWait#join_nowait(*threads)
ThreadsWait#next_wait(nonblock = nil)
ThreadsWait#threads
--------------------------------------------
ThreadsWait と ThreadGroup との大きな違いは,
生成された sub-thread が自動的に管理下に置かれるかどうかにある.
ThreadsWait は,ThreadGroup 横断で thread を待たねばならない場合と,
別の ThreadGroup を生成してまとめることができない thread 群を待たねばならない場合
とで有効性を維持する.
後者のような例が本当に存在するかは分からないが,少なくとも前者のような例はあり得る.
*** 単純な Thread 操作メソッドの不足 ***
特定の ThreadGroup に Thread を生成するには,
Thread を生成した上で対象の ThreadGroup に移動するしかなかった.
しかしそれでは ThreadGroup を移動する前に
Thread の処理が進行してしまう可能性が高く,
それを回避するために Thread を一旦 stop しておいて
移動後に wakeup する必要があった.
この問題への対策として,ThreadGroup#new_thread を導入する.
実行には,現在の ThreadGroup で生成した thread を
レシーバの ThreadGroup に移動する権限を持つことが必要であるとする.
その他,例えば ThreadGroup に属する Thread すべての終了を待つようなメソッド
ThreadGroup#join のように,ThreadGroup を Thread 集合として操作するために
不足と思えるメソッドを追加する.
*** ThreadGroup の dominate ***
現在の ThreadGroup では,enclose された場合に
外部から Thread を導入する手段がない.
enclose とはそういうものだからという話はあるが,
enclose された ThreadGroup を監視する Thread (ThreadGroup) 側からすると,
少々心許ない制約である.
例えば,監視対象の ThreadGroup 内に管理用 Thread の類が必要となりうるなら,
enclose する前にそうした Thread を生成しておく必要がある.
また,もし何らかのトラブルで管理用 Thread が落ちてしまうと回復の方法がない.
そこで,ThreadGroup 間に dominate という関係を規定する.
ある ThreadGroup A が別の ThreadGroup B を dominate しているならば,
A に属する Thread は,B に,あるいは B から Thread を移動させることができる.
ただし,B だけでなく Thread の移動元/移動先にも A が移動権限を持たねばならない.
Thread の移動権限を持つのは ThreadGroup の操作権限を持つ場合で,
* 対象が制約無しの ThreadGroup
* 対象が domanated で,自身が対象を dominate している ThreadGroup の Thread
のいずれかの場合である.
dominate している ThreadGroup A との関係を除いては,
dominate されている ThreadGroup B は enclose されている場合と同じ制約を持つ.
なお,dominated → enclosed → frozen の関係は一方通行であり,
状態変更後は元に戻すことはできない.
*** Thread が持つアクセス権限 ***
現在の ThreadGroup には,
ThreadGroup 環境からの Jail Break を防ぐための enclose という機構があるが,
enclose された ThreadGroup 内の Thread の権限についてはきちんとした規定がない.
ThreadGroup を enclose する目的は,ThreadGroup によって与えられた文脈から
不用意に離脱してしまわないようにという安全策の意味もあるが,
特定の Thread 群を信用できないというケースもあるだろう.
そう考えると,enclose や freeze された ThreadGroup から
他の ThreadGroup に属する Thread を操作することは認めるべきではないと言える.
逆に,そうした操作を許すことができるのなら,
わざわざ enclose や freeze をする必要は乏しいと考える.
よって,enclose または freeze された ThreadGroup の Thread が,
権限のない他の ThreadGroup の Thread を操作することを禁止することが望ましい.
具体的には,Thread のインスタンスメソッドの内,
---------------------------------------------------
[]=, abort_on_exception=, exit, kill, terminate,
priority=, raise, run, wakeup
---------------------------------------------------
である (状態参照は許すが,状態変更は許さない).
しかし,互換性を失うためにこの制約を直接導入することは難しいだろう.
ゆえに,デフォルトでは従来通り制約はなしとしておき,
制約を設定管理するためのメソッドとして
ThreadGroup#lock, ThreadGroup#locked? を導入する.
この場合,enclose などをされていない ThreadGroup に
制約を付与することも可能ではある (ユースケースは乏しそうだが…).
*** ThreadGroup の thread_queue ***
冒頭で述べたような polling の問題を回避するために,
終了した thread を受けとるための thread_queue を ThreadGroup が持てるようにする.
thread_queue は,終了した Thread を
終了順にできるだけ早く楽に処理にかけるための機構である.
もちろん,動作させる thread の処理内容に
終了をレポートする処理を書いておくという方法もないわけではないが,
それを実装するとなると少々面倒であろう.
終了した thread が自動的に特定の queue に追加されるなら
(この処理は C レベルで Coding する必要がある),
管理側がその queue から読み出して実行する処理を書くだけで良く,
面倒な部分を隠蔽することができる.
管理対象となるのは Thread の集合であるから,
ThreadGroup にそうした queue を持たせるのは妥当と言える.
thread_queue は一つの ThreadGroup につき一つだけとする.
thread_queue のモードは,正常終了,例外終了のそれぞれに対し,
thread_queue に追加する/しないの2種類を設定する.
デフォルトは「いずれも追加しない」である.
メモリ消費の爆発を避けるため,デフォルトでは thread_queue のサイズを有限とし,
保持サイズを超えた場合は古い Thread から捨てることとする.
thread_queue のアクセスには権限が必要である.
自らが制約を持たないか,自らが対象の ThreadGroup を dominate している場合に限り,
対象の ThreadGroup の thread_queue へのアクセスを許すこととする.
*** ThreadGroup の固有データ ***
Thread に文脈を与えるためには ThreadGroup に文脈を規定するデータを保持させる.
もし新しい Thread が生成されても,自動的に現在の ThreadGroup に所属するため,
ThreadGroup による文脈が保持される (ThreadGroup を移動すれば,文脈も変更される).
これまでは Thread のように ThreadGroup に固有データを持たせるためのメソッドが
定義されてはいなかったため,Thread クラス同様に固有データを扱うメソッドを追加する.
*** メソッド一覧 ***
ThreadGroup.new()
ThreadGroup.new(hash)
ThreadGroup 固有データを設定する引数を追加
current ThreadGroup を親 ThreadGroup として保持する
ThreadGroup.current
Thread.current.group に同じ
ThreadGroup.queueing
current thread をその threadgroup の thread queue に入れる
thread queue への投入が許可されていなければ,例外を生じる
ThreadGroup#add(thread)
ThreadGroup#add(*threads)
複数の thread を一度に受付できるように,引数パターンを拡張
max_threads を超過する場合は,例外を生じる
ThreadGroup#<<(thread)
ThreadGroup#add(thread) に同じ
ThreadGroup#list
従来と同じ
ThreadGroup#threads
ThreadsWait と同等のメソッドを揃える目的で追加するメソッド
実体は ThreadGroup#list
ThreadGroup#max_threads
ThreadGroup#max_threads=(size)
ThreadGroupに含めることが可能な最大 thread 数を設定/参照する
値 0 を設定した場合は無制限となる
制限を超過するような処理が実行された場合は例外を生じる
設定時にすでに ThreadGroup に存在する thread 数よりも少ない値を
設定しようとした場合,値を指定通りに設定した上で例外を生じる
ThreadGroup#size
ThreadGroup#length
呼んだ時点でThreadGroupに属するthreadの数を返す
ThreadGroup#enclose
ThreadGroup#enclosed?
従来と同じ
ただし,dominated であれば enclosed? は true となる
ThreadGroup#lock
ThreadGroup#locked?
他の ThreadGroup に属する Thread の操作制約の設定と状態参照
ThreadGroup#[]
ThreadGroup#[]=
ThreadGroup#key?(key)
ThreadGroup#keys
ThreadGroup 固有データへのアクセス
現在の ThreadGroup がレシーバの操作権限を持たない場合には参照・変更は不許可
( 現在の ThreadGroup == レシーバの場合は操作権限あり )
ThreadGroup#new_thread(){ ... }
レシーバの ThreadGroup に新しい thread を生成 (Thread.newと同じ)
引数は Thead#new と同じ
実行は,現在の ThreadGroup からレシーバへの Thread 移動権限を持つことが必要
ThreadGroup#start_thread(){ ... }
ThreadGroup#fork_thread(){ ... }
レシーバの ThreadGroup に新しい thread を生成 (Thread.{start,fork}と同じ)
引数は Thead#new と同じ
ThreadGroup#new_thread と違って Thread#initialize を呼ばない
実行は,現在の ThreadGroup からレシーバへの Thread 移動権限を持つことが必要
ThreadGroup#new_thread_of(klass,...){ ... }
ThreadGroup#start_thread_of(klass,...){ ... }
ThreadGroup#fork_thread_of(klass,...){ ... }
生成する thread が klass クラスのものであることを除いては new_thread 等と同じ
ただし,klass は Thread クラスのサブクラスでなければならない
ThreadGroup#dominate(threadgroup)
レシーバが引数の threadgroup を dominate するようにする
引数の threadgroup が enclose されていなければ enclose される
現在の ThreadGroup がレシーバと引数との両方に操作権限を持たなければ例外を発生
もし,引数の threadgroup == レシーバなら,例外を発生する
ThreadGroup#dominate?(threadgroup)
レシーバが threadgroup を dominate しているなら true を返す
現在の ThreadGroup がレシーバと引数との操作権限を持たない場合には例外となる
現在の ThreadGroup == 引数の場合,enclosed 以上であれば権限なしとする
( enclosed 以上の ThreadGroup が自分の dominate 状態を調べるのを禁止するため )
ThreadGroup#dominated?
レシーバを dominate している ThreadGroup を返す
現在の ThreadGroup がレシーバの操作権限を持たない場合には例外となる
現在の ThreadGroup == レシーバの場合,enclosed 以上であれば権限なしとする
( enclosed 以上の ThreadGroup が自分の dominate 状態を調べるのを禁止するため )
ThreadGroup#make_local_space!
レシーバに local space を作成する
レシーバがすでに固有の local space を持つ場合は何もせずに戻る
レシーバが継承された local space を持つ場合は,
それをコピーしたものを固有の local space として設定する
一度設定された local space を除去することはできない
local space を持つ threadgroup の thread が新たに threadgroup を生成した場合,
その local space が新たな threadgroup に継承 (共有) される
ThreadGroup#has_local_space?
レシーバが local space を持つかどうかを真偽値で返す
真を返す場合,その local space が固有のものか継承されているものかは問わない
ThreadGroup#join(exception=false)
ThreadGroup#join(limit, exception=false))
ThreadGroup のすべての thread が終了するのを待つ
次項の ThreadsWait と同等の機能を持たせるための書式も参照すること
引数 limit (秒単位の数値) が指定されていれば,その時間で timeout する
join を呼んだ後に生成された thread であっても終了を待つ
終了した thread を配列にして返す
引数 exception については,
nil であるときは,例外を生じた thread は破棄してそれ以外の thread のみを返す
true であるときは,例外の有無に限らずすべての thread を戻り値の配列に含める.
false のときは,例外を生じた thread に出会った時点でその例外を発生させる
ThreadGroup#join(thread, ..., thread, exception=false)
ThreadGroup#join(thread, ..., thread, limit, exception=false))
ThreadsWait と同等のメソッドを揃える目的で追加する引数バリエーション
引数の最左群が thread オブジェクトである場合,
それらの thread をすべてレシーバである threadgroup に add した後,
残りの引数を使って ThreadGroup#join を呼ぶ.
ThreadGroup#join_nowait(*threads)
ThreadsWait と同等のメソッドを揃える目的で追加するメソッド
実体は ThreadGroup#add(*threads)
ThreadGroup#wait_queueing()
ThreadGroup#wait_queueing(*threads)
指定された thread 群のすべてが threadqueue に入るのを待つ
thread queue への投入許可がどの種の thread にもなされていなければ,例外を生じる
引数指定がない場合,呼んだ時点で threadgroup に属するすべての thread を対象とする
queueing 待ちの対象とされた thread すべてを配列にして返す
ThreadGroup#values(exceptions=false)
すべての thread の戻り値を配列にして返す
値と thread との対応が取れないので,値だけが必要な場合に用いる
thread との対応が必要なら,join で得た thread の配列に対して each すること
引数が nil であるときは,例外を生じた thread は破棄してそれ以外の戻り値のみを返す
引数が true であるときは,例外は例外オブジェクトとして戻り値の配列に含める.
引数が false のときは,例外を生じた thread に出会った時点でその例外を発生させる
ThreadGroup#exit
ThreadGroup#kill
ThreadGroup#terminate
ThreadGroup のすべての thread を terminate する
kill を呼んだ後に生成された thread であっても terminate する
実体はこんな感じ
------------------------------------
current = Thread.current
Thread.exclusive do
until((lst = self.list - current).empty?) do
lst.each {|th| th.kill}
end
end
current.kill if current.group == self
------------------------------------
ThreadGroup#each{|th| ... }
呼んだ時点で ThreadGroup に含まれるすべての thread について繰り返す
threadgroup.list.each{|th| ... } と同等
ThreadGroup#each_queueing{|th| ... }
threadqueue に入れられた thread のそれぞれに対してブロックを実行する
thread queue を利用するため,対象 thread は thread queue の mode に依存する
thread queue への投入許可がどの種の thread にもなされていなければ,例外を生じる
ThreadGroup に属する thread が存在しなくなれば終了する
ThreadGroup#all_waits
ThreadGroup#all_waits{|th| ... }
ThreadsWait と同等のメソッドを揃える目的で追加するメソッド
ブロックを与えられない時は ThreadGroup#join と同じ
ブロックを与えられた時は ThreadGroup#each_join と同じ
ThreadGroup#thread_queue_pop(non_block=false)
thread queue から終了した thread を一つ取り出す
thread queue への投入許可がどの種の thread にもなされていなければ,例外を生じる
ThreadGroup#set_thread_queue_mode(succeed=false,exception=false,queueing=false)
終了した thread を蓄える thread queue のモードの設定
正常終了,例外終了,queueing のそれぞれに対し,thread を蓄えるかを真偽値で設定する
ThreadGroup#get_thread_queue_mode
終了した thread を蓄える thread queue のモードの参照
thread を蓄えるか否かの真偽値を [succeed, exception, queueing] の配列で返す
ThreadGroup#thread_queue_length
ThreadGroup#thread_queue_length=(len)
ThreadGroup#thread_queue_limit
ThreadGroup#thread_queue_limit=(len)
thread queue の最大長を参照・設定する
thread 数の爆発を防ぐためのもの
最大長を超える場合は古い thread から捨てられる
デフォルトの長さは,配列のデフォルト長さと同じ 16 としておく
ThreadGroup#thread_queue_empty?
終了した thread を蓄える thread queue のが空かどうかを調べる
queue が空であれば true を返す
ThreadGroup#finished?
ThreadsWait と同等のメソッドを揃える目的で追加するメソッド
実体は ! threadgroup.thread_queue_empty?
ThreadGroup#next_wait
ThreadsWait と同等のメソッドを揃える目的で追加するメソッド
実体は ThreadGroup#thread_queue_pop と同じ
ThreadGroup#abort_on_exception
ThreadGroup#abort_on_exception=(state)
TreadGroup 内の thread での例外発生時にインタプリタ自体を終了させるか否か
ThreadGroup#raise
現在 ThreadGroup に所属するすべての thread に例外を発生させる
新たに生成された thread にまで例外を発生させる必要はなさそうなので、
実体は Thread.exclusive{threadgroup.list.each{|th| th.raise(...)}} とする
ThreadGroup#alive?
現在 ThreadGroup に所属する thread で生きているものが存在すれば true を返す
ThreadGroup#empty?
ThreadsWait と同等のメソッドを揃える目的で追加するメソッド
現在 ThreadGroup に所属する thread が存在しなければ true
実体は threadgroup.list.empty? とする
ThreadGroup#wakeup
現在 ThreadGroup に所属する thread のすべてを起こす
実体は単純に Thread.exclusive{threadgroup.list.each{|th| th.wakeup}} とする
*** ThreadGroup には定義しない予定のメソッド ***
Thread にあるメソッドの内,以下は ThreadGroup での機能定義が難しいので
導入しない予定である.
ThreadGroup#value
複数の Thread が対象となるので,ThreadGroup#values で定義
ThreadGroup#run
現在 ThreadGroup に所属する thread のすべてを起こす
wakeup は意味があると思うが,run は疑問 (どれに制御を移すべきかが規定不能)
現在の thread list に順次 run をかけるという形はあり得るが...
ThreadGroup#priority
ThreadGroup#priority=
現在 ThreadGroup に所属する thread のすべてに対して設定する.
thread ごとに異なる可能性が高いので,参照は不可.
============================================================================
--
永井 秀利 (nagai@ai.kyutech.ac.jp)
九州工業大学大学院情報工学研究院知能情報工学研究系知能情報メディア部門助教
Attachments (1)
thgrp_improve.diff20110626-0005
(32.6 KB, text/x-diff)
Index: thread.c
===================================================================
--- thread.c (revision 32226)
+++ thread.c (working copy)
@@ -407,6 +407,7 @@
}
static VALUE rb_threadptr_raise(rb_thread_t *, int, VALUE *);
+static int thgroup_abort_on_exception(VALUE group);
void
ruby_thread_init_stack(rb_thread_t *th)
@@ -473,6 +474,7 @@
}
else if (th->safe_level < 4 &&
(th->vm->thread_abort_on_exception ||
+ thgroup_abort_on_exception(th->thgroup) ||
th->abort_on_exception || RTEST(ruby_debug))) {
/* exit on main_thread */
}
@@ -538,16 +540,18 @@
}
static VALUE
-thread_create_core(VALUE thval, VALUE args, VALUE (*fn)(ANYARGS))
+thread_create_into_thgroup(VALUE thval, VALUE thgroup, VALUE args, VALUE (*fn)(ANYARGS))
{
rb_thread_t *th;
int err;
- if (OBJ_FROZEN(GET_THREAD()->thgroup)) {
+ GetThreadPtr(thval, th);
+
+ if (OBJ_FROZEN(thgroup)) {
+ th->thgroup = (VALUE)NULL; /* clear ThreadGroup (maybe allocated) */
rb_raise(rb_eThreadError,
"can't start a new thread (frozen ThreadGroup)");
}
- GetThreadPtr(thval, th);
/* setup thread environment */
th->first_func = fn;
@@ -555,7 +559,7 @@
th->first_args = args; /* GC: shouldn't put before above line */
th->priority = GET_THREAD()->priority;
- th->thgroup = GET_THREAD()->thgroup;
+ th->thgroup = thgroup;
native_mutex_initialize(&th->interrupt_lock);
if (GET_VM()->event_hooks != NULL)
@@ -572,14 +576,34 @@
return thval;
}
+static VALUE
+thread_create_core(VALUE thval, VALUE args, VALUE (*fn)(ANYARGS))
+{
+ rb_thread_t *th;
+ GetThreadPtr(thval, th);
+ if (th->thgroup) {
+ /* ThreadGroup is already allocated */
+ /*
+ * NOTE: This check is required for ThreadGroup#new_thread_of method.
+ * The method call initialize of the subclass of Thread, and
+ * then, the threadgroup of the new thread must be allocated
+ * (initialize method doesn't have a ThreadGroup argument).
+ */
+ return thread_create_into_thgroup(thval, th->thgroup, args, fn);
+ } else {
+ return thread_create_into_thgroup(thval, GET_THREAD()->thgroup, args, fn);
+ }
+}
+
/* :nodoc: */
static VALUE
thread_s_new(int argc, VALUE *argv, VALUE klass)
{
rb_thread_t *th;
VALUE thread = rb_thread_alloc(klass);
+ GetThreadPtr(thread, th);
+ th->thgroup = (VALUE)NULL; /* clear ThreadGroup */
rb_obj_call_init(thread, argc, argv);
- GetThreadPtr(thread, th);
if (!th->first_args) {
rb_raise(rb_eThreadError, "uninitialized thread - check `%s#initialize'",
rb_class2name(klass));
@@ -3090,22 +3114,275 @@
rb_thread_atfork_internal(terminate_atfork_before_exec_i);
}
+enum thgroup_classlocal_tables_index {
+ RB_THGROUP_CLASSLOCAL_M_TBL,
+ RB_THGROUP_CLASSLOCAL_CONST_TBL,
+ RB_THGROUP_CLASSLOCAL_IV_TBL,
+ RB_THGROUP_CLASSLOCAL_IV_INDEX_TBL,
+ RB_THGROUP_CLASSLOCAL_TBL_MAX
+};
+
struct thgroup {
int enclosed;
+ int dominated;
+ int locked;
+ int max_threads;
+
VALUE group;
+ VALUE dominator;
+
+ int abort_on_exception;
+
+ /* thread queue */
+ int th_que_mode;
+ VALUE th_queue;
+ VALUE que_mutex;
+ VALUE que_waiting;
+
+ /* local storage */
+ st_table *local_storage;
+
+ /* local space */
+ int local_space; /* 0:none, 1:inherit, 2:own */
+ st_table *global_tbl;
+ st_table *class_tbl;
+ st_table *classlocal_tables;
};
+static int
+thgroup_mark_classlocal_tables_i(VALUE key, st_data_t val, st_data_t arg)
+{
+ st_table **tables = (st_table **)val;
+ enum thgroup_classlocal_tables_index idx;
+
+ RUBY_MARK_UNLESS_NULL(key);
+ for(idx = 0; idx < RB_THGROUP_CLASSLOCAL_TBL_MAX; idx++) {
+ rb_mark_tbl(tables[idx]);
+ }
+ return ST_CONTINUE;
+}
+
+static void
+thgroup_mark(void * const ptr)
+{
+ struct thgroup *data = NULL;
+ RUBY_MARK_ENTER("thgroup");
+ if (ptr) {
+ data = ptr;
+ RUBY_MARK_UNLESS_NULL(data->group);
+ RUBY_MARK_UNLESS_NULL(data->dominator);
+ RUBY_MARK_UNLESS_NULL(data->th_queue);
+ RUBY_MARK_UNLESS_NULL(data->que_mutex);
+ RUBY_MARK_UNLESS_NULL(data->que_waiting);
+ rb_mark_tbl(data->local_storage);
+ rb_mark_tbl(data->global_tbl);
+ rb_mark_tbl(data->class_tbl);
+ if (data->classlocal_tables) {
+ st_foreach(data->classlocal_tables, thgroup_mark_classlocal_tables_i, (st_data_t)0);
+ }
+ }
+ RUBY_MARK_LEAVE("thgroup");
+}
+
+static int
+thgroup_free_classlocal_tables_i(VALUE key, st_data_t val, st_data_t arg)
+{
+ st_table **tables = (st_table **)val;
+ enum thgroup_classlocal_tables_index idx;
+ for(idx = 0; idx < RB_THGROUP_CLASSLOCAL_TBL_MAX; idx++) {
+ if (tables[idx]) st_free_table(tables[idx]);
+ }
+ return ST_CONTINUE;
+}
+
+static void
+thgroup_free(void *ptr)
+{
+ RUBY_FREE_ENTER("thgroup");
+ if (ptr) {
+ struct thgroup *data = ptr;
+ if (data->local_storage) st_free_table(data->local_storage);
+ if (data->global_tbl) st_free_table(data->global_tbl);
+ if (data->class_tbl) st_free_table(data->class_tbl);
+ if (data->classlocal_tables) {
+ st_foreach(data->classlocal_tables, thgroup_free_classlocal_tables_i, (st_data_t)0);
+ st_free_table(data->classlocal_tables);
+ }
+ ruby_xfree(ptr);
+ }
+ RUBY_FREE_LEAVE("thgroup");
+}
+
+static int
+thgroup_memsize_classlocal_tables_i(VALUE key, st_data_t val, st_data_t arg)
+{
+ st_table **tables = (st_table **)val;
+ size_t *size = (size_t *)arg;
+ enum thgroup_classlocal_tables_index idx;
+ for(idx = 0; idx < RB_THGROUP_CLASSLOCAL_TBL_MAX; idx++) {
+ if (tables[idx]) *size += st_memsize(tables[idx]);
+ }
+ return ST_CONTINUE;
+}
+
static size_t
thgroup_memsize(const void *ptr)
{
- return ptr ? sizeof(struct thgroup) : 0;
+ if (ptr) {
+ struct thgroup *data = (struct thgroup *)ptr;
+ size_t size = sizeof(struct thgroup);
+ if (data->local_storage) size += st_memsize(data->local_storage);
+ if (data->global_tbl) size += st_memsize(data->global_tbl);
+ if (data->class_tbl) size += st_memsize(data->class_tbl);
+ if (data->classlocal_tables) {
+ st_foreach(data->classlocal_tables, thgroup_memsize_classlocal_tables_i, (st_data_t) &size);
+ }
+ return size;
+ } else {
+ return 0;
+ }
}
static const rb_data_type_t thgroup_data_type = {
"thgroup",
- {NULL, RUBY_TYPED_DEFAULT_FREE, thgroup_memsize,},
+ {thgroup_mark, thgroup_free, thgroup_memsize,},
};
+
+st_table*
+rb_thgroup_global_table_get(VALUE group)
+{
+ struct thgroup *data;
+
+ if (!group) return (st_table*)NULL;
+
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ return data->global_tbl;
+}
+
+st_table*
+rb_thgroup_class_table_get(VALUE group)
+{
+ struct thgroup *data;
+
+ if (!group) return (st_table*)NULL;
+
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ return data->class_tbl;
+}
+
+st_table **
+rb_thgroup_classlocal_tables_get(VALUE group, VALUE klass)
+{
+ struct thgroup *data;
+ st_data_t val;
+
+ if (!group) return (st_table**)NULL;
+
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ if (!data->classlocal_tables || !st_lookup(data->classlocal_tables, (st_data_t)klass, &val)) {
+ return (st_table**)NULL;
+ }
+ return (st_table**)val;
+}
+
+st_table*
+rb_thgroup_local_table_get_type(VALUE group, VALUE klass, enum thgroup_classlocal_tables_index type)
+{
+ st_table **table;
+
+ if (!group) return (st_table*)NULL;
+
+ if (table = rb_thgroup_classlocal_tables_get(group, klass)) {
+ return table[type];
+ }
+ return (st_table*)NULL;
+}
+
+st_table*
+rb_thgroup_local_m_table_get(VALUE group, VALUE klass)
+{
+ return rb_thgroup_local_table_get_type(group, klass, RB_THGROUP_CLASSLOCAL_M_TBL);
+}
+
+st_table*
+rb_thgroup_local_const_table_get(VALUE group, VALUE klass)
+{
+ return rb_thgroup_local_table_get_type(group, klass, RB_THGROUP_CLASSLOCAL_CONST_TBL);
+}
+
+st_table*
+rb_thgroup_local_iv_table_get(VALUE group, VALUE klass)
+{
+ return rb_thgroup_local_table_get_type(group, klass, RB_THGROUP_CLASSLOCAL_IV_TBL);
+}
+
+st_table*
+rb_thgroup_local_iv_index_table_get(VALUE group, VALUE klass)
+{
+ return rb_thgroup_local_table_get_type(group, klass, RB_THGROUP_CLASSLOCAL_IV_INDEX_TBL);
+}
+
+
+st_table **
+rb_thgroup_classlocal_tables_create(VALUE group, VALUE klass)
+{
+ struct thgroup *data;
+ st_table **tables;
+
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ if (!st_lookup(data->classlocal_tables, (st_data_t)klass, (st_data_t*) &tables)) {
+ /* allocate table array */
+ enum thgroup_classlocal_tables_index idx;
+
+ tables = ALLOC_N(st_table*, RB_THGROUP_CLASSLOCAL_TBL_MAX);
+
+ for(idx = 0; idx < RB_THGROUP_CLASSLOCAL_TBL_MAX; idx++) {
+ tables[idx] = (st_table*)NULL;
+ }
+
+ st_insert(data->classlocal_tables, (st_data_t)klass, (st_data_t)tables);
+ }
+
+ return tables;
+}
+
+st_table*
+rb_thgroup_local_table_create_type(VALUE group, VALUE klass, enum thgroup_classlocal_tables_index type)
+{
+ st_table **table;
+ table = rb_thgroup_classlocal_tables_create(group, klass);
+ if (!table[type]) {
+ table[type] = st_init_numtable();
+ }
+ return table[type];
+}
+
+st_table*
+rb_thgroup_local_m_table_create(VALUE group, VALUE klass)
+{
+ return rb_thgroup_local_table_create_type(group, klass, RB_THGROUP_CLASSLOCAL_M_TBL);
+}
+
+st_table*
+rb_thgroup_local_const_table_create(VALUE group, VALUE klass)
+{
+ return rb_thgroup_local_table_create_type(group, klass, RB_THGROUP_CLASSLOCAL_CONST_TBL);
+}
+
+st_table*
+rb_thgroup_local_iv_table_create(VALUE group, VALUE klass)
+{
+ return rb_thgroup_local_table_create_type(group, klass, RB_THGROUP_CLASSLOCAL_IV_TBL);
+}
+
+st_table*
+rb_thgroup_local_iv_index_table_create(VALUE group, VALUE klass)
+{
+ return rb_thgroup_local_table_create_type(group, klass, RB_THGROUP_CLASSLOCAL_IV_INDEX_TBL);
+}
+
+
/*
* Document-class: ThreadGroup
*
@@ -3125,12 +3402,216 @@
struct thgroup *data;
group = TypedData_Make_Struct(klass, struct thgroup, &thgroup_data_type, data);
- data->enclosed = 0;
+ data->enclosed = 0;
+ data->dominated = 0;
+ data->locked = 0;
+ data->max_threads = 0; /* no limit */
+
data->group = group;
+ data->dominator = GET_THREAD()->thgroup;
+ data->abort_on_exception = 0;
+
+ data->th_que_mode = 0;
+ data->th_queue = Qnil;
+ data->que_mutex = Qnil;
+ data->que_waiting = Qnil;
+
+ data->local_storage = (st_table *)NULL;
+
+ if (data->dominator) {
+ /* inherit local tables */
+ struct thgroup *dominator;
+ TypedData_Get_Struct(data->dominator, struct thgroup, &thgroup_data_type, dominator);
+ data->global_tbl = dominator->global_tbl;
+ data->class_tbl = dominator->class_tbl;
+ data->classlocal_tables = dominator->classlocal_tables;
+ data->local_space = 1;
+ } else {
+ data->global_tbl = (st_table *)NULL;
+ data->class_tbl = (st_table *)NULL;
+ data->classlocal_tables = (st_table *)NULL;
+ data->local_space = 0;
+ }
+
return group;
}
+static VALUE rb_thgroup_local_aset(VALUE group, ID id, VALUE val);
+
+static int
+thgroup_init_locals_i(VALUE key, VALUE value, VALUE group)
+{
+ if (key != Qundef) rb_thgroup_local_aset(group, rb_to_id(key), value);
+ return ST_CONTINUE;
+}
+
+static VALUE
+thgroup_initialize(int argc, VALUE *argv, VALUE group)
+{
+ struct thgroup *data;
+ VALUE locals;
+
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ if (!data->local_storage) {
+ data->local_storage = st_init_numtable();
+ }
+
+ rb_scan_args(argc, argv, ":", &locals);
+ if (!NIL_P(locals)) rb_hash_foreach(locals, thgroup_init_locals_i, group);
+
+ return group;
+}
+
+
+/*
+ * call-seq:
+ * ThreadGroup.current -> threadgroup
+ *
+ * Returns the threadgroup of the currently executing thread.
+ */
+
+static VALUE
+thgroup_s_current(VALUE klass)
+{
+ rb_thread_t *th;
+ GetThreadPtr(rb_thread_current(), th);
+ return th->thgroup;
+}
+
+
+VALUE
+rb_thgroup_local_aref(VALUE group, ID id)
+{
+ struct thgroup *data;
+ st_data_t val;
+
+ if (rb_safe_level() >= 4 && group != GET_THREAD()->thgroup) {
+ rb_raise(rb_eSecurityError, "Insecure: threadgroup locals");
+ }
+
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ if (!data->local_storage) {
+ return Qnil;
+ }
+ if (st_lookup(data->local_storage, id, &val)) {
+ return (VALUE)val;
+ }
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * thgroup[sym] -> obj or nil
+ *
+ * Attribute Reference---Returns the value of a threadgroup-local variable, using
+ * either a symbol or a string name. If the specified variable does not exist,
+ * returns <code>nil</code>.
+ */
+
+static VALUE
+rb_thgroup_aref(VALUE group, VALUE id)
+{
+ return rb_thgroup_local_aref(group, rb_to_id(id));
+}
+
+VALUE
+rb_thgroup_local_aset(VALUE group, ID id, VALUE val)
+{
+ struct thgroup *data;
+
+ if (rb_safe_level() >= 4 && group != GET_THREAD()->thgroup) {
+ rb_raise(rb_eSecurityError, "Insecure: can't modify threadgroup locals");
+ }
+
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+
+ if (OBJ_FROZEN(group)) {
+ rb_error_frozen("threadgroup locals");
+ }
+ if (!data->local_storage) {
+ data->local_storage = st_init_numtable();
+ }
+ if (NIL_P(val)) {
+ st_delete_wrap(data->local_storage, id);
+ return Qnil;
+ }
+ st_insert(data->local_storage, id, val);
+ return val;
+}
+
+/*
+ * call-seq:
+ * thgrp[sym] = obj -> obj
+ *
+ * Attribute Assignment---Sets or creates the value of a threadgroup-local variable,
+ * using either a symbol or a string. See also <code>ThreadGroup#[]</code>.
+ */
+
+static VALUE
+rb_thgroup_aset(VALUE self, VALUE id, VALUE val)
+{
+ return rb_thgroup_local_aset(self, rb_to_id(id), val);
+}
+
+/*
+ * call-seq:
+ * thgrp.key?(sym) -> true or false
+ *
+ * Returns <code>true</code> if the given string (or symbol) exists as a
+ * threadgroup-local variable.
+ *
+ * me = Thread.current.group
+ * me[:oliver] = "a"
+ * me.key?(:oliver) #=> true
+ * me.key?(:stanley) #=> false
+ */
+
+static VALUE
+rb_thgroup_key_p(VALUE self, VALUE key)
+{
+ struct thgroup *data;
+ ID id = rb_to_id(key);
+
+ TypedData_Get_Struct(self, struct thgroup, &thgroup_data_type, data);
+ if (!data->local_storage) {
+ return Qfalse;
+ }
+ if (st_lookup(data->local_storage, id, 0)) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+static int
+thgroup_keys_i(ID key, VALUE value, VALUE ary)
+{
+ rb_ary_push(ary, ID2SYM(key));
+ return ST_CONTINUE;
+}
+
+/*
+ * call-seq:
+ * thgrp.keys -> array
+ *
+ * Returns an an array of the names of the threadgroup-local variables (as Symbols).
+ */
+
+static VALUE
+rb_thgroup_keys(VALUE self)
+{
+ struct thgroup *data;
+ VALUE ary = rb_ary_new();
+
+ TypedData_Get_Struct(self, struct thgroup, &thgroup_data_type, data);
+
+ if (data->local_storage) {
+ st_foreach(data->local_storage, thgroup_keys_i, ary);
+ }
+ return ary;
+}
+
+
struct thgroup_list_params {
VALUE ary;
VALUE group;
@@ -3173,9 +3654,143 @@
return ary;
}
+struct thgroup_size_params {
+ int count;
+ VALUE group;
+};
+static int
+thgroup_size_i(st_data_t key, st_data_t val, st_data_t data)
+{
+ VALUE group = ((struct thgroup_size_params *)data)->group;
+ rb_thread_t *th;
+ GetThreadPtr((VALUE)key, th);
+
+ if (th->thgroup == group) {
+ ((struct thgroup_size_params *)data)->count++;
+ }
+ return ST_CONTINUE;
+}
+
/*
* call-seq:
+ * thgrp.size -> fixnum
+ * thgrp.length -> fixnum
+ *
+ * Returns the number of threads that belong to this group.
+ */
+
+static VALUE
+thgroup_size(VALUE group)
+{
+ struct thgroup_size_params param;
+
+ param.count = 0;
+ param.group = group;
+ st_foreach(GET_THREAD()->vm->living_threads, thgroup_size_i, (st_data_t) & param);
+ return INT2FIX(param.count);
+}
+
+/*
+ * call-seq:
+ * thgrp.empty? -> true or false
+ *
+ * Returns <code>true</code> if +self+ contains no threads.
+ */
+
+static VALUE
+thgroup_empty_p(VALUE group)
+{
+ return (RARRAY_LEN(thgroup_list(group)) == 0)? Qtrue: Qfalse;
+}
+
+
+struct thgroup_alive_params {
+ VALUE group;
+ int status;
+};
+
+static int
+thgroup_alive_i(st_data_t key, st_data_t val, st_data_t data)
+{
+ VALUE thread = (VALUE)key;
+
+ if (RTEST(rb_thread_alive_p(thread))) {
+ rb_thread_t *th;
+ GetThreadPtr(thread, th);
+ if (th->thgroup == ((struct thgroup_alive_params *)data)->group) {
+ ((struct thgroup_alive_params *)data)->status = 1;
+ return ST_STOP;
+ }
+ }
+ return ST_CONTINUE;
+}
+
+/*
+ * call-seq:
+ * thgrp.alive? -> true or false
+ *
+ * Returns <code>true</code> if +self+ contains a thread which is running
+ * or sleeping.
+ */
+
+static VALUE
+thgroup_alive_p(VALUE group)
+{
+ struct thgroup_alive_params param;
+
+ param.group = group;
+ param.status = 0;
+ st_foreach(GET_THREAD()->vm->living_threads, thgroup_alive_i, (st_data_t) & param);
+ return (param.status)? Qtrue: Qfalse;
+}
+
+/*
+ * call-seq:
+ * thgrp.wakeup -> thgrp
+ *
+ * Wakeup all threads which <i>thgrp</i> contains.
+ */
+
+static VALUE
+thgroup_wakeup(VALUE group)
+{
+ long i;
+ VALUE th_list;
+
+ th_list = thgroup_list(group);
+ for (i=0; i<RARRAY_LEN(th_list); i++) {
+ rb_thread_wakeup(RARRAY_PTR(th_list)[i]);
+ }
+ return group;
+}
+
+/*
+ * call-seq:
+ * thgrp.raise
+ * thgrp.raise(string)
+ * thgrp.raise(exception [, string [, array]])
+ *
+ * Raise a same kind of exception from all threads which <i>thgrp</i> contains.
+ */
+
+static VALUE
+thgroup_raise(int argc, VALUE *argv, VALUE self)
+{
+ long i;
+ VALUE th_list;
+ rb_thread_t *th;
+
+ th_list = thgroup_list(self);
+ for (i=0; i<RARRAY_LEN(th_list); i++) {
+ GetThreadPtr(RARRAY_PTR(th_list)[i], th);
+ rb_threadptr_raise(th, argc, argv);
+ }
+ return self;
+}
+
+/*
+ * call-seq:
* thgrp.enclose -> thgrp
*
* Prevents threads from being added to or removed from the receiving
@@ -3226,31 +3841,186 @@
/*
* call-seq:
- * thgrp.add(thread) -> thgrp
+ * thgrp.dominated? -> true or false
*
- * Adds the given <em>thread</em> to this group, removing it from any other
- * group to which it may have previously belonged.
+ * Returns <code>true</code> if <em>thgrp</em> is dominated. See also
+ * ThreadGroup#lock.
+ */
+
+static VALUE
+thgroup_dominated_p(VALUE group)
+{
+ struct thgroup *data;
+
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ if (data->dominated)
+ return Qtrue;
+ return Qfalse;
+}
+
+
+/*
+ * call-seq:
+ * thgrp.locked? -> true or false
*
- * puts "Initial group is #{ThreadGroup::Default.list}"
- * tg = ThreadGroup.new
- * t1 = Thread.new { sleep }
- * t2 = Thread.new { sleep }
- * puts "t1 is #{t1}"
- * puts "t2 is #{t2}"
- * tg.add(t1)
- * puts "Initial group now #{ThreadGroup::Default.list}"
- * puts "tg group now #{tg.list}"
+ * Returns <code>true</code> if <em>thgrp</em> is locked. See also
+ * ThreadGroup#lock.
+ */
+
+static VALUE
+thgroup_locked_p(VALUE group)
+{
+ struct thgroup *data;
+
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ if (data->locked)
+ return Qtrue;
+ return Qfalse;
+}
+
+static int
+thgroup_copy_classlocal_tables_i(VALUE key, st_data_t val, st_data_t arg)
+{
+ st_table **tables = (st_table **)val;
+ enum thgroup_classlocal_tables_index idx;
+ for(idx = 0; idx < RB_THGROUP_CLASSLOCAL_TBL_MAX; idx++) {
+ if (tables[idx]) {
+ tables[idx] = st_copy(tables[idx]);
+ }
+ }
+ return ST_CONTINUE;
+}
+
+/*
+ * call-seq:
+ * thgrp.make_local_space! -> thgrp
*
- * <em>produces:</em>
+ * Make a local space on <em>thgrp</em>. If <em>thgrp</em> already has its own
+ * local space, do nothing. If it has an inherited local space, make a copy as
+ * its own space.
+ * It is impossible to remove the local space. When a thread on <em>thgrp</em>
+ * makes a new ThreadGroup, the local space of <em>thgrp</em> is inherited.
+ */
+
+static VALUE
+thgroup_make_local_space_bang(VALUE group)
+{
+ struct thgroup *data;
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+
+ switch(data->local_space) {
+ case 0: /* none */
+ data->global_tbl = st_init_numtable();
+ data->class_tbl = st_init_numtable();
+ data->classlocal_tables = st_init_numtable();
+ data->local_space = 2;
+ break;
+
+ case 1: /* inherit */
+ if (data->global_tbl) {
+ data->global_tbl = st_copy(data->global_tbl);
+ } else {
+ data->global_tbl = st_init_numtable();
+ }
+ if (data->class_tbl) {
+ data->class_tbl = st_copy(data->class_tbl);
+ } else {
+ data->class_tbl = st_init_numtable();
+ }
+ if (data->classlocal_tables) {
+ data->classlocal_tables = st_copy(data->classlocal_tables);
+ st_foreach(data->classlocal_tables, thgroup_copy_classlocal_tables_i, (st_data_t)0);
+ } else {
+ data->classlocal_tables = st_init_numtable();
+ }
+ data->local_space = 2;
+ break;
+
+ case 2: /* own */
+ break;
+ }
+
+ return group;
+}
+
+int
+rb_thgroup_has_local_space(VALUE group)
+{
+ struct thgroup *data;
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ return data->local_space;
+}
+
+/*
+ * call-seq:
+ * thgrp.has_local_space? -> true or false
*
- * Initial group is #<Thread:0x401bdf4c>
- * t1 is #<Thread:0x401b3c90>
- * t2 is #<Thread:0x401b3c18>
- * Initial group now #<Thread:0x401b3c18>#<Thread:0x401bdf4c>
- * tg group now #<Thread:0x401b3c90>
+ * Returns <code>true</code> if <em>thgrp</em> has a local space.
+ * The local space may be inherited from the parent ThreadGroup, that is,
+ * the threadgroup of the thread which created <em>thgrp</em>.
*/
static VALUE
+thgroup_has_local_space_p(VALUE group)
+{
+ if (rb_thgroup_has_local_space(group)) {
+ return Qtrue;
+ }
+ return Qfalse;
+}
+
+
+static int
+thgroup_abort_on_exception(VALUE group)
+{
+ if (group) {
+ struct thgroup *data;
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ if (data->abort_on_exception) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * call-seq:
+ * thgrp.abort_on_exception -> true or false
+ *
+ * Returns the status of the threadgroup-local ``abort on exception''
+ * condition for <i>thgrp</i>. The default is <code>false</code>. See also
+ * <code>ThreadGroup::abort_on_exception=</code>.
+ */
+
+static VALUE
+thgroup_abort_exc_m(VALUE group)
+{
+ return thgroup_abort_on_exception(group) ? Qtrue : Qfalse;
+}
+
+
+/*
+ * call-seq:
+ * thgrp.abort_on_exception= boolean -> true or false
+ *
+ * When set to <code>true</code>, causes all threads (including the main
+ * program) to abort if an exception is raised in <i>thgrp</i>. The process
+ * will effectively <code>exit(0)</code>.
+ */
+
+static VALUE
+thgroup_abort_exc_set(VALUE group, VALUE val)
+{
+ struct thgroup *data;
+ rb_secure(4);
+
+ TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
+ data->abort_on_exception = RTEST(val);
+ return val;
+}
+
+
+static VALUE
thgroup_add(VALUE group, VALUE thread)
{
rb_thread_t *th;
@@ -3286,6 +4056,134 @@
/*
+ * call-seq:
+ * thgrp.add(thread [, thread...]) -> thgrp
+ *
+ * Adds the given each <em>thread</em> to this group, removing it from any
+ * other group to which it may have previously belonged.
+ *
+ * puts "Initial group is #{ThreadGroup::Default.list}"
+ * tg = ThreadGroup.new
+ * t1 = Thread.new { sleep }
+ * t2 = Thread.new { sleep }
+ * puts "t1 is #{t1}"
+ * puts "t2 is #{t2}"
+ * tg.add(t1)
+ * puts "Initial group now #{ThreadGroup::Default.list}"
+ * puts "tg group now #{tg.list}"
+ *
+ * <em>produces:</em>
+ *
+ * Initial group is #<Thread:0x401bdf4c>
+ * t1 is #<Thread:0x401b3c90>
+ * t2 is #<Thread:0x401b3c18>
+ * Initial group now #<Thread:0x401b3c18>#<Thread:0x401bdf4c>
+ * tg group now #<Thread:0x401b3c90>
+ */
+
+static VALUE
+thgroup_add_m(int argc, VALUE *argv, VALUE group)
+{
+ int i;
+
+ for (i=0; i<argc; i++) {
+ thgroup_add(group, argv[i]);
+ }
+ return group;
+}
+
+
+static VALUE
+thgroup_new_thread_core(int argc, VALUE *argv, VALUE klass, VALUE group)
+{
+ rb_thread_t *th;
+ VALUE thread = rb_thread_alloc(klass);
+ GetThreadPtr(thread, th);
+ th->thgroup = group;
+ rb_obj_call_init(thread, argc, argv);
+ if (!th->first_args) {
+ rb_raise(rb_eThreadError, "uninitialized thread - check `%s#initialize'",
+ rb_class2name(klass));
+ }
+ return thread;
+}
+
+/*
+ * call-seq:
+ * thgrp.new_thread([args]*) {|args| block } -> thread
+ *
+ * Creates new thread into <i>thgrp</i>. See also <code>Thread#new</code>.
+ */
+
+static VALUE
+thgroup_new_thread(int argc, VALUE *argv, VALUE group)
+{
+ return thgroup_new_thread_core(argc, argv, rb_cThread, group);
+}
+
+/*
+ * call-seq:
+ * thgrp.new_thread_of(klass, [args]*) {|args| block } -> thread
+ *
+ * Creates new thread of <i>klass</i> class into <i>thgrp</i>.
+ * See also <code>Thread#new</code>.
+ */
+
+static VALUE
+thgroup_new_thread_of(int argc, VALUE *argv, VALUE group)
+{
+ rb_thread_t *th;
+ VALUE klass;
+ VALUE thread;
+
+ if (argc == 0) {
+ rb_raise(rb_eArgError, "no Thread or its sub-class given");
+ }
+
+ klass = argv[0];
+ if (!RTEST(rb_class_inherited_p(klass, rb_cThread))) {
+ rb_raise(rb_eArgError, "expect Thread or its subclass for 1st argument");
+ }
+
+ return thgroup_new_thread_core(argc - 1, argv + 1, klass, group);
+}
+
+/*
+ * call-seq:
+ * thgrp.start_thread([args]*) {|args| block } -> thread
+ * thgrp.fork_thread([args]*) {|args| block } -> thread
+ *
+ * Creates new thread into <i>thgrp</i>. See also <code>Thread#start</code>.
+ */
+
+static VALUE
+thgroup_start_thread(VALUE group, VALUE args)
+{
+ return thread_create_into_thgroup(rb_thread_alloc(rb_cThread), group, args, 0);
+}
+
+/*
+ * call-seq:
+ * thgrp.start_thread_of(klass, [args]*) {|args| block } -> thread
+ * thgrp.fork_thread_of(klass, [args]*) {|args| block } -> thread
+ *
+ * Creates new thread of <i>klass</i> class into <i>thgrp</i>.
+ * See also <code>ThreadGroup#start_thread</code>.
+ */
+
+static VALUE
+thgroup_start_thread_of(VALUE group, VALUE args)
+{
+ VALUE klass;
+ klass = rb_ary_shift(args);
+ if (!rb_obj_is_kind_of(klass, rb_cThread)) {
+ rb_raise(rb_eArgError, "expect Thread or its subclass for 1st argument");
+ }
+ return thread_create_into_thgroup(rb_thread_alloc(klass), group, args, 0);
+}
+
+
+/*
* Document-class: Mutex
*
* Mutex implements a simple semaphore that can be used to coordinate access to
@@ -4644,10 +5542,35 @@
cThGroup = rb_define_class("ThreadGroup", rb_cObject);
rb_define_alloc_func(cThGroup, thgroup_s_alloc);
+ rb_define_singleton_method(cThGroup, "current", thgroup_s_current, 0);
+ rb_define_method(cThGroup, "initialize", thgroup_initialize, -1);
rb_define_method(cThGroup, "list", thgroup_list, 0);
+ rb_define_method(cThGroup, "threads", thgroup_list, 0);
+ rb_define_method(cThGroup, "size", thgroup_size, 0);
+ rb_define_method(cThGroup, "length", thgroup_size, 0);
rb_define_method(cThGroup, "enclose", thgroup_enclose, 0);
rb_define_method(cThGroup, "enclosed?", thgroup_enclosed_p, 0);
- rb_define_method(cThGroup, "add", thgroup_add, 1);
+ rb_define_method(cThGroup, "locked?", thgroup_locked_p, 0);
+ rb_define_method(cThGroup, "new_thread", thgroup_new_thread, -1);
+ rb_define_method(cThGroup, "new_thread_of", thgroup_new_thread_of, -1);
+ rb_define_method(cThGroup, "start_thread", thgroup_start_thread, -2);
+ rb_define_method(cThGroup, "fork_thread", thgroup_start_thread, -2);
+ rb_define_method(cThGroup, "start_thread_of", thgroup_start_thread_of, -2);
+ rb_define_method(cThGroup, "fork_thread_of", thgroup_start_thread_of, -2);
+ rb_define_method(cThGroup, "[]", rb_thgroup_aref, 1);
+ rb_define_method(cThGroup, "[]=", rb_thgroup_aset, 2);
+ rb_define_method(cThGroup, "key?", rb_thgroup_key_p, 1);
+ rb_define_method(cThGroup, "keys", rb_thgroup_keys, 0);
+ rb_define_method(cThGroup, "alive?", thgroup_alive_p, 0);
+ rb_define_method(cThGroup, "empty?", thgroup_empty_p, 0);
+ rb_define_method(cThGroup, "raise", thgroup_raise, -1);
+ rb_define_method(cThGroup, "wakeup", thgroup_wakeup, -1);
+ rb_define_method(cThGroup, "make_local_space!", thgroup_make_local_space_bang, 0);
+ rb_define_method(cThGroup, "has_local_space?", thgroup_has_local_space_p, 0);
+ rb_define_method(cThGroup, "abort_on_exception", thgroup_abort_exc_m, 0);
+ rb_define_method(cThGroup, "abort_on_exception=", thgroup_abort_exc_set, 1);
+ rb_define_method(cThGroup, "add", thgroup_add_m, -1);
+ rb_define_method(cThGroup, "<<", thgroup_add_m, 1);
{
th->thgroup = th->vm->thgroup_default = rb_obj_alloc(cThGroup);
Index: vm_method.c
===================================================================
--- vm_method.c (revision 32226)
+++ vm_method.c (working copy)
@@ -143,13 +143,18 @@
static int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2);
+int rb_thgroup_has_local_space(VALUE group);
+st_table *rb_thgroup_local_m_table_get(VALUE group, VALUE klass);
+st_table *rb_thgroup_local_m_table_create(VALUE group, VALUE klass);
+
static rb_method_entry_t *
rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type,
rb_method_definition_t *def, rb_method_flag_t noex)
{
rb_method_entry_t *me;
- st_table *mtbl;
+ st_table *mtbl = (st_table*)NULL;
st_data_t data;
+ VALUE thgrp;
if (NIL_P(klass)) {
klass = rb_cObject;
@@ -173,7 +178,13 @@
}
rb_check_frozen(klass);
- mtbl = RCLASS_M_TBL(klass);
+ thgrp = GET_THREAD()->thgroup;
+ if (thgrp && rb_thgroup_has_local_space(thgrp)) {
+ mtbl = rb_thgroup_local_m_table_create(thgrp, klass);
+ }
+ if (!mtbl) {
+ mtbl = RCLASS_M_TBL(klass);
+ }
/* check re-definition */
if (st_lookup(mtbl, mid, &data)) {
@@ -348,14 +359,32 @@
}
static rb_method_entry_t*
-search_method(VALUE klass, ID id)
+search_method_with_local_table(VALUE klass, ID id, int *is_local)
{
st_data_t body;
+ VALUE thgrp;
+ int use_local_tbl;
+ st_table *local_tbl;
+
if (!klass) {
return 0;
}
- while (!st_lookup(RCLASS_M_TBL(klass), id, &body)) {
+ thgrp = GET_THREAD()->thgroup;
+ use_local_tbl = (thgrp)? rb_thgroup_has_local_space(thgrp): 0;
+
+ while (1) {
+ if (use_local_tbl) {
+ local_tbl = rb_thgroup_local_m_table_get(thgrp, klass);
+ if (local_tbl && st_lookup(local_tbl, id, &body)) {
+ *is_local = 1;
+ break;
+ }
+ }
+ if (st_lookup(RCLASS_M_TBL(klass), id, &body)) {
+ *is_local = 0;
+ break;
+ }
klass = RCLASS_SUPER(klass);
if (!klass) {
return 0;
@@ -365,6 +394,13 @@
return (rb_method_entry_t *)body;
}
+static rb_method_entry_t*
+search_method(VALUE klass, ID id)
+{
+ int is_local;
+ return search_method_with_local_table(klass, id, &is_local);
+}
+
/*
* search method entry without the method cache.
*
@@ -374,9 +410,10 @@
rb_method_entry_t *
rb_method_entry_get_without_cache(VALUE klass, ID id)
{
- rb_method_entry_t *me = search_method(klass, id);
+ int is_local = 0;
+ rb_method_entry_t *me = search_method_with_local_table(klass, id, &is_local);
- if (ruby_running) {
+ if (ruby_running && !is_local) {
struct cache_entry *ent;
ent = cache + EXPR1(klass, id);
ent->filled_version = GET_VM_STATE_VERSION();
@@ -400,7 +437,13 @@
rb_method_entry(VALUE klass, ID id)
{
struct cache_entry *ent;
+ VALUE thgrp = GET_THREAD()->thgroup;
+ if (thgrp && rb_thgroup_has_local_space(thgrp)) {
+ /* has local space ==> don't use method cache */
+ return rb_method_entry_get_without_cache(klass, id);
+ }
+
ent = cache + EXPR1(klass, id);
if (ent->filled_version == GET_VM_STATE_VERSION() &&
ent->mid == id && ent->klass == klass) {