diff --git a/file.c b/file.c index 64f5965aef53bd..41f3c372cef995 100644 --- a/file.c +++ b/file.c @@ -2879,15 +2879,27 @@ chmod_internal(const char *path, void *mode) /* * call-seq: - * File.chmod(mode_int, file_name, ... ) -> integer + * File.chmod(mode, *paths) -> integer * - * Changes permission bits on the named file(s) to the bit pattern - * represented by mode_int. Actual effects are operating system - * dependent (see the beginning of this section). On Unix systems, see - * chmod(2) for details. Returns the number of files - * processed. + * Changes the mode (i.e., permissions) of the entries of each the given +paths+; + * see {File Permissions}[rdoc-ref:File@File+Permissions]. + * Returns the count of the given +paths+: + * + * filepath = 't.tmp' + * File.write(filepath, 'foo') + * dirpath = 'tempdir' + * Dir.mkdir(dirpath) + * File::Stat.new(filepath).mode.to_s(8) # => "100664" + * File::Stat.new(dirpath).mode.to_s(8) # => "40775" + * File.chmod(0775, filepath, dirpath) # => 2 + * File::Stat.new(filepath).mode.to_s(8) # => "100775" + * File::Stat.new(dirpath).mode.to_s(8) # => "40775" + * File.chmod(0664, filepath, dirpath) # => 2 + * File::Stat.new(filepath).mode.to_s(8) # => "100664" + * File::Stat.new(dirpath).mode.to_s(8) # => "40664" + * File.delete(filepath) + * Dir.rmdir(dirpath) * - * File.chmod(0644, "testfile", "out") #=> 2 */ static VALUE @@ -3047,16 +3059,46 @@ chown_internal(const char *path, void *arg) /* * call-seq: - * File.chown(owner_int, group_int, file_name, ...) -> integer + * File.chown(owner_int, group_int, *paths) -> integer + * + * Changes the owner and group of the entry at each of the given +paths+; + * returns the count of the given +paths+: + * + * # Super user; all privileges. + * Process.uid => 0 + * Process.gid => 0 + * # Create a directory and a file. + * dirpath = 'doc/foo' + * Dir.mkdir(dirpath) + * filepath = 't.tmp' + * File.write(filepath, 'foo') + * # Get their user and group ids. + * dirstat = File::Stat.new(dirpath) + * dirstat.uid => 0 + * dirstat.gid => 0 + * filestat = File::Stat.new(filepath) + * filestat.uid => 0 + * filestat.gid => 0 + * # Change ownership of both. + * File.chown(1000, 1000, filepath, dirpath) => 2 + * dirstat = File::Stat.new(dirpath) + * dirstat.uid => 1000 + * dirstat.gid => 1000 + * filestat = File::Stat.new(filepath) + * filestat.uid => 1000 + * filestat.gid => 1000 + * # Clean up. + * Dir.rmdir(dirpath) + * File.delete(filepath) * - * Changes the owner and group of the named file(s) to the given - * numeric owner and group id's. Only a process with superuser - * privileges may change the owner of a file. The current owner of a - * file may change the file's group to any group to which the owner - * belongs. A nil or -1 owner or group id is ignored. - * Returns the number of files processed. + * Notes: * - * File.chown(nil, 100, "testfile") + * - On Windows, the owner and group are not changed. + * - Only a process with superuser privileges can change the owner of an entry. + * - The owner of an entry can change its group to any group + * to which the owner belongs. + * - A +nil+ or +-1+ owner or group id is ignored. + * - The method follows symbolic links to the target entry. * */ diff --git a/proc.c b/proc.c index 586d02139c4215..aeaf6de448027f 100644 --- a/proc.c +++ b/proc.c @@ -2210,6 +2210,20 @@ rb_proc_parameters(int argc, VALUE *argv, VALUE self) return rb_iseq_parameters(iseq, is_proc); } +static st_index_t +iseq_location_hash(st_index_t hash, const rb_iseq_t *iseq) +{ + const struct rb_iseq_constant_body *body = ISEQ_BODY(iseq); + if (body) { + const rb_iseq_location_t *loc = &body->location; + hash = rb_st_hash_uint(hash, (st_index_t)loc->code_location.beg_pos.lineno); + hash = rb_st_hash_uint(hash, (st_index_t)loc->code_location.beg_pos.column); + hash = rb_st_hash_uint(hash, (st_index_t)loc->code_location.end_pos.lineno); + hash = rb_st_hash_uint(hash, (st_index_t)loc->code_location.end_pos.column); + } + return hash; +} + st_index_t rb_hash_proc(st_index_t hash, VALUE prc) { @@ -2224,13 +2238,13 @@ rb_hash_proc(st_index_t hash, VALUE prc) VALUE recipe = rb_proc_refinements_recipe(prc); long len = RARRAY_LEN(recipe); hash = rb_st_hash_uint(hash, (st_index_t)RARRAY_AREF(recipe, REFINEMENT_RECIPE_BASE_CREF)); - hash = rb_st_hash_uint(hash, (st_index_t)((const rb_iseq_t *)RARRAY_AREF(recipe, REFINEMENT_RECIPE_SRC_ISEQ))->body); + hash = iseq_location_hash(hash, (const rb_iseq_t *)RARRAY_AREF(recipe, REFINEMENT_RECIPE_SRC_ISEQ)); for (long i = REFINEMENT_RECIPE_MODS; i < len; i++) { hash = rb_st_hash_uint(hash, (st_index_t)RARRAY_AREF(recipe, i)); } } else { - hash = rb_st_hash_uint(hash, (st_index_t)ISEQ_BODY(proc->block.as.captured.code.iseq)); + hash = iseq_location_hash(hash, proc->block.as.captured.code.iseq); } break; case block_type_ifunc: diff --git a/thread_pthread.c b/thread_pthread.c index 44b02032a6fa69..b17bbbc3f3ea06 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -1482,7 +1482,7 @@ ractor_sched_enq(rb_vm_t *vm, rb_ractor_t *r) // With every snt dedicated or retired, only the timer thread's // timeout branch can serve the entry or widen the pool: wake it // (a no-op unless it sleeps untimed). - if (vm->ractor.sched.snt_cnt == 0) { + if (RUBY_ATOMIC_LOAD(vm->ractor.sched.snt_cnt) == 0) { timer_thread_wakeup_locked(vm); } @@ -1528,8 +1528,8 @@ ractor_sched_deq(rb_vm_t *vm, rb_ractor_t *cr) RUBY_DEBUG_LOG("wait grq_cnt:%d", (int)vm->ractor.sched.grq_cnt); if (SNT_IDLE_RETIRE >= 0 && ++idle_streak > SNT_IDLE_RETIRE && - (int)vm->ractor.sched.snt_cnt > SNT_KEEP_MINIMUM) { - vm->ractor.sched.snt_cnt--; + (int)RUBY_ATOMIC_LOAD(vm->ractor.sched.snt_cnt) > SNT_KEEP_MINIMUM) { + RUBY_ATOMIC_DEC(vm->ractor.sched.snt_cnt); RUBY_DEBUG_LOG("retire, snt_cnt:%d", (int)vm->ractor.sched.snt_cnt); break; // returning NULL ends this nt; see the caller } @@ -1862,11 +1862,15 @@ thread_sched_atfork(struct rb_thread_sched *sched) if (th_has_dedicated_nt(th)) { vm->ractor.sched.snt_cnt = 0; +#if USE_RUBY_DEBUG_LOG vm->ractor.sched.dnt_cnt = 1; +#endif } else { vm->ractor.sched.snt_cnt = 1; +#if USE_RUBY_DEBUG_LOG vm->ractor.sched.dnt_cnt = 0; +#endif } vm->ractor.sched.running_cnt = 0; @@ -2029,7 +2033,9 @@ Init_native_thread(rb_thread_t *main_th) main_th->nt->vm = vm; // setup mn +#if USE_RUBY_DEBUG_LOG vm->ractor.sched.dnt_cnt = 1; +#endif } extern int ruby_mn_threads_enabled; @@ -2074,18 +2080,21 @@ native_thread_dedicated_inc(rb_vm_t *vm, rb_ractor_t *cr, struct rb_native_threa RUBY_DEBUG_LOG("nt:%d %d->%d", nt->serial, nt->dedicated, nt->dedicated + 1); if (nt->dedicated == 0) { - ractor_sched_lock(vm, cr); - { - vm->ractor.sched.snt_cnt--; - vm->ractor.sched.dnt_cnt++; - - // This may have dedicated the last snt away from a pending - // entry whose enqueue saw snt_cnt > 0 (see ractor_sched_enq). - if (vm->ractor.sched.snt_cnt == 0 && vm->ractor.sched.grq_cnt > 0) { - timer_thread_wakeup_locked(vm); + // Lock-free; pairs with ractor_sched_enq (enq: grq_cnt up then read + // snt_cnt / here: snt_cnt down then read grq_cnt) against lost wakeups. + if (RUBY_ATOMIC_FETCH_SUB(vm->ractor.sched.snt_cnt, 1) == 1) { + // the last snt went dedicated; pending entries need the timer thread + ractor_sched_lock(vm, cr); + { + if (vm->ractor.sched.grq_cnt > 0) { + timer_thread_wakeup_locked(vm); + } } + ractor_sched_unlock(vm, cr); } - ractor_sched_unlock(vm, cr); +#if USE_RUBY_DEBUG_LOG + vm->ractor.sched.dnt_cnt++; +#endif } nt->dedicated++; @@ -2099,21 +2108,21 @@ native_thread_dedicated_dec(rb_vm_t *vm, rb_ractor_t *cr, struct rb_native_threa nt->dedicated--; if (nt->dedicated == 0) { - ractor_sched_lock(vm, cr); - { - /* max_cpu bounds the shared threads and this is where one rejoins - * them, so this is where the cap has to hold. A thread with no room - * to come back to belongs to neither count until it ends. */ - if (vm->ractor.sched.snt_cnt < vm->ractor.sched.max_cpu || - (int)vm->ractor.sched.snt_cnt <= MINIMUM_SNT) { - vm->ractor.sched.snt_cnt++; + // Rejoin under the max_cpu cap; with no room this nt retires and + // belongs to neither count until it ends. + while (1) { + rb_atomic_t snt = RUBY_ATOMIC_LOAD(vm->ractor.sched.snt_cnt); + if (snt < vm->ractor.sched.max_cpu || (int)snt <= MINIMUM_SNT) { + if (RUBY_ATOMIC_CAS(vm->ractor.sched.snt_cnt, snt, snt + 1) == snt) break; } else { nt->retiring = true; + break; } - vm->ractor.sched.dnt_cnt--; } - ractor_sched_unlock(vm, cr); +#if USE_RUBY_DEBUG_LOG + vm->ractor.sched.dnt_cnt--; +#endif } } diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index 97674d466d51f2..4226a39cb045db 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -824,19 +824,25 @@ native_thread_check_and_create_shared(rb_vm_t *vm) if (!vm->ractor.main_ractor->threads.sched.enable_mn_threads) schedulable_ractor_cnt--; // do not need snt for main ractor - unsigned int snt_cnt = vm->ractor.sched.snt_cnt; - if (((int)snt_cnt < MINIMUM_SNT) || - (snt_cnt < schedulable_ractor_cnt && - snt_cnt < vm->ractor.sched.max_cpu)) { + // CAS keeps a concurrent rejoin from pushing snt_cnt past the cap + rb_atomic_t snt_cnt = RUBY_ATOMIC_LOAD(vm->ractor.sched.snt_cnt); + while (((int)snt_cnt < MINIMUM_SNT) || + (snt_cnt < schedulable_ractor_cnt && + snt_cnt < vm->ractor.sched.max_cpu)) { + rb_atomic_t prev = RUBY_ATOMIC_CAS(vm->ractor.sched.snt_cnt, snt_cnt, snt_cnt + 1); + if (prev == snt_cnt) { + need_to_make = true; + break; + } + snt_cnt = prev; + } + if (need_to_make) { RUBY_DEBUG_LOG("added snt:%u dnt:%u ractor_cnt:%u grq_cnt:%u", vm->ractor.sched.snt_cnt, vm->ractor.sched.dnt_cnt, vm->ractor.cnt, vm->ractor.sched.grq_cnt); - - vm->ractor.sched.snt_cnt++; - need_to_make = true; } else { RUBY_DEBUG_LOG("snt:%d ractor_cnt:%d", (int)vm->ractor.sched.snt_cnt, (int)vm->ractor.cnt); @@ -852,7 +858,7 @@ native_thread_check_and_create_shared(rb_vm_t *vm) // Roll back, or this function would conclude forever that the // pool is wide enough and never try again. ractor_sched_lock(vm, NULL); - vm->ractor.sched.snt_cnt--; + RUBY_ATOMIC_DEC(vm->ractor.sched.snt_cnt); ractor_sched_unlock(vm, NULL); native_thread_destroy(nt); } diff --git a/vm_core.h b/vm_core.h index b9a57e210bd3a1..35c2d69512317f 100644 --- a/vm_core.h +++ b/vm_core.h @@ -749,8 +749,8 @@ typedef struct rb_vm_struct { bool locked; rb_nativethread_cond_t cond; // GRQ - unsigned int snt_cnt; // count of shared NTs - unsigned int dnt_cnt; // count of dedicated NTs + rb_atomic_t snt_cnt; // count of shared NTs; lock-free (see native_thread_dedicated_inc) + unsigned int dnt_cnt; // count of dedicated NTs; logging only (USE_RUBY_DEBUG_LOG), not atomic unsigned int running_cnt;