* thread.c: added Thread#thread_variable_(get|set),
Thread#thread_variable?, and Thread#thread_variables for operating on variables that are local to threads. [ruby-core:47790] * vm.c: ditto * test/ruby/test_thread.rb: tests for thread variables. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@37384 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
b9cba0711b
commit
2285319b31
10
ChangeLog
10
ChangeLog
@ -1,3 +1,13 @@
|
|||||||
|
Tue Oct 30 02:20:10 2012 Aaron Patterson <aaron@tenderlovemaking.com>
|
||||||
|
|
||||||
|
* thread.c: added Thread#thread_variable_(get|set),
|
||||||
|
Thread#thread_variable?, and Thread#thread_variables for operating
|
||||||
|
on variables that are local to threads. [ruby-core:47790]
|
||||||
|
|
||||||
|
* vm.c: ditto
|
||||||
|
|
||||||
|
* test/ruby/test_thread.rb: tests for thread variables.
|
||||||
|
|
||||||
Mon Oct 29 18:22:58 2012 Nobuyoshi Nakada <nobu@ruby-lang.org>
|
Mon Oct 29 18:22:58 2012 Nobuyoshi Nakada <nobu@ruby-lang.org>
|
||||||
|
|
||||||
* ext/stringio/stringio.c (strio_close): close separatedly per each
|
* ext/stringio/stringio.c (strio_close): close separatedly per each
|
||||||
|
10
NEWS
10
NEWS
@ -83,6 +83,16 @@ with all sufficient information, see the ChangeLog file.
|
|||||||
* added Struct#to_h returning values with keys corresponding to the
|
* added Struct#to_h returning values with keys corresponding to the
|
||||||
instance variable names.
|
instance variable names.
|
||||||
|
|
||||||
|
* Thread
|
||||||
|
* added method:
|
||||||
|
* added Thread#thread_variable_get for getting thread local variables
|
||||||
|
(these are different than Fiber local variables).
|
||||||
|
* added Thread#thread_variable_set for setting thread local variables.
|
||||||
|
* added Thread#thread_variables for getting a list of the thread local
|
||||||
|
variable keys.
|
||||||
|
* added Thread#thread_variable? for testing to see if a particular thread
|
||||||
|
variable has been set.
|
||||||
|
|
||||||
* Time
|
* Time
|
||||||
* change return value:
|
* change return value:
|
||||||
* Time#to_s returned encoding defaults to US-ASCII but automatically
|
* Time#to_s returned encoding defaults to US-ASCII but automatically
|
||||||
|
@ -27,6 +27,79 @@ class TestThread < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_main_thread_variable_in_enumerator
|
||||||
|
assert_equal Thread.main, Thread.current
|
||||||
|
|
||||||
|
Thread.current.thread_variable_set :foo, "bar"
|
||||||
|
|
||||||
|
thread, value = Fiber.new {
|
||||||
|
Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
|
||||||
|
}.resume
|
||||||
|
|
||||||
|
assert_equal Thread.current, thread
|
||||||
|
assert_equal Thread.current.thread_variable_get(:foo), value
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_thread_variable_in_enumerator
|
||||||
|
Thread.new {
|
||||||
|
Thread.current.thread_variable_set :foo, "bar"
|
||||||
|
|
||||||
|
thread, value = Fiber.new {
|
||||||
|
Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
|
||||||
|
}.resume
|
||||||
|
|
||||||
|
assert_equal Thread.current, thread
|
||||||
|
assert_equal Thread.current.thread_variable_get(:foo), value
|
||||||
|
}.join
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_thread_variables
|
||||||
|
assert_equal [], Thread.new { Thread.current.thread_variables }.join.value
|
||||||
|
|
||||||
|
t = Thread.new {
|
||||||
|
Thread.current.thread_variable_set(:foo, "bar")
|
||||||
|
Thread.current.thread_variables
|
||||||
|
}
|
||||||
|
assert_equal [:foo], t.join.value
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_thread_variable?
|
||||||
|
refute Thread.new { Thread.current.thread_variable?("foo") }.join.value
|
||||||
|
t = Thread.new {
|
||||||
|
Thread.current.thread_variable_set("foo", "bar")
|
||||||
|
}.join
|
||||||
|
|
||||||
|
assert t.thread_variable?("foo")
|
||||||
|
assert t.thread_variable?(:foo)
|
||||||
|
refute t.thread_variable?(:bar)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_thread_variable_strings_and_symbols_are_the_same_key
|
||||||
|
t = Thread.new {}.join
|
||||||
|
t.thread_variable_set("foo", "bar")
|
||||||
|
assert_equal "bar", t.thread_variable_get(:foo)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_thread_variable_frozen
|
||||||
|
t = Thread.new { }.join
|
||||||
|
t.freeze
|
||||||
|
assert_raises(RuntimeError) do
|
||||||
|
t.thread_variable_set(:foo, "bar")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_thread_variable_security
|
||||||
|
t = Thread.new { sleep }
|
||||||
|
|
||||||
|
assert_raises(SecurityError) do
|
||||||
|
Thread.new { $SAFE = 4; t.thread_variable_get(:foo) }.join
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_raises(SecurityError) do
|
||||||
|
Thread.new { $SAFE = 4; t.thread_variable_set(:foo, :baz) }.join
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_mutex_synchronize
|
def test_mutex_synchronize
|
||||||
m = Mutex.new
|
m = Mutex.new
|
||||||
r = 0
|
r = 0
|
||||||
|
155
thread.c
155
thread.c
@ -2525,6 +2525,9 @@ rb_thread_local_aref(VALUE thread, ID id)
|
|||||||
* #=> nil if fiber-local
|
* #=> nil if fiber-local
|
||||||
* #=> 2 if thread-local (The value 2 is leaked to outside of meth method.)
|
* #=> 2 if thread-local (The value 2 is leaked to outside of meth method.)
|
||||||
*
|
*
|
||||||
|
* For thread-local variables, please see <code>Thread#thread_local_get</code>
|
||||||
|
* and <code>Thread#thread_local_set</code>.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
@ -2561,7 +2564,9 @@ rb_thread_local_aset(VALUE thread, ID id, VALUE val)
|
|||||||
* thr[sym] = obj -> obj
|
* thr[sym] = obj -> obj
|
||||||
*
|
*
|
||||||
* Attribute Assignment---Sets or creates the value of a fiber-local variable,
|
* Attribute Assignment---Sets or creates the value of a fiber-local variable,
|
||||||
* using either a symbol or a string. See also <code>Thread#[]</code>.
|
* using either a symbol or a string. See also <code>Thread#[]</code>. For
|
||||||
|
* thread-local variables, please see <code>Thread#thread_variable_set</code>
|
||||||
|
* and <code>Thread#thread_variable_get</code>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
@ -2570,6 +2575,80 @@ rb_thread_aset(VALUE self, VALUE id, VALUE val)
|
|||||||
return rb_thread_local_aset(self, rb_to_id(id), val);
|
return rb_thread_local_aset(self, rb_to_id(id), val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* call-seq:
|
||||||
|
* thr.thread_variable_get(key) -> obj or nil
|
||||||
|
*
|
||||||
|
* Returns the value of a thread local variable that has been set. Note that
|
||||||
|
* these are different than fiber local values. For fiber local values,
|
||||||
|
* please see Thread#[] and Thread#[]=.
|
||||||
|
*
|
||||||
|
* Thread local values are carried along with threads, and do not respect
|
||||||
|
* fibers. For example:
|
||||||
|
*
|
||||||
|
* Thread.new {
|
||||||
|
* Thread.current.thread_variable_set("foo", "bar") # set a thread local
|
||||||
|
* Thread.current["foo"] = "bar" # set a fiber local
|
||||||
|
*
|
||||||
|
* Fiber.new {
|
||||||
|
* Fiber.yield [
|
||||||
|
* Thread.current.thread_variable_get("foo"), # get the thread local
|
||||||
|
* Thread.current["foo"], # get the fiber local
|
||||||
|
* ]
|
||||||
|
* }.resume
|
||||||
|
* }.join.value # => ['bar', nil]
|
||||||
|
*
|
||||||
|
* The value "bar" is returned for the thread local, where nil is returned
|
||||||
|
* for the fiber local. The fiber is executed in the same thread, so the
|
||||||
|
* thread local values are available.
|
||||||
|
*
|
||||||
|
* See also Thread#[]
|
||||||
|
*/
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
rb_thread_variable_get(VALUE thread, VALUE id)
|
||||||
|
{
|
||||||
|
VALUE locals;
|
||||||
|
rb_thread_t *th;
|
||||||
|
|
||||||
|
GetThreadPtr(thread, th);
|
||||||
|
|
||||||
|
if (rb_safe_level() >= 4 && th != GET_THREAD()) {
|
||||||
|
rb_raise(rb_eSecurityError, "Insecure: can't modify thread locals");
|
||||||
|
}
|
||||||
|
|
||||||
|
locals = rb_iv_get(thread, "locals");
|
||||||
|
return rb_hash_aref(locals, ID2SYM(rb_to_id(id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* call-seq:
|
||||||
|
* thr.thread_variable_set(key, value)
|
||||||
|
*
|
||||||
|
* Sets a thread local with +key+ to +value+. Note that these are local to
|
||||||
|
* threads, and not to fibers. Please see Thread#thread_variable_get and
|
||||||
|
* Thread#[] for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
rb_thread_variable_set(VALUE thread, VALUE id, VALUE val)
|
||||||
|
{
|
||||||
|
VALUE locals;
|
||||||
|
rb_thread_t *th;
|
||||||
|
|
||||||
|
GetThreadPtr(thread, th);
|
||||||
|
|
||||||
|
if (rb_safe_level() >= 4 && th != GET_THREAD()) {
|
||||||
|
rb_raise(rb_eSecurityError, "Insecure: can't modify thread locals");
|
||||||
|
}
|
||||||
|
if (OBJ_FROZEN(thread)) {
|
||||||
|
rb_error_frozen("thread locals");
|
||||||
|
}
|
||||||
|
|
||||||
|
locals = rb_iv_get(thread, "locals");
|
||||||
|
return rb_hash_aset(locals, ID2SYM(rb_to_id(id)), val);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* call-seq:
|
* call-seq:
|
||||||
* thr.key?(sym) -> true or false
|
* thr.key?(sym) -> true or false
|
||||||
@ -2651,6 +2730,76 @@ rb_thread_keys(VALUE self)
|
|||||||
return ary;
|
return ary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
keys_i(VALUE key, VALUE value, VALUE ary)
|
||||||
|
{
|
||||||
|
rb_ary_push(ary, key);
|
||||||
|
return ST_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* call-seq:
|
||||||
|
* thr.thread_variables -> array
|
||||||
|
*
|
||||||
|
* Returns an an array of the names of the thread-local variables (as Symbols).
|
||||||
|
*
|
||||||
|
* thr = Thread.new do
|
||||||
|
* Thread.current.thread_variable_set(:cat, 'meow')
|
||||||
|
* Thread.current.thread_variable_set("dog", 'woof')
|
||||||
|
* end
|
||||||
|
* thr.join #=> #<Thread:0x401b3f10 dead>
|
||||||
|
* thr.thread_variables #=> [:dog, :cat]
|
||||||
|
*
|
||||||
|
* Note that these are not fiber local variables. Please see Thread#[] and
|
||||||
|
* Thread#thread_variable_get for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
rb_thread_variables(VALUE thread)
|
||||||
|
{
|
||||||
|
VALUE locals;
|
||||||
|
VALUE ary;
|
||||||
|
|
||||||
|
locals = rb_iv_get(thread, "locals");
|
||||||
|
ary = rb_ary_new();
|
||||||
|
rb_hash_foreach(locals, keys_i, ary);
|
||||||
|
|
||||||
|
return ary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* call-seq:
|
||||||
|
* thr.thread_variable?(key) -> true or false
|
||||||
|
*
|
||||||
|
* Returns <code>true</code> if the given string (or symbol) exists as a
|
||||||
|
* thread-local variable.
|
||||||
|
*
|
||||||
|
* me = Thread.current
|
||||||
|
* me.thread_variable_set(:oliver, "a")
|
||||||
|
* me.thread_variable?(:oliver) #=> true
|
||||||
|
* me.thread_variable?(:stanley) #=> false
|
||||||
|
*
|
||||||
|
* Note that these are not fiber local variables. Please see Thread#[] and
|
||||||
|
* Thread#thread_variable_get for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
rb_thread_variable_p(VALUE thread, VALUE key)
|
||||||
|
{
|
||||||
|
VALUE locals;
|
||||||
|
|
||||||
|
locals = rb_iv_get(thread, "locals");
|
||||||
|
|
||||||
|
if (!RHASH(locals)->ntbl)
|
||||||
|
return Qfalse;
|
||||||
|
|
||||||
|
if (st_lookup(RHASH(locals)->ntbl, ID2SYM(rb_to_id(key)), 0)) {
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Qfalse;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* call-seq:
|
* call-seq:
|
||||||
* thr.priority -> integer
|
* thr.priority -> integer
|
||||||
@ -4541,6 +4690,10 @@ Init_Thread(void)
|
|||||||
rb_define_method(rb_cThread, "priority", rb_thread_priority, 0);
|
rb_define_method(rb_cThread, "priority", rb_thread_priority, 0);
|
||||||
rb_define_method(rb_cThread, "priority=", rb_thread_priority_set, 1);
|
rb_define_method(rb_cThread, "priority=", rb_thread_priority_set, 1);
|
||||||
rb_define_method(rb_cThread, "status", rb_thread_status, 0);
|
rb_define_method(rb_cThread, "status", rb_thread_status, 0);
|
||||||
|
rb_define_method(rb_cThread, "thread_variable_get", rb_thread_variable_get, 1);
|
||||||
|
rb_define_method(rb_cThread, "thread_variable_set", rb_thread_variable_set, 2);
|
||||||
|
rb_define_method(rb_cThread, "thread_variables", rb_thread_variables, 0);
|
||||||
|
rb_define_method(rb_cThread, "thread_variable?", rb_thread_variable_p, 1);
|
||||||
rb_define_method(rb_cThread, "alive?", rb_thread_alive_p, 0);
|
rb_define_method(rb_cThread, "alive?", rb_thread_alive_p, 0);
|
||||||
rb_define_method(rb_cThread, "stop?", rb_thread_stop_p, 0);
|
rb_define_method(rb_cThread, "stop?", rb_thread_stop_p, 0);
|
||||||
rb_define_method(rb_cThread, "abort_on_exception", rb_thread_abort_exc, 0);
|
rb_define_method(rb_cThread, "abort_on_exception", rb_thread_abort_exc, 0);
|
||||||
|
2
vm.c
2
vm.c
@ -1838,6 +1838,7 @@ ruby_thread_init(VALUE self)
|
|||||||
GetThreadPtr(self, th);
|
GetThreadPtr(self, th);
|
||||||
|
|
||||||
th_init(th, self);
|
th_init(th, self);
|
||||||
|
rb_iv_set(self, "locals", rb_hash_new());
|
||||||
th->vm = vm;
|
th->vm = vm;
|
||||||
|
|
||||||
th->top_wrapper = 0;
|
th->top_wrapper = 0;
|
||||||
@ -2178,6 +2179,7 @@ Init_VM(void)
|
|||||||
|
|
||||||
/* create main thread */
|
/* create main thread */
|
||||||
th_self = th->self = TypedData_Wrap_Struct(rb_cThread, &thread_data_type, th);
|
th_self = th->self = TypedData_Wrap_Struct(rb_cThread, &thread_data_type, th);
|
||||||
|
rb_iv_set(th_self, "locals", rb_hash_new());
|
||||||
vm->main_thread = th;
|
vm->main_thread = th;
|
||||||
vm->running_thread = th;
|
vm->running_thread = th;
|
||||||
th->vm = vm;
|
th->vm = vm;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user