Project

General

Profile

Bug #8444

Regexp vars $~ and friends are not thread local

Added by jamespharaoh (James Pharaoh) about 6 years ago. Updated 1 day ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
ruby -v:
trunk
Backport:
[ruby-core:55153]

Description

=begin
In the docs for the Regexp special variables, $~ and friends, it says "These global variables are thread-local and method-local variables". However the following gives an unexpected result:

def get_proc
proc do |str|
str =~ /(.+)/
sleep 0.1
puts "got #{$1} from #{str}\n"
end
end
proc = get_proc
t1 = Thread.new { proc.call "abc" }
t2 = Thread.new { proc.call "def" }
t1.join
t2.join

This outputs the following:

got abc from abc
got abc from def

The expected result is of course:

got abc from abc
got def from def

Clearly the variables are being scoped to the get_proc method and are being shared by both threads. This runs contrary to the documentation and also to expectations.

This behaviour should either be changed, or the documentation updated to reflect the actual behaviour.

Interestingly, the following does work as expected:

proc = proc do |str|
str =~ /(.+)/
sleep 0.1
puts "got #{$1} from #{str}\n"
end
t1 = Thread.new { proc.call "abc" }
t2 = Thread.new { proc.call "def" }
t1.join
t2.join

=end

History

Updated by jeremyevans0 (Jeremy Evans) 1 day ago

  • Backport deleted (1.9.3: UNKNOWN, 2.0.0: UNKNOWN)

This behavior is still present in the master branch and it does seem like a bug to me. It does seems specific to returning a closure from a method and having two separate threads call that closure.

It doesn't occur if you pass the proc directly to Thread.new:

def get_proc
lambda do |str|
str =~ /(.+)/
sleep 0.1
puts "got #{$1} from #{str}\n"
end
end
proc = get_proc
t1 = Thread.new("abc", &proc)
t2 = Thread.new("def", &proc)
t1.join
t2.join

or if you switch to calling a method directly:

def get_proc str
str =~ /(.+)/
sleep 0.1
puts "got #{$1} from #{str}\n"
end
t1 = Thread.new { get_proc "abc" }
t2 = Thread.new { get_proc "def" }
t1.join
t2.join

From some debugging, one difference between the two cases in the original post is that different branches are taken in lep_svar, with the failing case taking the if and the passing case taking the else.

Also available in: Atom PDF