Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 57 additions & 15 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <i>mode_int</i>. Actual effects are operating system
* dependent (see the beginning of this section). On Unix systems, see
* <code>chmod(2)</code> 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
Expand Down Expand Up @@ -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 <code>nil</code> 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.
*
*/

Expand Down
18 changes: 16 additions & 2 deletions proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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:
Expand Down
55 changes: 32 additions & 23 deletions thread_pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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++;
Expand All @@ -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
}
}

Expand Down
22 changes: 14 additions & 8 deletions thread_pthread_mn.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions vm_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down