aboutsummaryrefslogtreecommitdiffstats
path: root/railties
diff options
context:
space:
mode:
Diffstat (limited to 'railties')
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/lib/binding_of_caller.rb168
-rw-r--r--railties/lib/breakpoint.rb89
-rw-r--r--railties/lib/breakpoint_client.rb207
4 files changed, 240 insertions, 226 deletions
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index fb4fa31707..baa5494f41 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Added Florian Gross' latest version of Breakpointer and friends that fixes a variaty of bugs #441 [Florian Gross]
+
* Fixed skeleton Rakefile to work with sqlite3 out of the box #521 [rasputnik]
* Fixed that script/breakpointer didn't get the Ruby path rewritten as the other scripts #523 [brandt@kurowski.net]
diff --git a/railties/lib/binding_of_caller.rb b/railties/lib/binding_of_caller.rb
index 759e760016..d18fbdef3d 100644
--- a/railties/lib/binding_of_caller.rb
+++ b/railties/lib/binding_of_caller.rb
@@ -1,85 +1,83 @@
-begin
- require 'simplecc'
-rescue LoadError
- class Continuation #:nodoc:
- def self.create(*args, &block)
- cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
- result ||= args
- return *[cc, *result]
- end
- end
-end
-
-class Binding #:nodoc:
- # This method returns the binding of the method that called your
- # method. It will raise an Exception when you're not inside a method.
- #
- # It's used like this:
- # def inc_counter(amount = 1)
- # Binding.of_caller do |binding|
- # # Create a lambda that will increase the variable 'counter'
- # # in the caller of this method when called.
- # inc = eval("lambda { |arg| counter += arg }", binding)
- # # We can refer to amount from inside this block safely.
- # inc.call(amount)
- # end
- # # No other statements can go here. Put them inside the block.
- # end
- # counter = 0
- # 2.times { inc_counter }
- # counter # => 2
- #
- # Binding.of_caller must be the last statement in the method.
- # This means that you will have to put everything you want to
- # do after the call to Binding.of_caller into the block of it.
- # This should be no problem however, because Ruby has closures.
- # If you don't do this an Exception will be raised. Because of
- # the way that Binding.of_caller is implemented it has to be
- # done this way.
- def self.of_caller(&block)
- old_critical = Thread.critical
- Thread.critical = true
- count = 0
- cc, result, error, extra_data = Continuation.create(nil, nil)
- error.call if error
-
- tracer = lambda do |*args|
- type, context, extra_data = args[0], args[4], args
- if type == "return"
- count += 1
- # First this method and then calling one will return --
- # the trace event of the second event gets the context
- # of the method which called the method that called this
- # method.
- if count == 2
- # It would be nice if we could restore the trace_func
- # that was set before we swapped in our own one, but
- # this is impossible without overloading set_trace_func
- # in current Ruby.
- set_trace_func(nil)
- cc.call(eval("binding", context), nil, extra_data)
- end
- elsif type == "line" then
- nil
- elsif type == "c-return" and extra_data[3] == :set_trace_func then
- nil
- else
- set_trace_func(nil)
- error_msg = "Binding.of_caller used in non-method context or " +
- "trailing statements of method using it aren't in the block."
- cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
- end
- end
-
- unless result
- set_trace_func(tracer)
- return nil
- else
- Thread.critical = old_critical
- case block.arity
- when 1 then yield(result)
- else yield(result, extra_data)
- end
- end
- end
-end \ No newline at end of file
+begin
+ require 'simplecc'
+rescue LoadError
+ class Continuation; end # :nodoc: # for RDoc
+ def Continuation.create(*args, &block) # :nodoc:
+ cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
+ result ||= args
+ return *[cc, *result]
+ end
+end
+
+class Binding; end # for RDoc
+# This method returns the binding of the method that called your
+# method. It will raise an Exception when you're not inside a method.
+#
+# It's used like this:
+# def inc_counter(amount = 1)
+# Binding.of_caller do |binding|
+# # Create a lambda that will increase the variable 'counter'
+# # in the caller of this method when called.
+# inc = eval("lambda { |arg| counter += arg }", binding)
+# # We can refer to amount from inside this block safely.
+# inc.call(amount)
+# end
+# # No other statements can go here. Put them inside the block.
+# end
+# counter = 0
+# 2.times { inc_counter }
+# counter # => 2
+#
+# Binding.of_caller must be the last statement in the method.
+# This means that you will have to put everything you want to
+# do after the call to Binding.of_caller into the block of it.
+# This should be no problem however, because Ruby has closures.
+# If you don't do this an Exception will be raised. Because of
+# the way that Binding.of_caller is implemented it has to be
+# done this way.
+def Binding.of_caller(&block)
+ old_critical = Thread.critical
+ Thread.critical = true
+ count = 0
+ cc, result, error, extra_data = Continuation.create(nil, nil)
+ error.call if error
+
+ tracer = lambda do |*args|
+ type, context, extra_data = args[0], args[4], args
+ if type == "return"
+ count += 1
+ # First this method and then calling one will return --
+ # the trace event of the second event gets the context
+ # of the method which called the method that called this
+ # method.
+ if count == 2
+ # It would be nice if we could restore the trace_func
+ # that was set before we swapped in our own one, but
+ # this is impossible without overloading set_trace_func
+ # in current Ruby.
+ set_trace_func(nil)
+ cc.call(eval("binding", context), nil, extra_data)
+ end
+ elsif type == "line" then
+ nil
+ elsif type == "c-return" and extra_data[3] == :set_trace_func then
+ nil
+ else
+ set_trace_func(nil)
+ error_msg = "Binding.of_caller used in non-method context or " +
+ "trailing statements of method using it aren't in the block."
+ cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
+ end
+ end
+
+ unless result
+ set_trace_func(tracer)
+ return nil
+ else
+ Thread.critical = old_critical
+ case block.arity
+ when 1 then yield(result)
+ else yield(result, extra_data)
+ end
+ end
+end
diff --git a/railties/lib/breakpoint.rb b/railties/lib/breakpoint.rb
index 6de2d64a7e..1923d49733 100644
--- a/railties/lib/breakpoint.rb
+++ b/railties/lib/breakpoint.rb
@@ -21,6 +21,9 @@ require 'drb'
require 'drb/acl'
module Breakpoint
+ id = %q$Id: breakpoint.rb 41 2005-01-22 20:22:10Z flgr $
+ Version = id.split(" ")[2].to_i
+
extend self
# This will pop up an interactive ruby session at a
@@ -114,10 +117,10 @@ module Breakpoint
end
end
- module CommandBundle #:nodoc:
+ module CommandBundle
# Proxy to a Breakpoint client. Lets you directly execute code
# in the context of the client.
- class Client#:nodoc:
+ class Client
def initialize(eval_handler) # :nodoc:
@eval_handler = eval_handler
end
@@ -133,15 +136,23 @@ module Breakpoint
end
# Will execute the specified statement at the client.
- def method_missing(method, *args)
- if args.empty?
- result = eval("#{method}")
+ def method_missing(method, *args, &block)
+ if args.empty? and not block
+ result = eval "#{method}"
else
- result = eval("#{method}(*Marshal.load(#{Marshal.dump(args).inspect}))")
- end
-
- unless [true, false, nil].include?(result)
- result.extend(DRbUndumped) if result
+ # This is a bit ugly. The alternative would be using an
+ # eval context instead of an eval handler for executing
+ # the code at the client. The problem with that approach
+ # is that we would have to handle special expressions
+ # like "self", "nil" or constants ourself which is hard.
+ remote = eval %{
+ result = lambda { |block, *args| #{method}(*args, &block) }
+ def result.call_with_block(*args, &block)
+ call(block, *args)
+ end
+ result
+ }
+ remote.call_with_block(*args, &block)
end
return result
@@ -175,6 +186,7 @@ module Breakpoint
# client.File.open("temp.txt", "w") { |f| f.puts "Hello" }
def client()
if Breakpoint.use_drb? then
+ sleep(0.5) until Breakpoint.drb_service.eval_handler
Client.new(Breakpoint.drb_service.eval_handler)
else
Client.new(lambda { |code| eval(code, TOPLEVEL_BINDING) })
@@ -205,7 +217,7 @@ module Breakpoint
# These exceptions will be raised on failed asserts
# if Breakpoint.asserts_cause_exceptions is set to
# true.
- class FailedAssertError < RuntimeError#:nodoc:
+ class FailedAssertError < RuntimeError
end
# This asserts that the block evaluates to true.
@@ -279,7 +291,7 @@ module Breakpoint
@collision_handler.call
end
- def ping; end
+ def ping() end
def add_breakpoint(context, message)
workspace = IRB::WorkSpace.new(context)
@@ -290,31 +302,7 @@ module Breakpoint
@handler.call(workspace, message)
end
- def register_handler(&block)
- @handler = block
- end
-
- def unregister_handler
- @handler = nil
- end
-
- attr_reader :eval_handler
-
- def register_eval_handler(&block)
- @eval_handler = block
- end
-
- def unregister_eval_handler
- @eval_handler = lambda { }
- end
-
- def register_collision_handler(&block)
- @collision_handler = block
- end
-
- def unregister_collision_handler
- @collision_handler = lambda { }
- end
+ attr_accessor :handler, :eval_handler, :collision_handler
end
# Will run Breakpoint in DRb mode. This will spawn a server
@@ -359,7 +347,8 @@ module Breakpoint
#
# Detailed information about running DRb through firewalls is
# available at http://www.rubygarden.org/ruby?DrbTutorial
- def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'], ignore_collisions = false) #:nodoc:
+ def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'],
+ ignore_collisions = false)
return false if @use_drb
@@ -402,7 +391,7 @@ module Breakpoint
end
# Deactivates a running Breakpoint service.
- def deactivate_drb #:nodoc:
+ def deactivate_drb
@service.stop_service unless @service.nil?
@service = nil
@use_drb = false
@@ -411,7 +400,7 @@ module Breakpoint
# Returns true when Breakpoints are used over DRb.
# Breakpoint.activate_drb causes this to be true.
- def use_drb? #:nodoc:
+ def use_drb?
@use_drb == true
end
end
@@ -440,7 +429,11 @@ module IRB # :nodoc:
@CONF[:MAIN_CONTEXT] = irb.context
old_sigint = trap("SIGINT") do
- irb.signal_handle
+ begin
+ irb.signal_handle
+ rescue RubyLex::TerminateLineInput
+ # ignored
+ end
end
catch(:IRB_EXIT) do
@@ -464,7 +457,7 @@ module IRB # :nodoc:
end
end
- class Context#:nodoc:
+ class Context
alias :old_evaluate :evaluate
def evaluate(line, line_no)
if line.chomp == "exit" then
@@ -475,7 +468,7 @@ module IRB # :nodoc:
end
end
- class WorkSpace#:nodoc:
+ class WorkSpace
alias :old_evaluate :evaluate
def evaluate(*args)
@@ -493,7 +486,7 @@ module IRB # :nodoc:
end
end
- module InputCompletor#:nodoc:
+ module InputCompletor
def self.eval(code, context, *more)
# Big hack, this assumes that InputCompletor
# will only call eval() when it wants code
@@ -504,9 +497,9 @@ module IRB # :nodoc:
end
module DRb # :nodoc:
- class DRbObject#:nodoc:
- undef :inspect
- undef :clone
+ class DRbObject
+ undef :inspect if method_defined?(:inspect)
+ undef :clone if method_defined?(:clone)
end
end
@@ -522,4 +515,4 @@ def assert(&block)
Binding.of_caller do |context|
Breakpoint.assert(context, &block)
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/breakpoint_client.rb b/railties/lib/breakpoint_client.rb
index fa93c11f3e..0abcb6824e 100644
--- a/railties/lib/breakpoint_client.rb
+++ b/railties/lib/breakpoint_client.rb
@@ -2,17 +2,18 @@ require 'breakpoint'
require 'optparse'
require 'timeout'
-options = {
+Options = {
:ClientURI => nil,
:ServerURI => "druby://localhost:42531",
- :RetryDelay => 1,
+ :RetryDelay => 3,
+ :Permanent => true,
:Verbose => false
}
ARGV.options do |opts|
script_name = File.basename($0)
opts.banner = [
- "Usage: ruby #{script_name} [options] [server uri]",
+ "Usage: ruby #{script_name} [Options] [server uri]",
"",
"This tool lets you connect to a breakpoint service ",
"which was started via Breakpoint.activate_drb.",
@@ -29,18 +30,13 @@ ARGV.options do |opts|
"connections from the server.",
"Default: Find a good URI automatically.",
"Example: -c druby://localhost:12345"
- ) { |options[:ClientURI]| }
+ ) { |Options[:ClientURI]| }
opts.on("-s", "--server-uri=uri",
"Connect to the server specified at the",
"specified uri.",
"Default: druby://localhost:42531"
- ) { |options[:ServerURI]| }
-
- opts.on("-v", "--verbose",
- "Report all connections and disconnections",
- "Default: false"
- ) { |options[:Verbose]| }
+ ) { |Options[:ServerURI]| }
opts.on("-R", "--retry-delay=delay", Integer,
"Automatically try to reconnect to the",
@@ -49,124 +45,149 @@ ARGV.options do |opts|
"A value of 0 disables automatical",
"reconnecting completely.",
"Default: 10"
- ) { |options[:RetryDelay]| }
+ ) { |Options[:RetryDelay]| }
+
+ opts.on("-P", "--[no-]permanent",
+ "Run the breakpoint client in permanent mode.",
+ "This means that the client will keep continue",
+ "running even after the server has closed the",
+ "connection. Useful for example in Rails."
+ ) { |Options[:Permanent]| }
+
+ opts.on("-V", "--[no-]verbose",
+ "Run the breakpoint client in verbose mode.",
+ "Will produce more messages, for example between",
+ "individual breakpoints. This might help in seeing",
+ "that the breakpoint client is still alive, but adds",
+ "quite a bit of clutter."
+ ) { |Options[:Verbose]| }
opts.separator ""
opts.on("-h", "--help",
"Show this help message."
) { puts opts; exit }
+ opts.on("-v", "--version",
+ "Display the version information."
+ ) do
+ id = %q$Id: breakpoint_client.rb 40 2005-01-22 20:05:00Z flgr $
+ puts id.sub("Id: ", "")
+ puts "(Breakpoint::Version = #{Breakpoint::Version})"
+ exit
+ end
opts.parse!
end
-options[:ServerURI] = ARGV[0] if ARGV[0]
+Options[:ServerURI] = ARGV[0] if ARGV[0]
-$running = true
+module Handlers
+ extend self
-trap("INT"){$running = false}
+ def breakpoint_handler(workspace, message)
+ puts message
+ IRB.start(nil, nil, workspace)
+
+ puts ""
+ if Options[:Verbose] then
+ puts "Resumed execution. Waiting for next breakpoint...", ""
+ end
+ end
+
+ def eval_handler(code)
+ result = eval(code, TOPLEVEL_BINDING)
+ if result then
+ DRbObject.new(result)
+ else
+ result
+ end
+ end
-puts "Waiting for initial breakpoint..."
+ def collision_handler()
+ msg = [
+ " *** Breakpoint service collision ***",
+ " Another Breakpoint service tried to use the",
+ " port already occupied by this one. It will",
+ " keep waiting until this Breakpoint service",
+ " is shut down.",
+ " ",
+ " If you are using the Breakpoint library for",
+ " debugging a Rails or other CGI application",
+ " this likely means that this Breakpoint",
+ " session belongs to an earlier, outdated",
+ " request and should be shut down via 'exit'."
+ ].join("\n")
+
+ if RUBY_PLATFORM["win"] then
+ # This sucks. Sorry, I'm not doing this because
+ # I like funky message boxes -- I need to do this
+ # because on Windows I have no way of displaying
+ # my notification via puts() when gets() is still
+ # being performed on STDIN. I have not found a
+ # better solution.
+ begin
+ require 'tk'
+ root = TkRoot.new { withdraw }
+ Tk.messageBox('message' => msg, 'type' => 'ok')
+ root.destroy
+ rescue Exception
+ puts "", msg, ""
+ end
+ else
+ puts "", msg, ""
+ end
+ end
+end
+
+# Used for checking whether we are currently in the reconnecting loop.
+reconnecting = false
loop do
- DRb.start_service(options[:ClientURI])
+ DRb.start_service(Options[:ClientURI])
begin
- service = DRbObject.new(nil, options[:ServerURI])
+ service = DRbObject.new(nil, Options[:ServerURI])
begin
- timeout(10) { service.ping }
- rescue Timeout::Error, DRb::DRbConnError
- if options[:Verbose]
- puts "",
- " *** Breakpoint service didn't respond to ping request ***",
- " This likely happened because of a misconfigured ACL (see the",
- " documentation of Breakpoint.activate_drb, note that by default",
- " you can only connect to a remote Breakpoint service via a SSH",
- " tunnel), but might also be caused by an extremely slow connection.",
- ""
- end
- raise
- end
+ service.eval_handler = Handlers.method(:eval_handler)
+ service.collision_handler = Handlers.method(:collision_handler)
+ service.handler = Handlers.method(:breakpoint_handler)
- begin
- service.register_eval_handler do |code|
- result = eval(code, TOPLEVEL_BINDING)
- if result
- DRbObject.new(result)
- else
- result
- end
- end
-
- service.register_collision_handler do
- msg = [
- " *** Breakpoint service collision ***",
- " Another Breakpoint service tried to use the",
- " port already occupied by this one. It will",
- " keep waiting until this Breakpoint service",
- " is shut down.",
- " ",
- " If you are using the Breakpoint library for",
- " debugging a Rails or other CGI application",
- " this likely means that this Breakpoint",
- " session belongs to an earlier, outdated",
- " request and should be shut down via 'exit'."
- ].join("\n")
-
- if RUBY_PLATFORM["win"] then
- # This sucks. Sorry, I'm not doing this because
- # I like funky message boxes -- I need to do this
- # because on Windows I have no way of displaying
- # my notification via puts() when gets() is still
- # being performed on STDIN. I have not found a
- # better solution.
- begin
- require 'tk'
- root = TkRoot.new { withdraw }
- Tk.messageBox('message' => msg, 'type' => 'ok')
- root.destroy
- rescue Exception
- puts "", msg, ""
- end
- else
- puts "", msg, ""
- end
+ reconnecting = false
+ if Options[:Verbose] then
+ puts "Connection established. Waiting for breakpoint...", ""
end
- service.register_handler do |workspace, message|
- puts message
- IRB.start(nil, nil, workspace)
- puts "", "Resumed execution. Waiting for next breakpoint...", ""
- end
-
- puts "Connection established. Waiting for breakpoint...", "" if options[:Verbose]
-
- while $running
+ loop do
begin
service.ping
rescue DRb::DRbConnError => error
- puts "Server exited. Closing connection..." if options[:Verbose]
+ puts "Server exited. Closing connection...", ""
+ exit! unless Options[:Permanent]
break
end
sleep(0.5)
end
ensure
- service.unregister_handler
+ service.eval_handler = nil
+ service.collision_handler = nil
+ service.handler = nil
end
rescue Exception => error
- break unless $running
- if options[:RetryDelay] > 0 then
- puts "No connection to breakpoint service at #{options[:ServerURI]}:", " (#{error.inspect})" if options[:Verbose]
- error.backtrace if $DEBUG
-
- puts " Reconnecting in #{options[:RetryDelay]} seconds..." if options[:Verbose]
-
- sleep options[:RetryDelay]
+ if Options[:RetryDelay] > 0 then
+ if not reconnecting then
+ reconnecting = true
+ puts "No connection to breakpoint service at #{Options[:ServerURI]} " +
+ "(#{error.class})"
+ puts error.backtrace if $DEBUG
+ puts "Tries to connect will be made every #{Options[:RetryDelay]} seconds..."
+ end
+
+ sleep Options[:RetryDelay]
retry
else
raise
end
end
-end \ No newline at end of file
+end