Video transcript & code
In episode #406, we learned about the fact that Ruby keeps around information about the last regular expression match that was performed. We learned that we can request this information, both via the
Regexp.last_match class method, and via a bunch of strange-looking single-character global variables like
On looking at this code, you might have wondered if using these features introduces thread-safety concerns. After all, we're talking about what appear to be global variables. Or a class-level method, which is effectively global. Doesn't this mean that matching against a regular expression in one thread could unpredictable re-set the last-match information in other threads?
Let's check to see if this is the case.
After making an initial match, we'll start a new thread.
Inside the thread, we'll take a look at the
Regexp.last_match, and its alias,
join the thread, in order to wait for it to finish.
When we run this, we can see that the main-thread last-match data does not cross the thread boundary. Both last-match expressions result in
Now let's do a regex match inside the thread, and verify that this sets the last-match information.
Finally, we'll check the last-match data again in the main thread.
Looking at the results, we can see that the regex match inside the thread has not affected the main-thread local state.
line = "shodan: ERROR Notification of station IT failed" line =~ /ERROR|FATAL/ # => 8 match = Regexp.last_match # => #<MatchData "ERROR"> $~ # => #<MatchData "ERROR"> Thread.new do Regexp.last_match # => nil $~ # => nil line =~ /shodan/ Regexp.last_match # => #<MatchData "shodan"> end.join Regexp.last_match # => #<MatchData "ERROR">
What this shows us is that regular expression last-match information, while appearing to be global, is actually managed at a thread-local level by Ruby. That's why I've referred to the shorthand variables as "pseudo-global". They look like globals, but in reality they are not.
As a result, it is in fact safe to use these values in multi-threaded code. The last-match information will always be from the local thread; it will not "bleed over" from other threads.
There are some other language-supplied pseudo-globals in Ruby. I'm not going to include a comprehensive list here. But if you are ever concerned about whether a Ruby-provided global or class-level method is actually global across threads, now you know a quick and dirty way to check.