From 2db2fb9f6c742d5bd0019ccd11c7a375e1b12c0b Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 26 Nov 2020 04:25:42 +0900 Subject: per-ractor Random::DEFAULT Random generators are not Ractor-safe, so we need to prepare per-ractor default random genearators. This patch set `Random::DEFAULT = Randm` (not a Random instance, but the Random class) and singleton methods like `Random.rand()` use a per-ractor random generator. [Feature #17322] --- ractor.c | 16 ++++++++ ractor_core.h | 2 + random.c | 69 ++++++++++++++++------------------- spec/ruby/core/random/default_spec.rb | 17 ++++++++- test/ruby/test_rand.rb | 12 ------ 5 files changed, 65 insertions(+), 51 deletions(-) diff --git a/ractor.c b/ractor.c index 491a1e3f71..a5f22a4724 100644 --- a/ractor.c +++ b/ractor.c @@ -222,6 +222,7 @@ ractor_free(void *ptr) rb_native_cond_destroy(&r->wait.cond); ractor_queue_free(&r->incoming_queue); ractor_waiting_list_free(&r->taking_ractors); + if (r->default_rand) ruby_xfree(r->default_rand); ruby_xfree(r); } @@ -1767,6 +1768,21 @@ rb_ractor_stderr_set(VALUE err) } } +void * +rb_ractor_default_rand(void *ptr) +{ + if (rb_ractor_main_p()) { + static void *default_rnd; + if (UNLIKELY(ptr != NULL)) default_rnd = ptr; + return default_rnd; + } + else { + rb_ractor_t *cr = GET_RACTOR(); + if (UNLIKELY(ptr != NULL)) cr->default_rand = ptr; + return cr->default_rand; + } +} + /// traverse function // 2: stop search diff --git a/ractor_core.h b/ractor_core.h index 07528cc69c..dd662141a0 100644 --- a/ractor_core.h +++ b/ractor_core.h @@ -127,6 +127,8 @@ struct rb_ractor_struct { VALUE verbose; VALUE debug; + void *default_rand; // used in random.c + // gc.c rb_objspace_reachable_objects_from struct gc_mark_func_data_struct { void *data; diff --git a/random.c b/random.c index d9a347e426..22c54492e6 100644 --- a/random.c +++ b/random.c @@ -120,8 +120,6 @@ typedef struct { #define DEFAULT_SEED_CNT 4 -static rb_random_mt_t default_rand; - static VALUE rand_init(const rb_random_interface_t *, rb_random_t *, VALUE); static VALUE random_seed(VALUE); static void fill_random_seed(uint32_t *seed, size_t cnt); @@ -148,10 +146,22 @@ rand_start(rb_random_mt_t *r) return &rand_mt_start(r)->base; } +static rb_random_mt_t * +default_rand(void) +{ + void *rb_ractor_default_rand(void *); // ractor.c + rb_random_mt_t *rnd = (rb_random_mt_t *)rb_ractor_default_rand(NULL); + if (rnd == NULL) { + rnd = ZALLOC(rb_random_mt_t); + rb_ractor_default_rand(rnd); + } + return rnd; +} + static rb_random_mt_t * default_mt(void) { - return rand_mt_start(&default_rand); + return rand_mt_start(default_rand()); } unsigned int @@ -230,7 +240,7 @@ const rb_data_type_t rb_random_data_type = { static void random_mt_free(void *ptr) { - if (ptr != &default_rand) + if (ptr != default_rand()) xfree(ptr); } @@ -274,7 +284,7 @@ static rb_random_t * try_get_rnd(VALUE obj) { if (obj == rb_cRandom) { - return rand_start(&default_rand); + return rand_start(default_rand()); } if (!rb_typeddata_is_kind_of(obj, &rb_random_data_type)) return NULL; if (RTYPEDDATA_TYPE(obj) == &random_mt_type) @@ -290,7 +300,7 @@ try_get_rnd(VALUE obj) static const rb_random_interface_t * try_rand_if(VALUE obj, rb_random_t *rnd) { - if (rnd == &default_rand.base) { + if (rnd == &default_rand()->base) { return &random_mt_if; } return rb_rand_if(obj); @@ -709,7 +719,7 @@ rand_mt_state(VALUE obj) static VALUE random_s_state(VALUE klass) { - return mt_state(&default_rand.mt); + return mt_state(&default_rand()->mt); } /* :nodoc: */ @@ -724,7 +734,7 @@ rand_mt_left(VALUE obj) static VALUE random_s_left(VALUE klass) { - return INT2FIX(default_rand.mt.left); + return INT2FIX(default_rand()->mt.left); } /* :nodoc: */ @@ -829,7 +839,7 @@ static VALUE rb_f_srand(int argc, VALUE *argv, VALUE obj) { VALUE seed, old; - rb_random_mt_t *r = &default_rand; + rb_random_mt_t *r = rand_mt_start(default_rand()); if (rb_check_arity(argc, 0, 1) == 0) { seed = random_seed(obj); @@ -1191,10 +1201,17 @@ rb_random_bytes(VALUE obj, long n) static VALUE random_s_bytes(VALUE obj, VALUE len) { - rb_random_t *rnd = rand_start(&default_rand); + rb_random_t *rnd = rand_start(default_rand()); return rand_bytes(&random_mt_if, rnd, NUM2LONG(rb_to_int(len))); } +static VALUE +random_s_seed(VALUE obj) +{ + rb_random_mt_t *rnd = rand_mt_start(default_rand()); + return rnd->base.seed; +} + static VALUE range_values(VALUE vmax, VALUE *begp, VALUE *endp, int *exclp) { @@ -1518,7 +1535,7 @@ static VALUE rb_f_rand(int argc, VALUE *argv, VALUE obj) { VALUE vmax; - rb_random_t *rnd = rand_start(&default_rand); + rb_random_t *rnd = rand_start(default_rand()); if (rb_check_arity(argc, 0, 1) && !NIL_P(vmax = argv[0])) { VALUE v = rand_range(obj, rnd, vmax); @@ -1543,7 +1560,7 @@ rb_f_rand(int argc, VALUE *argv, VALUE obj) static VALUE random_s_rand(int argc, VALUE *argv, VALUE obj) { - VALUE v = rand_random(argc, argv, Qnil, rand_start(&default_rand)); + VALUE v = rand_random(argc, argv, Qnil, rand_start(default_rand())); check_random_number(v, argv); return v; } @@ -1626,27 +1643,10 @@ Init_RandomSeedCore(void) explicit_bzero(&mt, sizeof(mt)); } -/* construct Random::DEFAULT bits */ -static VALUE -Init_Random_default(VALUE klass) -{ - rb_random_mt_t *r = &default_rand; - struct MT *mt = &r->mt; - VALUE v = TypedData_Wrap_Struct(klass, &random_mt_type, r); - - rb_gc_register_mark_object(v); - with_random_seed(DEFAULT_SEED_CNT, 1) { - init_by_array(mt, seedbuf, DEFAULT_SEED_CNT); - r->base.seed = make_seed_value(seedbuf, DEFAULT_SEED_CNT); - } - - return v; -} - void rb_reset_random_seed(void) { - rb_random_mt_t *r = &default_rand; + rb_random_mt_t *r = default_rand(); uninit_genrand(&r->mt); r->base.seed = INT2FIX(0); } @@ -1701,17 +1701,12 @@ InitVM_Random(void) rb_define_private_method(rb_cRandom, "left", rand_mt_left, 0); rb_define_method(rb_cRandom, "==", rand_mt_equal, 1); - { - /* Direct access to Ruby's Pseudorandom number generator (PRNG). */ - VALUE rand_default = Init_Random_default(rb_cRandom); - /* The default Pseudorandom number generator. Used by class - * methods of Random. */ - rb_define_const(rb_cRandom, "DEFAULT", rand_default); - } + rb_define_const(rb_cRandom, "DEFAULT", rb_cRandom); rb_define_singleton_method(rb_cRandom, "srand", rb_f_srand, -1); rb_define_singleton_method(rb_cRandom, "rand", random_s_rand, -1); rb_define_singleton_method(rb_cRandom, "bytes", random_s_bytes, 1); + rb_define_singleton_method(rb_cRandom, "seed", random_s_seed, 0); rb_define_singleton_method(rb_cRandom, "new_seed", random_seed, 0); rb_define_singleton_method(rb_cRandom, "urandom", random_raw_seed, 1); rb_define_private_method(CLASS_OF(rb_cRandom), "state", random_s_state, 0); diff --git a/spec/ruby/core/random/default_spec.rb b/spec/ruby/core/random/default_spec.rb index 1755154294..0333abed6b 100644 --- a/spec/ruby/core/random/default_spec.rb +++ b/spec/ruby/core/random/default_spec.rb @@ -1,8 +1,21 @@ require_relative '../../spec_helper' describe "Random::DEFAULT" do - it "returns a Random instance" do - Random::DEFAULT.should be_an_instance_of(Random) + + it "returns a random number generator" do + Random::DEFAULT.should respond_to(:rand) + end + + ruby_version_is ''...'3.0' do + it "returns a Random instance" do + Random::DEFAULT.should be_an_instance_of(Random) + end + end + + ruby_version_is '3.0' do + it "returns a Random instance" do + Random::DEFAULT.should be_an_instance_of(Class) + end end it "changes seed on reboot" do diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb index 2fcac2d7d0..af10a27ce0 100644 --- a/test/ruby/test_rand.rb +++ b/test/ruby/test_rand.rb @@ -336,18 +336,6 @@ class TestRand < Test::Unit::TestCase } end - def test_default - r1 = Random::DEFAULT.dup - r2 = Random::DEFAULT.dup - 3.times do - x0 = rand - x1 = r1.rand - x2 = r2.rand - assert_equal(x0, x1) - assert_equal(x0, x2) - end - end - def test_marshal bug3656 = '[ruby-core:31622]' assert_raise(TypeError, bug3656) { -- cgit v1.2.3