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
46 changes: 46 additions & 0 deletions coroutine/amd64/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ enum {COROUTINE_REGISTERS = 6};
#include <sanitizer/asan_interface.h>
#endif

#if defined(__SANITIZE_THREAD__)
#define COROUTINE_SANITIZE_THREAD
#elif defined(__has_feature)
#if __has_feature(thread_sanitizer)
#define COROUTINE_SANITIZE_THREAD
#endif
#endif

#if defined(COROUTINE_SANITIZE_THREAD)
/* ThreadSanitizer cannot follow a userspace stack switch on its own: its
* per-OS-thread shadow stack must be handed to the destination fiber on every
* coroutine switch via the fiber API, otherwise it leaks the shadow stack
* across switches and eventually faults inside libtsan. */
#include <sanitizer/tsan_interface.h>
#endif

struct coroutine_context
{
void **stack_pointer;
Expand All @@ -42,12 +58,26 @@ struct coroutine_context
void *stack_base;
size_t stack_size;
#endif

#if defined(COROUTINE_SANITIZE_THREAD)
void *tsan_fiber;
/* Whether we created tsan_fiber (via __tsan_create_fiber, must be
* destroyed) or borrowed it from __tsan_get_current_fiber (the OS thread's
* implicit fiber, owned by TSan; must not be destroyed). */
int tsan_fiber_owned;
#endif
};

typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);

static inline void coroutine_initialize_main(struct coroutine_context * context) {
context->stack_pointer = NULL;

#if defined(COROUTINE_SANITIZE_THREAD)
/* The OS thread's implicit (already running) fiber, owned by TSan. */
context->tsan_fiber = __tsan_get_current_fiber();
context->tsan_fiber_owned = 0;
#endif
}

