16

I'm trying to debug a multithreaded ruby script, the problem is when I do

binding.pry

The other threads continue sending output to the console. How do I make them stop at binding.pry and then start up again when I exit? I'm thinking there's a way to do this in .pryrc

pguardiario
  • 53,827
  • 19
  • 119
  • 159

3 Answers3

5

It sounds like you are proposing using the invocation of binding.pry to interrogate all child threads and suspend them until you end your pry session. That is not possible for technical and practical reasons. The Binding and Thread classes don't work that way, and multithreading in Ruby doesn't work that way.

Threads in Ruby can only be suspended by calling Kernel#sleep or Thread.stop. (and those are functionally equivalent) Crucially, these methods can only be invoked on the current thread. One thread cannot suspend another thread. (Thread.stop is a class method, not an instance method)

Let's look at what binding.pry actually does: objects of class Binding encapsulate the execution context at some particular place in the code and retain this context for future use. So when you put binding.pry into your code, you're telling Ruby to encapsulate the execution context for the current thread.

What that means is when you call binding.pry in the main thread the Binding object has context for the current thread and can tell itself to sleep, but the core Ruby Thread class does not allow it to tell any other threads to sleep.

Even if it did support it, it would be weird and error-prone and the cause of a lot of head-scratching. Imagine that you have code like this:

# we are in the main thread
Thread.new do
  # we are in the child thread
  foo = Foo.new(bar.fetch(:baz, {}))
  foo.save
end

# we are in the main thread
binding.pry

Because of the way Ruby handles context-switching, if binding.pry told all child threads to stop then the child thread might stop ANYWHERE in the call stack, including anywhere in the code for Foo.new or .save. Having those threads pause and resume in the middle of executing code that you did not write will cause you trouble. For example, what happens if an ActiveRecord connection from the pool is checked out and used for a SELECT query but the thread got put to sleep before it returned the connection to the pool and before it got a response? Bad stuff. Lots of bad stuff.

It sounds like the real solution for you is to change the verbosity of the child threads. If you are troubleshooting chatty code and your other threads are being noisy while you're trying to work in a single thread, then set the other threads to use, for example, a lower logging level temporarily.

anothermh
  • 9,815
  • 3
  • 33
  • 52
3

If binding.pry is yielding inconsistent results, try the following:

  1. If it exists, remove pry-stack_explorer from your gemfile and then repackage your application. That gem appears to have issues with pry-byebug.

  2. Additionally, not sure if you tried this already but a few server restarts couldn't hurt; this has resolved weird issues like this for me more often than I'd like to admit.

However, you may be trying to do something that simply cannot be done with binding.pry:

One fundamental about binding.pry that must be understood is that its scope would include only the current thread, not every thread in a multi-threaded application as you have described.

Hypothetically, if binding.pry did affect every thread (again, it does not), this would result in unpredictable behavior especially if some threads are performing data access/retrieval/update operations.

To accomplish what you want, you may need to take a different approach:

Although it may be tedious depending on how many threads are in your application, you may need to control/stop each thread individually. Obviously, this can be done with Thread.stop.

lax1089
  • 3,403
  • 3
  • 17
  • 37
2

According to the FAQ:

Threads don't work, what's wrong?

Some systems (Mac OS X in particular) use Editline instead of GNU Readline. The Ruby Readline library currently has an issue when compiled with the Editline library which makes it block all threads rather than just the one calling Readline.readline() (it blocks while holding the global VM lock, preventing other threads from acquiring it). This can be fixed by installing GNU Readline on OS X.

So it sounds as if Rubys built with Editline block all threads as you expect and that the developers of Pry consider that to be a bug. I couldn't find any way for using Pry to automatically block all threads from one call to binding.pry.

This answer suggests using a Mutex to synchronize your threads before calling binding.pry. Obviously, that requires changes to your code and you might not want to synchronize threads just for the sake of debugging. On the other hand, synchronizing threads does reduce uncertainty and might make debugging easier without using Pry at all.

As an alternative, you can put breakpoints in each thread:

require 'pry'

Thread.new do
  10.times do |i|
    binding.pry
    puts "subthread: #{i}"
  end
end


10.times do |i|
  binding.pry
  puts "main thread: #{i}"
end

That'll stop execution for both the main and subthreads at a specific moment. Unfortunately that doesn't help if you want to inspect the the other thread or know exactly where it was when the current thread was stopped. It's also a bit of a pain to comment out all the breakpoints if aren't currently debugging. (But you can disable Pry altogether by setting the DISABLE_PRY environment variable to a non-nil value.)

If you really need to debug a multithread program, you'll probably want to use something like GDB that provides more support.

Community
  • 1
  • 1
Kathryn
  • 1,557
  • 8
  • 12