2

I am developing a ruby application which uses threads. I am having difficulty debugging it. The main problem is that I want to step through a particular thread. I set the breakpoint and run, but nothing happens until I use something like thr sw 2. But then subsequent input is FUBAR'd. I have tried pry, but pry seems no better at dealing with threads.

Solutions? Work-arounds?

EDIT 1:

FYI: Pry version 0.10.1 on Ruby 2.0.0 with byebug

So here is what I experience with Pry. Try 1:I set the thread-specific breakpoint using break

  1. Let program run.
  2. Breakpoint is triggered.
  3. Pry correctly shows me the breakpoint and surrounding code.
  4. Now nothing. I have no prompt. Control-C doesn't work. In fact, I have to kill -9 the process from outside.

Try 2: Using the "binding.pry" method above.

  1. same
  2. same
  3. same!
  4. I get a Pry prompt! I can yield expressions.
  5. I try to use "step" or "next" and suddenly I'm in "Pry::history#load" . So now the debugger has jumped to the thread that is processing the input itself. This is not functional.

Pry output:

[2] pry(#<QDS::Node::Primary>)> step

From: /usr/local/rvm/gems/ruby-2.0.0-p598/gems/pry-0.10.1/lib/pry/history.rb @ line 37 Pry::History#load:

    35: def load
    36:   @loader.call do |line|
 => 37:     @pusher.call(line.chomp)
    38:     @history << line.chomp
    39:     @original_lines += 1
    40:   end
    41: end

[2] pry(#<Pry::History>)>
  1. I try "exit" and now I'm at "pry(main)" without anything else working.

Pry output:

[1] pry(main)> continue
Error: Cannot find local context. Did you use `binding.pry`?
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
Otheus
  • 785
  • 10
  • 18
  • I can't get markdown formatting to properly show the ruby output as code. I have no idea why. – Otheus Apr 15 '15 at 11:44

1 Answers1

1

pry is just fine working with threads. There are probably glitches in your code.

Let’s try to examine the following thread switcher (example was taken here):

require 'pry'

module SeqExec
  class Seqs
    attr_reader :init
    def synch_prior mx, cv
      Thread.new {
        mx.synchronize {
          @init[:prior] = true
          loop do
            cv.wait mx
            # binding.pry
            yield if block_given?
            cv.broadcast
          end
        }
      }
    end

    def synch_posterior mx, cv
      Thread.new {
        mx.synchronize {
          @init[:posterior] = true
          loop do
            cv.wait mx
            yield if block_given?
            cv.broadcast
          end
        }
      }
    end

    def synch λ1, λ2
      @init = {}

      mx = Mutex.new
      cv = ConditionVariable.new

      synch_prior(mx, cv, &λ1)     # prior function
      Thread.pass until {:prior=>true} == @init

      synch_posterior(mx, cv, &λ2) # posterior function
      Thread.pass until {:prior=>true,:posterior=>true} == @init

      cv.signal                    # we are ready to start
    end
  end
end

module SeqExec
  Thread.abort_on_exception = true
  def pre &cb
    @prior = cb
  end
  def post &cb
    @posterior = cb
  end
  def run λ1 = nil, λ2 = nil
    pre &λ1 if λ1
    post &λ2 if λ2
    raise ArgumentError.new "Cannot run sequential execution, lambdas are not set" \
      unless (@prior && @posterior)
    Seqs.new.synch @prior, @posterior
  end
end

include SeqExec

@i=0
@stack = []
pre { sleep 0.3; print "-#{@i += 1}-"; @stack.push(@i) }
post { print "|#{@stack.pop}|" }
run

10.times { sleep 0.1 }
sleep 30000

With binding.pry commented out, it prints:

#⇒ -1-|1|-2-|2|-3-|3|-4-|4|-5-|5|-6-|6|-7-........

With binding.pry uncommented, we yield:

Frame number: 0/2
From: /tmp/a.rb @ line 12 SeqExec::Seqs#synch_prior:

     6: def synch_prior mx, cv
     7:   Thread.new {
     8:     mx.synchronize {
     9:       @init[:prior] = true
    10:       loop do
    11:         cv.wait mx
 => 12:         binding.pry
    13:         yield if block_given?
    14:         cv.broadcast
    15:       end
    16:     }
    17:   }
    18: end

▶ mx
=> #<Mutex:0xb21f204>
▶ exit
-1-|1|
Frame number: 0/2

From: /tmp/a.rb @ line 12 SeqExec::Seqs#synch_prior:

     6: def synch_prior mx, cv
     7:   Thread.new {
     8:     mx.synchronize {
     9:       @init[:prior] = true
    10:       loop do
    11:         cv.wait mx
 => 12:         binding.pry
    13:         yield if block_given?
    14:         cv.broadcast
    15:       end
    16:     }
    17:   }
    18: end

▶ exit
-2-|2|
Frame number: 0/2
...

Needless to say, the above means that threads stopped until pry is resumed.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • 1
    Doesn't really work that way for me. First, you're suggesting that "glitches in the code" prevent a debugger from working properly. Do you know how absurd that sounds? I'm editing my original question to illustrate the problem with Pry and threads. – Otheus Apr 15 '15 at 11:26
  • This does not sound absurd at all. E.g. deadlock will ruin any debugger attempt (I’m aware this is not your case, just an example.) My guess would by to try to remove `byebug` dependency and test what happens on the dry gemset with pry installed. I never met the behaviour you described (pry steps into it’s own context), though I have some experience with thread debugging. That’s why I suggested some glitches within your code/env. – Aleksei Matiushkin Apr 15 '15 at 11:51
  • Of _course_ there are glitches. That's what debuggers _are for_ :P Without `byebug`, how do I `step` through code? – Otheus Apr 15 '15 at 11:59
  • 1
    Well, you out-argued me, debugger should not surrender to glitches, but nobody’s perfect :) Anyway, you muddled me up. I was concern, `step` is pry’s own feature, since ruby2. And I have tried the code I provided with `byebug` — it works (steps into etc.) like a charm. I think, the main difference between working example above and your development code is that equipment design usually has no mutices, other thread is still running, possibly ends up and that mixes the things in pry’s mind up. I would suggest you to set breakpoints in each thread, to prevent race conditions in debug env. – Aleksei Matiushkin Apr 15 '15 at 12:15
  • " I would suggest you to set breakpoints in each thread, to prevent race conditions in debug env" Oooh that's an idea. And it depressingly sounds like I must start from scratch in testing Threads. Thanks. I'll follow up. – Otheus Apr 15 '15 at 13:28
  • Hopefully you’ll succeed this way. _Sidenote_: SO’s parser does not like codeblocks right after lists. Lines “Pry output” save formatting. – Aleksei Matiushkin Apr 15 '15 at 14:22
  • I gave up on pry again because it seemed incapable of handling the basic task of actually stopping execution where requested. I'm going to try to find someone locally who can help me through this. – Otheus May 08 '15 at 21:14