static inline void coroutine_initialize(
Expand All @@ -64,6 +94,11 @@ static inline void coroutine_initialize(
context->stack_size = size;
#endif

#if defined(COROUTINE_SANITIZE_THREAD)
context->tsan_fiber = __tsan_create_fiber(0);
context->tsan_fiber_owned = 1;
#endif

// Stack grows down. Force 16-byte alignment.
char * top = (char*)stack + size;
context->stack_pointer = (void**)((uintptr_t)top & ~0xF);
Expand All @@ -80,6 +115,17 @@ struct coroutine_context * coroutine_transfer(struct coroutine_context * current
static inline void coroutine_destroy(struct coroutine_context * context)
{
context->stack_pointer = NULL;

#if defined(COROUTINE_SANITIZE_THREAD)
/* Only destroy fibers we created. The borrowed __tsan_get_current_fiber()
* handle (the OS thread's implicit fiber) is owned by TSan; destroying it
* aborts libtsan (FiberDestroy -> ProcWire CheckFailed). */
if (context->tsan_fiber && context->tsan_fiber_owned) {
__tsan_destroy_fiber(context->tsan_fiber);
context->tsan_fiber = NULL;
context->tsan_fiber_owned = 0;
}
#endif
}

#endif /* COROUTINE_AMD64_CONTEXT_H */
7 changes: 7 additions & 0 deletions ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ def self.new(*args, name: nil, &block)
#
# Ractor.current #=> #<Ractor:#1 running>
def self.current
Primitive.attr! :leaf
__builtin_cexpr! %q{
rb_ractor_self(rb_ec_ractor_ptr(ec));
}
Expand All @@ -256,6 +257,7 @@ def self.current
# r.join # wait for r's termination
# Ractor.count #=> 1
def self.count
Primitive.attr! :leaf
__builtin_cexpr! %q{
ULONG2NUM(GET_VM()->ractor.cnt);
}
Expand Down Expand Up @@ -385,6 +387,7 @@ def inspect

# Returns the name set in Ractor.new, or +nil+.
def name
Primitive.attr! :leaf
__builtin_cexpr! %q{RACTOR_PTR(self)->name}
end

Expand Down Expand Up @@ -517,13 +520,15 @@ def self.store_if_absent(sym)

# Returns the main ractor.
def self.main
Primitive.attr! :leaf
__builtin_cexpr! %q{
rb_ractor_self(GET_VM()->ractor.main_ractor);
}
end

# Returns true if the current ractor is the main ractor.
def self.main?
Primitive.attr! :leaf
__builtin_cexpr! %q{
RBOOL(GET_VM()->ractor.main_ractor == rb_ec_ractor_ptr(ec))
}
Expand Down Expand Up @@ -564,6 +569,7 @@ def require feature # :nodoc: -- otherwise RDoc outputs it as a class method
# Returns the default port of the Ractor.
#
def default_port
Primitive.attr! :leaf
__builtin_cexpr! %q{
ractor_default_port_value(RACTOR_PTR(self))
}
Expand Down Expand Up @@ -819,6 +825,7 @@ def close
#
# Returns whether or not the port is closed.
def closed?
Primitive.attr! :leaf
__builtin_cexpr! %q{
ractor_port_closed_p(ec, self);
}
Expand Down
60 changes: 40 additions & 20 deletions set.c
Original file line number Diff line number Diff line change
Expand Up @@ -677,10 +677,31 @@ set_i_to_a(VALUE set)

/*
* call-seq:
* to_set(&block) -> self or new_set
* to_set {|element| ... } -> new_set
* to_set -> self or new_set
*
* Without a block, if +self+ is an instance of +Set+, returns +self+.
* Otherwise, calls <tt>Set.new(self, &block)</tt>.
* With a block given, creates and returns a new set;
* calls the block with each element of +self+,
* and adds the block's returns value to the new set:
*
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* set.to_set {|i| i * 2 } # => Set[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
*
* With no block given, when +self+ is an instance of +Set+,
* returns +self+:
*
* set = Set[*0..9]
* set.to_set
* set.to_set.equal?(set) # => true
*
* With no block given, when +self+ is an instance of a subclass of +Set+,
* returns a \Set object containing the elements of +self+:
*
* class MySet < Set; end
* my_set = MySet[*0..9] # => #<MySet: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}>
* set = my_set.to_set # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
*
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
*/
static VALUE
set_i_to_set(VALUE set)
Expand Down Expand Up @@ -1373,7 +1394,11 @@ set_i_compare_by_identity_p(VALUE set)
* call-seq:
* size -> integer
*
* Returns the number of elements.
* Returns the number of elements in +self+:
*
* Set[*0..9].size # => 10
*
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_size(VALUE set)
Expand Down Expand Up @@ -2394,17 +2419,13 @@ rb_set_size(VALUE set)
*
* === Methods for \Set Operations
*
* - #| (aliased as #union and #+):
* Returns a new set containing all elements from +self+
* and all elements from a given enumerable (no duplicates).
* - #& (aliased as #intersection):
* Returns a new set containing all elements common to +self+
* and a given enumerable.
* Returns a new set containing the intersection of +self+ and the given enumerable.
* - #- (aliased as #difference):
* Returns a copy of +self+ with all elements
* in a given enumerable removed.
* - #^: Returns a new set containing all elements from +self+
* and a given enumerable except those common to both.
* Returns a new set containing the difference of +self+ and the given enumerable.
* - #^: Returns a new set containing the exclusive OR of +self+ and the given enumerable.
* - #| (aliased as #union and #+):
* Returns a new set containing the union of +self+ and the given enumerable.
*
* === Methods for Comparing
*
Expand All @@ -2418,7 +2439,7 @@ rb_set_size(VALUE set)
* - #compare_by_identity?:
* Returns whether the set considers only identity
* when comparing elements.
* - #length (aliased as #size):
* - #size (aliased as #length):
* Returns the count of elements.
* - #empty?:
* Returns whether the set has no elements.
Expand All @@ -2442,15 +2463,14 @@ rb_set_size(VALUE set)
* === Methods for Assigning
*
* - #add (aliased as #<<):
* Adds a given object to the set; returns +self+.
* Adds the given object to +self+, returns +self+.
* - #add?:
* If the given object is not an element in the set,
* adds it and returns +self+; otherwise, returns +nil+.
* Like #add, but returns +nil+ if the given object is already in +self+.
* - #merge:
* Merges the elements of each given enumerable object to the set; returns +self+.
* Adds the elements of the given enumerables to +self+; returns +self+.
* - #replace:
* Replaces the contents of the set with the contents
* of a given enumerable.
* Replaces the contents of +self+ with the contents of the given enumerable;
* returns +self+.
*
* === Methods for Deleting
*
Expand Down
7 changes: 7 additions & 0 deletions thread_pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,13 @@ coroutine_transfer0(struct coroutine_context *transfer_from, struct coroutine_co
__sanitizer_start_switch_fiber(fake_stack, transfer_to->stack_base, transfer_to->stack_size);
#endif

#if defined(COROUTINE_SANITIZE_THREAD)
/* Tell TSan we are switching to transfer_to's fiber before the stack
* switch, so its per-thread shadow stack stays bound to the right
* coroutine. */
__tsan_switch_to_fiber(transfer_to->tsan_fiber, 0);
#endif

RBIMPL_ATTR_MAYBE_UNUSED()
struct coroutine_context *returning_from = coroutine_transfer(transfer_from, transfer_to);

Expand Down
13 changes: 13 additions & 0 deletions tool/lib/test/unit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,19 @@ def _run_suite suite, type

leakchecker.check("#{inst.class}\##{inst.__name__}")

# Optionally verify GC internal consistency after each test. An
# integer >= 2 samples once every N tests (use a prime so a
# randomized test order samples a different set each run); any other
# non-empty value verifies every test (O(heap) per test, so only
# practical for small runs).
if (interval = ENV["RUBY_TEST_GC_VERIFY"])
n = interval.to_i
@__gc_verify_tick = (@__gc_verify_tick || 0) + 1
if n <= 1 || (@__gc_verify_tick % n).zero?
GC.verify_internal_consistency
end
end

_end_method(inst)

inst._assertions
Expand Down