Subtle bug with catch/throw. Am I missing something?

Hi,
I'm having trouble locating this bug that I'm having. I think it's
because I don't completely understand Ruby's throw/catch mechanism.

I'm trying to write a simple example that demonstrates the same problem,
but I can't track down the bug.

Here's my code: Is there any subtleties that may cause throw/catch to
not behave as expected?

  def idle(engine,key)
    catch(:exit) do <-- Catch :exit
      while engine.nextFrame(key)
        engine.drawFrame do
          drawInContext(@img, 0, 0, 1)
        end

        @enterEvent.listen do
          @state = method(:active)
          puts "player entered"
          throw :exit <-- Throw :exit
        end
      end
    end
  end

Gives me: "in `throw': uncaught throw `exit' (NameError)"

And what's really strange is, the problem goes away after putting in a
print statement.

  def idle(engine,key)
    catch(:exit) do <-- Catch :exit
      while engine.nextFrame(key)
        engine.drawFrame do
          drawInContext(@img, 0, 0, 1)
        end

        @enterEvent.listen do
          @state = method(:active)
          puts "player entered"
          throw :exit <-- Throw :exit
        end
      end
    end

    puts "end" <-- This line causes the problem to go away.
  end

Thanks a lot for your help. Any place to start looking would be really
helpful. I'm fresh out of ideas.
  -Patrick

I'm using Ruby 1.8.6 on Windows.

···

--
Posted via http://www.ruby-forum.com/.

Patrick Li wrote:

        @enterEvent.listen do
          @state = method(:active)
          puts "player entered"
          throw :exit <-- Throw :exit
        end

What does this @enterEvent.listen do? If it saves the block passed to it
and terminates, and the block gets executed later, when the main control
is already out of your idle method, you will get the error. Here's
example of an error-producing code to illustrate what I say:

class K
  def listen(&b)
    @b=b
  end
  def idle
    catch(:a){listen{throw(:a)}}
  end
  def call
    @b.call
  end
end
k=K::new
k.idle
k.call

It causes an error even though throw is inside catch, when we look at
the code.

TPR.

···

--
Posted via http://www.ruby-forum.com/\.

Patrick Li wrote:

Hi,
I'm having trouble locating this bug that I'm having. I think it's
because I don't completely understand Ruby's throw/catch mechanism.

I'm trying to write a simple example that demonstrates the same problem,
but I can't track down the bug.

Here's my code: Is there any subtleties that may cause throw/catch to
not behave as expected?

  def idle(engine,key)
    catch(:exit) do <-- Catch :exit
      while engine.nextFrame(key)
        engine.drawFrame do
          drawInContext(@img, 0, 0, 1)
        end

        @enterEvent.listen do
          @state = method(:active)
          puts "player entered"
          throw :exit <-- Throw :exit
        end
      end
    end
  end

Gives me: "in `throw': uncaught throw `exit' (NameError)"

I'm guessing that the problem is that the listen block has been saved away and is no longer above the #catch on the stack. Here's a simpler example:

def listen
   catch(:exit) do
     @listeners << proc {throw :exit}
   end
end

@listeners =
listen
@listeners[0].call

This produces the same error message.

···

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Patrick,

Just a guess: can you clarify the 'while' loop with a 'do' I wonder?

Just wondering whether the interpreter is getting confused here - and
the extra 'puts' is somehow clarifying the situation for it ?

Cheers

John

ie:

while engine.nextFrame(key) do
...

···

--
Posted via http://www.ruby-forum.com/.

Ah I see.
Thank you. That would have taken me forever to find.

Is there a way around this? Or is this basically a design issue?

···

--
Posted via http://www.ruby-forum.com/.

Patrick Li wrote:

Ah I see.
Thank you. That would have taken me forever to find.

Is there a way around this? Or is this basically a design issue?

It's a design issue, if it is the case, because at the time the :exit is
thrown, you're already out of idle(), so catching it there is definitely
not what you intended. Probably you should catch the exception somewhere
higher on the stack, from the method that, I guess, calls idle in a
loop, or even higher.

TPR.

···

--
Posted via http://www.ruby-forum.com/\.

Thanks for your responses.

I think I understand now. I was just using the catch as a lazy way to
break out of the loop. I can just use a loop flag instead then.

I still don't know why the "puts" statement avoids the error. The "do"
after the "while" doesn't help either.

···

--
Posted via http://www.ruby-forum.com/.

Patrick Li wrote:

Thanks for your responses.

I think I understand now. I was just using the catch as a lazy way to
break out of the loop. I can just use a loop flag instead then.

I still don't know why the "puts" statement avoids the error. The "do"
after the "while" doesn't help either.

Patrick,

Again total guesswork , but based on the other posts from people who
actually know what they are talking about... - sounds like the symbol
':exit' is possibly out-of-scope by the time the 'throw' is executed.

So (purely out of interest, as you are solving this another way...)

Could you try replacing :exit with an instance or class-based variable
like:

def initialize
  @exit=:exit
end

And then refer to it in both your catch and throw ? (or at least your
catch..)

Again, based on what's been posted here - sounds like timing plays a
role here: maybe the 'puts' delays execution just-enough to keep the
thread-of-execution within the method for the 'throw' to find the
referring catch...?

This is quite an interesting problem...I'm learning quite a bit about
ruby just from watching the other posts ! :slight_smile:

Cheers

John

···

--
Posted via http://www.ruby-forum.com/\.

John Pritchard-williams wrote:

Patrick Li wrote:

Thanks for your responses.

I think I understand now. I was just using the catch as a lazy way to break out of the loop. I can just use a loop flag instead then.

I still don't know why the "puts" statement avoids the error. The "do" after the "while" doesn't help either.

Patrick,

Again total guesswork , but based on the other posts from people who actually know what they are talking about... - sounds like the symbol ':exit' is possibly out-of-scope by the time the 'throw' is executed.

Symbols are literal values, so they are not affected by scope.

···

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

I agree, it's a very ... interesting problem. (Frustrating at first, but
interesting afterwards).

It's a good guess, but setting @exit = :exit, didn't help.

I wish I could post a little sample code that exhibits the same
behavior, but I just can't seem to isolate it.

I'm playing fast and fancy with continuations, so that's probably
messing around with scopes quite a bit.

···

--
Posted via http://www.ruby-forum.com/.