In Progress
Unit 1, Lesson 1
In Progress

Pseudoglobal

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 $~, $&, and $`.

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, $~.

Then we'll 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 nil.

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.

Happy hacking!

Responses