Created
January 29, 2025 17:27
-
-
Save tenderlove/bf1c5c15c5b3d8f28f0ae6e4e8b36513 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "set" | |
require "rbconfig" | |
class Wrapper | |
def initialize | |
@a = Set.new | |
@b = Set.new | |
end | |
def write_a | |
# We would like a warning here | |
@a << "a" | |
end | |
def write_b | |
# We would like a warning here as well | |
@b << "b" | |
end | |
end | |
# No warnings are emitted until the warning is enabled | |
obj = Wrapper.new | |
Thread.new { | |
obj.write_a # no warning | |
obj.write_b # no warning | |
}.join | |
# Enable the warning | |
Warning[:non_owner_thread_writes] = true | |
obj = Wrapper.new | |
Thread.new { | |
obj.write_a | |
obj.write_b | |
}.join | |
# We would expect `write_a` and `write_b` to be blamed because they need a | |
# lock, but the output actually looks like this: | |
# | |
# /Users/aaron/git/ruby/lib/set.rb:515: warning: Hash mutated from a thread that didn't create it | |
# /Users/aaron/git/ruby/lib/set.rb:515: warning: Hash mutated from a thread that didn't create it | |
# /Users/aaron/git/ruby/lib/set.rb:515: warning: Hash mutated from a thread that didn't create it | |
# /Users/aaron/git/ruby/lib/set.rb:515: warning: Hash mutated from a thread that didn't create it | |
# | |
# It's because Set is implemented in Ruby, so it gets "blamed" for writing | |
# to the hash, but actually we need to do locking in `write_a` and `write_b`. | |
# To deal with this, we need to filter the warnings. Below is an example for | |
# filtering the warnings. It might need more logic for our app, but hopefully | |
# it can get the point across | |
module Warning | |
class << self | |
prepend Module.new { | |
def warn(msg, category:) | |
# We only care about non-owner writes | |
return super unless category == :non_owner_thread_writes | |
uplevel = 1 | |
# Add some filtering logic here | |
caller_locations.each do |loc| | |
path = loc.path | |
# If the path starts with either of these directories, then we need | |
# to go up a frame. | |
if path.start_with?(RbConfig::CONFIG["topdir"]) || path.start_with?(RbConfig::CONFIG["rubylibdir"]) | |
uplevel += 1 | |
else | |
break | |
end | |
end | |
# If the first frame is _not_ in the above paths, then just emit | |
return super if uplevel == 1 | |
# Otherwise, call Kernel.warn with the appropriate uplevel | |
Kernel.warn(msg, uplevel:, category:) | |
end | |
} | |
end | |
end | |
obj = Wrapper.new | |
Thread.new { | |
obj.write_a | |
obj.write_b | |
}.join | |
# Now the output looks like this: | |
# | |
# ./test.rb:12: warning: /Users/aaron/git/ruby/lib/set.rb:515: warning: Hash mutated from a thread that didn't create it | |
# ./test.rb:12: warning: /Users/aaron/git/ruby/lib/set.rb:515: warning: Hash mutated from a thread that didn't create it | |
# ./test.rb:17: warning: /Users/aaron/git/ruby/lib/set.rb:515: warning: Hash mutated from a thread that didn't create it | |
# ./test.rb:17: warning: /Users/aaron/git/ruby/lib/set.rb:515: warning: Hash mutated from a thread that didn't create it |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment