Overview
Pry-byebug integrates the pry REPL with byebug's debugging capabilities, creating a debugging environment that supports breakpoints, step-through execution, and interactive code evaluation. The gem extends pry with additional commands for controlling program execution flow, examining stack frames, and inspecting variable states during runtime.
The integration operates through several core components. The Pry
class gains debugging commands like step
, next
, continue
, and finish
. The PryByebug
module manages debugger state and coordinates between pry sessions and byebug's execution control. The Breakpoints
class handles breakpoint creation, modification, and removal across the debugging session.
require 'pry-byebug'
def calculate_total(items)
binding.pry # Debugger entry point
total = 0
items.each do |item|
total += item[:price] * item[:quantity]
end
total
end
items = [
{ name: 'Widget', price: 10.50, quantity: 2 },
{ name: 'Gadget', price: 25.00, quantity: 1 }
]
result = calculate_total(items)
# => Drops into pry-byebug session at binding.pry
When execution reaches binding.pry
, the program pauses and opens an interactive session. The debugger displays the current line, surrounding context, and provides access to all variables and methods in the current scope. The session accepts both regular Ruby code and debugging commands.
class OrderProcessor
def initialize(orders)
@orders = orders
@processed = []
end
def process_all
@orders.each_with_index do |order, index|
binding.pry if order[:amount] > 1000 # Conditional debugging
processed_order = process_order(order)
@processed << processed_order
end
end
private
def process_order(order)
# Processing logic here
order.merge(processed_at: Time.now)
end
end
The debugger maintains full access to instance variables, local variables, and method definitions. Commands execute within the current binding, allowing inspection of object state and method invocation during program execution.
# During debugging session, all these work:
self # => Current object instance
@orders # => Instance variable access
local_variables # => List of local variables
order # => Current local variable value
process_order(order) # => Method invocation
Basic Usage
Pry-byebug requires adding binding.pry
statements to source code at points where debugging should begin. The debugger activates when Ruby execution encounters these statements, opening an interactive session with access to the current execution context.
require 'pry-byebug'
def fibonacci(n)
return n if n <= 1
binding.pry # Debug session starts here
a, b = 0, 1
(2..n).each do |i|
a, b = b, a + b
binding.pry if i == n - 1 # Conditional breakpoint
end
b
end
fibonacci(8)
Inside the debugging session, execution control commands manage program flow. The step
command advances one line of execution, entering method calls when encountered. The next
command advances one line without entering method calls. The continue
command resumes normal execution until the next breakpoint or program termination.
# Debugging session commands:
step # Execute next line, step into methods
next # Execute next line, step over methods
continue # Resume execution until next breakpoint
finish # Complete current method and return to caller
up # Move up one frame in call stack
down # Move down one frame in call stack
The debugger provides stack frame navigation for examining the call hierarchy. Each frame represents a method invocation with its own variable scope and execution context. The up
and down
commands navigate between frames while maintaining access to frame-specific variables and methods.
def method_a(value)
processed = value * 2
method_b(processed)
end
def method_b(input)
binding.pry # When this activates:
result = input + 10
method_c(result)
end
def method_c(final)
final.to_s
end
method_a(5)
# In debugging session:
# up # Navigate to method_a frame, access 'processed' variable
# down # Return to method_b frame
Variable inspection works through direct reference or helper methods. Local variables, instance variables, and constants remain accessible during debugging. The ls
command lists available methods and variables for the current object or class.
class DataProcessor
VALID_FORMATS = ['json', 'xml', 'csv'].freeze
def initialize(format)
@format = format
@errors = []
end
def process(data)
binding.pry
validate_format
# During debugging:
# @format # Instance variable access
# @errors # Instance variable access
# VALID_FORMATS # Constant access
# data # Parameter access
# ls # List methods and variables
# ls DataProcessor # List class methods
end
private
def validate_format
unless VALID_FORMATS.include?(@format)
@errors << "Invalid format: #{@format}"
end
end
end
Code evaluation during debugging accepts any valid Ruby expression. Method calls, variable assignments, and object modifications execute within the current binding, allowing runtime experimentation and state manipulation.
def calculate_discount(price, discount_rate)
binding.pry
discounted_price = price * (1 - discount_rate)
discounted_price.round(2)
end
calculate_discount(100, 0.15)
# In debugging session:
# price # => 100
# discount_rate # => 0.15
# price * discount_rate # => 15.0
# discount_rate = 0.20 # Modify variable
# discounted_price = price * 0.8 # Create new variable
Advanced Usage
Pry-byebug supports remote debugging for applications running in different processes or containers. The pry-remote
functionality creates debugging servers that accept connections from external pry clients, enabling debugging of deployed applications or background processes.
require 'pry-byebug'
require 'pry-remote'
class BackgroundWorker
def process_queue
loop do
job = fetch_next_job
if job && job[:debug]
binding.remote_pry # Remote debugging entry point
end
process_job(job) if job
sleep 1
end
rescue => e
binding.remote_pry # Debug exceptions remotely
raise
end
private
def fetch_next_job
# Queue polling logic
end
def process_job(job)
# Job processing logic
end
end
# Start remote debugging server
worker = BackgroundWorker.new
worker.process_queue
Remote debugging sessions maintain full debugging capabilities while running in separate processes. The debugging server accepts multiple client connections, allowing collaborative debugging sessions with shared breakpoints and execution control.
Conditional breakpoints provide fine-grained debugging control by activating only when specified conditions evaluate to true. Complex conditions can examine variable states, method return values, or object relationships before triggering debugging sessions.
class OrderAnalyzer
def analyze_orders(orders)
orders.each_with_index do |order, index|
# Multiple conditional breakpoint strategies
binding.pry if order[:total] > 5000 # Value threshold
binding.pry if order[:customer_type] == 'premium' # String matching
binding.pry if index > 100 && order[:total] < 50 # Complex conditions
binding.pry if suspicious_order?(order) # Method-based conditions
process_order(order)
end
end
private
def suspicious_order?(order)
order[:total] > 10000 &&
order[:items].length < 3 &&
order[:customer_id].start_with?('temp_')
end
def process_order(order)
# Order processing logic
end
end
The debugger supports breakpoint management through programmatic interfaces. Breakpoints can be set, listed, disabled, and removed during debugging sessions or through application code. The Pry::Byebug::Breakpoints
class provides methods for breakpoint manipulation.
# Programmatic breakpoint management
def setup_debugging
# Set breakpoint at specific file and line
Pry::Byebug::Breakpoints.add_file(__FILE__, 45)
# Set method breakpoint
Pry::Byebug::Breakpoints.add_method(OrderProcessor, :process_order)
# Conditional breakpoint with custom logic
Pry::Byebug::Breakpoints.add_file(__FILE__, 50) do |binding|
binding.local_variable_get(:user_id) == 'admin'
end
end
# During debugging session:
# breakpoints # List all active breakpoints
# break --condition # Add conditional breakpoint
# break --delete 1 # Remove breakpoint by number
# break --disable 2 # Disable breakpoint without deletion
Thread debugging requires special consideration since pry-byebug operates within single thread contexts. Multi-threaded applications need coordination mechanisms to avoid debugging session conflicts and ensure thread-safe debugging practices.
require 'thread'
class ThreadedProcessor
def initialize
@queue = Queue.new
@debug_mutex = Mutex.new
@threads = []
end
def start_workers(count)
count.times do |i|
@threads << Thread.new do
loop do
job = @queue.pop
break if job == :shutdown
# Thread-safe debugging
if job[:debug] && @debug_mutex.try_lock
begin
binding.pry # Only one thread debugs at a time
ensure
@debug_mutex.unlock
end
end
process_job(job)
end
end
end
end
def add_job(job)
@queue.push(job)
end
def shutdown
@threads.length.times { @queue.push(:shutdown) }
@threads.each(&:join)
end
private
def process_job(job)
# Job processing implementation
end
end
Exception debugging intercepts raised exceptions before they propagate, providing debugging access at the exact point of failure. The rescue
clause can include debugging statements to examine variables and execution state when exceptions occur.
class ErrorProneService
def risky_operation(data)
begin
validate_data(data)
transform_data(data)
save_data(data)
rescue StandardError => e
# Debug at exception point with full context
binding.pry
# Examine exception details and local state
# e.class # Exception type
# e.message # Exception message
# e.backtrace # Full stack trace
# data # Original input data
# local_variables # All local variables
# Decide whether to re-raise or handle
raise if data[:critical]
log_error(e, data)
end
end
private
def validate_data(data)
raise ArgumentError, "Invalid data format" unless data.is_a?(Hash)
end
def transform_data(data)
# Transformation that might fail
end
def save_data(data)
# Persistence that might fail
end
def log_error(error, context)
# Error logging implementation
end
end
Testing Strategies
Testing pry-byebug integration requires careful isolation to prevent debugging sessions from interrupting test execution. Test environments need configuration that disables interactive debugging while maintaining code coverage and functionality testing.
# spec/spec_helper.rb
require 'pry-byebug'
RSpec.configure do |config|
# Disable pry in test environment
config.before(:each) do
allow_any_instance_of(Binding).to receive(:pry).and_return(nil)
allow_any_instance_of(Binding).to receive(:remote_pry).and_return(nil)
end
# Alternative: conditional debugging based on environment
config.before(:each) do
ENV['ENABLE_PRY_IN_TESTS'] ||= 'false'
end
end
# Test with conditional debugging
def debug_enabled?
ENV['ENABLE_PRY_IN_TESTS'] == 'true'
end
def conditional_pry
binding.pry if debug_enabled?
end
Mock debugging scenarios test application behavior under debugging conditions without triggering actual debugging sessions. Stubbing binding.pry
calls allows testing of debugging-related logic and ensuring applications handle debugging gracefully.
class DebugAwareService
def process_with_debug(data, debug: false)
validate_input(data)
binding.pry if debug
result = transform_data(data)
binding.pry if debug && result.nil?
save_result(result) unless result.nil?
result
end
private
def validate_input(data); end
def transform_data(data); end
def save_result(result); end
end
# Testing debugging integration
RSpec.describe DebugAwareService do
let(:service) { described_class.new }
let(:test_data) { { id: 1, value: 'test' } }
describe '#process_with_debug' do
context 'when debugging enabled' do
it 'calls binding.pry at appropriate points' do
expect_any_instance_of(Binding).to receive(:pry).twice
service.process_with_debug(test_data, debug: true)
end
it 'processes data correctly even with debug calls' do
allow_any_instance_of(Binding).to receive(:pry)
result = service.process_with_debug(test_data, debug: true)
expect(result).not_to be_nil
end
end
context 'when debugging disabled' do
it 'does not call binding.pry' do
expect_any_instance_of(Binding).not_to receive(:pry)
service.process_with_debug(test_data, debug: false)
end
end
end
end
Integration testing with debugging requires test isolation strategies that prevent debugging sessions from blocking test execution while ensuring debugging functionality works correctly in development environments.
# Integration test for debugging-enabled controllers
RSpec.describe OrdersController, type: :controller do
describe 'debugging integration' do
context 'in development environment' do
before do
allow(Rails.env).to receive(:development?).and_return(true)
allow_any_instance_of(Binding).to receive(:pry).and_return(nil)
end
it 'processes orders with debugging support' do
order_params = {
items: [{ name: 'Test', price: 10.99, quantity: 2 }],
debug: true
}
post :create, params: { order: order_params }
expect(response).to have_http_status(:created)
expect(Order.count).to eq(1)
end
end
context 'in production environment' do
before do
allow(Rails.env).to receive(:production?).and_return(true)
end
it 'ignores debug parameters in production' do
order_params = {
items: [{ name: 'Test', price: 10.99, quantity: 2 }],
debug: true
}
# Should not attempt debugging in production
expect_any_instance_of(Binding).not_to receive(:pry)
post :create, params: { order: order_params }
expect(response).to have_http_status(:created)
end
end
end
end
Testing debugging utilities requires creating controlled scenarios where debugging behavior can be verified without interrupting test flow. Custom debugging helpers and debugging state management need comprehensive testing coverage.
class DebuggingHelper
class << self
def debug_if(condition)
binding.pry if condition && debugging_enabled?
end
def debug_on_error
yield
rescue StandardError => e
binding.pry if debugging_enabled?
raise
end
def debugging_enabled?
ENV['DEBUG'] == 'true' && !Rails.env.test?
end
def capture_debug_context(binding_context)
{
local_variables: binding_context.local_variables,
instance_variables: binding_context.receiver.instance_variables,
method_name: binding_context.frame_description
}
end
end
end
# Testing debugging utilities
RSpec.describe DebuggingHelper do
describe '.debug_if' do
context 'when condition is true and debugging enabled' do
before do
allow(described_class).to receive(:debugging_enabled?).and_return(true)
allow_any_instance_of(Binding).to receive(:pry)
end
it 'triggers debugging session' do
expect_any_instance_of(Binding).to receive(:pry)
described_class.debug_if(true)
end
end
context 'when condition is false' do
it 'does not trigger debugging' do
expect_any_instance_of(Binding).not_to receive(:pry)
described_class.debug_if(false)
end
end
end
describe '.debug_on_error' do
context 'when block raises error and debugging enabled' do
before do
allow(described_class).to receive(:debugging_enabled?).and_return(true)
allow_any_instance_of(Binding).to receive(:pry)
end
it 'triggers debugging before re-raising' do
expect_any_instance_of(Binding).to receive(:pry)
expect {
described_class.debug_on_error { raise StandardError, 'test error' }
}.to raise_error(StandardError, 'test error')
end
end
end
describe '.capture_debug_context' do
it 'captures binding context information' do
test_var = 'local_value'
context = described_class.capture_debug_context(binding)
expect(context[:local_variables]).to include(:test_var)
expect(context[:method_name]).to include('capture_debug_context')
end
end
end
Production Patterns
Production debugging requires careful implementation to avoid security vulnerabilities and performance degradation. Remote debugging capabilities should include authentication, connection limits, and automatic session timeouts to prevent unauthorized access and resource exhaustion.
class SecureRemoteDebugger
AUTHORIZED_IPS = ['10.0.0.0/24', '192.168.1.0/24'].freeze
MAX_CONCURRENT_SESSIONS = 3
SESSION_TIMEOUT = 300 # 5 minutes
class << self
def enable_if_authorized(ip_address, user_credentials)
return false unless authorized_ip?(ip_address)
return false unless valid_credentials?(user_credentials)
return false if session_limit_reached?
enable_remote_debugging
schedule_session_cleanup
true
end
def secure_breakpoint(condition: nil, user: nil)
return unless production_debugging_enabled?
return unless authorized_user?(user)
if condition.nil? || condition.call
binding.remote_pry
end
rescue => e
Rails.logger.error "Remote debugging error: #{e.message}"
end
private
def authorized_ip?(ip_address)
AUTHORIZED_IPS.any? { |range| IPAddr.new(range).include?(ip_address) }
end
def valid_credentials?(credentials)
# Implement secure credential validation
BCrypt::Password.new(ENV['DEBUG_PASSWORD_HASH']) == credentials
end
def session_limit_reached?
active_remote_sessions.count >= MAX_CONCURRENT_SESSIONS
end
def active_remote_sessions
# Track active debugging sessions
@sessions ||= []
end
def enable_remote_debugging
require 'pry-remote'
# Configure secure remote debugging
end
def schedule_session_cleanup
Thread.new do
sleep SESSION_TIMEOUT
cleanup_expired_sessions
end
end
def production_debugging_enabled?
ENV['PRODUCTION_DEBUG'] == 'true' && Rails.env.production?
end
def authorized_user?(user)
user&.admin? || user&.developer?
end
end
end
Application monitoring integration captures debugging session activity, performance metrics, and security events. Debugging sessions should log access attempts, duration, and actions performed for audit compliance and security monitoring.
class DebugMonitor
include Singleton
def initialize
@sessions = []
@metrics = Hash.new(0)
end
def track_session_start(user:, ip:, context:)
session = {
id: SecureRandom.uuid,
user: user,
ip: ip,
context: context,
started_at: Time.current,
commands: []
}
@sessions << session
@metrics[:total_sessions] += 1
log_session_event(:started, session)
notify_monitoring_system(:debug_session_started, session)
session[:id]
end
def track_command(session_id, command, result)
session = find_session(session_id)
return unless session
command_record = {
command: command,
executed_at: Time.current,
result_type: result.class.name,
success: !result.is_a?(Exception)
}
session[:commands] << command_record
@metrics[:total_commands] += 1
@metrics[:failed_commands] += 1 if result.is_a?(Exception)
log_command_execution(session, command_record)
end
def track_session_end(session_id)
session = find_session(session_id)
return unless session
session[:ended_at] = Time.current
session[:duration] = session[:ended_at] - session[:started_at]
log_session_event(:ended, session)
generate_session_report(session)
cleanup_session(session_id)
end
def current_metrics
{
active_sessions: @sessions.count { |s| s[:ended_at].nil? },
total_sessions: @metrics[:total_sessions],
total_commands: @metrics[:total_commands],
failed_commands: @metrics[:failed_commands],
average_session_duration: calculate_average_duration
}
end
private
def find_session(session_id)
@sessions.find { |s| s[:id] == session_id }
end
def log_session_event(event, session)
Rails.logger.info({
event: "debug_session_#{event}",
session_id: session[:id],
user: session[:user]&.id,
ip: session[:ip],
context: session[:context],
timestamp: Time.current
}.to_json)
end
def log_command_execution(session, command_record)
Rails.logger.debug({
event: 'debug_command_executed',
session_id: session[:id],
command: command_record[:command],
success: command_record[:success],
timestamp: command_record[:executed_at]
}.to_json)
end
def notify_monitoring_system(event, data)
# Integration with monitoring tools (DataDog, New Relic, etc.)
Metrics.increment("debug.#{event}")
Metrics.histogram('debug.session_duration', data[:duration]) if data[:duration]
end
def generate_session_report(session)
# Generate detailed session report for audit purposes
report = {
session_summary: session.except(:commands),
command_summary: {
total_commands: session[:commands].length,
successful_commands: session[:commands].count { |c| c[:success] },
failed_commands: session[:commands].count { |c| !c[:success] }
},
security_flags: detect_security_concerns(session)
}
store_audit_report(report)
end
def detect_security_concerns(session)
concerns = []
# Detect suspicious patterns
concerns << :excessive_commands if session[:commands].length > 100
concerns << :long_duration if session[:duration] > 1800 # 30 minutes
concerns << :system_access if session[:commands].any? { |c|
dangerous_patterns = ['system', 'exec', '`', 'File.', 'Dir.']
dangerous_patterns.any? { |pattern| c[:command].include?(pattern) }
}
concerns
end
def cleanup_session(session_id)
@sessions.reject! { |s| s[:id] == session_id }
end
def calculate_average_duration
completed_sessions = @sessions.select { |s| s[:duration] }
return 0 if completed_sessions.empty?
total_duration = completed_sessions.sum { |s| s[:duration] }
total_duration / completed_sessions.length
end
def store_audit_report(report)
# Store in audit database or send to compliance system
AuditLog.create!(
event_type: 'debug_session_completed',
details: report,
created_at: Time.current
)
end
end
Performance optimization for debugging-enabled applications requires careful consideration of debugging overhead and resource usage. Conditional debugging, lazy evaluation, and resource cleanup prevent debugging infrastructure from impacting production performance.
class PerformantDebugger
class << self
def smart_debug(threshold: 0.1, &block)
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
result = block.call
execution_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
if execution_time > threshold
binding.pry # Debug slow operations
end
result
end
def memory_aware_debug(max_objects: 1000)
initial_objects = ObjectSpace.count_objects[:T_OBJECT]
yield
final_objects = ObjectSpace.count_objects[:T_OBJECT]
objects_created = final_objects - initial_objects
if objects_created > max_objects
GC.start # Force garbage collection before debugging
binding.pry
end
end
def conditional_remote_debug(condition_proc)
# Avoid remote debugging overhead unless condition met
return unless condition_proc.call
return unless remote_debugging_available?
# Lazy load remote debugging dependencies
require 'pry-remote' unless defined?(PryRemote)
binding.remote_pry
rescue LoadError => e
Rails.logger.warn "Remote debugging unavailable: #{e.message}"
end
def resource_limited_debug(max_memory_mb: 100)
memory_usage = get_memory_usage_mb
if memory_usage < max_memory_mb
binding.pry
else
Rails.logger.warn "Debugging skipped: memory usage #{memory_usage}MB exceeds limit"
end
end
private
def remote_debugging_available?
# Check if remote debugging infrastructure is ready
ENV['REMOTE_DEBUG_HOST'] && ENV['REMOTE_DEBUG_PORT']
end
def get_memory_usage_mb
# Get current process memory usage
status_file = "/proc/#{Process.pid}/status"
return 0 unless File.exist?(status_file)
File.readlines(status_file).each do |line|
if line.start_with?('VmRSS:')
return line.split[1].to_i / 1024 # Convert KB to MB
end
end
0
end
end
end
# Usage in performance-critical applications
class HighVolumeProcessor
def process_batch(items)
PerformantDebugger.smart_debug(threshold: 0.05) do
items.each_slice(100) do |batch|
PerformantDebugger.memory_aware_debug(max_objects: 500) do
process_item_batch(batch)
end
end
end
end
def handle_critical_error(error, context)
PerformantDebugger.resource_limited_debug(max_memory_mb: 50) do
# Debug critical errors with memory constraints
analyze_error_context(error, context)
end
end
private
def process_item_batch(batch)
# High-volume processing logic
end
def analyze_error_context(error, context)
# Error analysis logic
end
end
Common Pitfalls
Debugging session interference occurs when multiple debugging entry points activate simultaneously, creating nested debugging contexts that confuse execution flow and variable access. Nested binding.pry
calls create stacked debugging sessions that require careful navigation to avoid losing execution context.
class ProblematicDebugging
def outer_method(data)
processed_data = preprocess(data)
binding.pry # First debugging entry point
result = inner_method(processed_data)
binding.pry # Second debugging entry point - creates confusion
result
end
def inner_method(data)
binding.pry # Third debugging entry point - nested debugging
data.each do |item|
binding.pry if item[:debug] # Fourth potential entry point
transform_item(item)
end
end
private
def preprocess(data); end
def transform_item(item); end
end
# Better approach: Controlled debugging with flags
class ImprovedDebugging
def initialize(debug_level: 0)
@debug_level = debug_level
@debug_stack = []
end
def outer_method(data)
debug_context(:outer_method) do
processed_data = preprocess(data)
controlled_debug(level: 1) # Only debug if level >= 1
result = inner_method(processed_data)
controlled_debug(level: 2) # Only debug if level >= 2
result
end
end
def inner_method(data)
debug_context(:inner_method) do
controlled_debug(level: 3)
data.each_with_index do |item, index|
conditional_debug(item[:debug] && index < 5) # Limit debugging iterations
transform_item(item)
end
end
end
private
def debug_context(method_name)
@debug_stack.push(method_name)
yield
ensure
@debug_stack.pop
end
def controlled_debug(level:)
return unless @debug_level >= level
return if @debug_stack.length > 3 # Prevent deep nesting
binding.pry
end
def conditional_debug(condition)
return unless condition
return if currently_debugging?
binding.pry
end
def currently_debugging?
# Check if already in a debugging session
caller.any? { |frame| frame.include?('pry') }
end
end
Variable modification during debugging sessions can create inconsistent program state that leads to unexpected behavior when execution resumes. Debugging sessions allow complete variable access, including modification of local variables, instance variables, and object state.
class StateModificationIssues
def calculate_total(items, tax_rate)
subtotal = 0
items.each do |item|
binding.pry # Dangerous debugging point
# During debugging, developer might modify variables:
# subtotal = 1000 # Accidental modification
# tax_rate = 0 # Testing different values
# items.clear # Destructive operation
subtotal += item[:price] * item[:quantity]
end
total = subtotal * (1 + tax_rate)
total.round(2)
end
end
# Safer debugging approach with state protection
class ProtectedStateDebugging
def calculate_total(items, tax_rate)
# Create debugging snapshot
original_state = {
items_count: items.length,
tax_rate: tax_rate,
items_checksum: items.hash
}
subtotal = 0
items.each do |item|
debug_with_protection(original_state) do
# Debugging within protected context
end
subtotal += item[:price] * item[:quantity]
end
validate_state_integrity(original_state, items, tax_rate)
total = subtotal * (1 + tax_rate)
total.round(2)
end
private
def debug_with_protection(original_state)
if debugging_enabled?
puts "=== DEBUGGING SESSION START ==="
puts "WARNING: Modifying variables may cause unexpected behavior"
puts "Original state: #{original_state}"
binding.pry
puts "=== DEBUGGING SESSION END ==="
end
end
def validate_state_integrity(original_state, items, tax_rate)
inconsistencies = []
inconsistencies << "items count changed" if items.length != original_state[:items_count]
inconsistencies << "tax rate modified" if tax_rate != original_state[:tax_rate]
inconsistencies << "items content modified" if items.hash != original_state[:items_checksum]
if inconsistencies.any?
Rails.logger.warn "State modified during debugging: #{inconsistencies.join(', ')}"
end
end
def debugging_enabled?
defined?(Pry) && ENV['DEBUG'] == 'true'
end
end
Thread safety violations occur when debugging multi-threaded applications without proper synchronization. Debugging sessions operate within single thread contexts, but shared state modifications during debugging can create race conditions and data corruption in concurrent environments.
class ThreadSafetyProblems
def initialize
@shared_counter = 0
@shared_data = {}
end
def concurrent_processing(items)
threads = items.each_slice(10).map do |batch|
Thread.new do
batch.each do |item|
binding.pry if item[:debug] # DANGEROUS: No thread synchronization
# These operations are not thread-safe during debugging:
@shared_counter += 1 # Race condition
@shared_data[item[:id]] = item # Concurrent hash modification
end
end
end
threads.each(&:join)
end
end
# Thread-safe debugging implementation
class ThreadSafeDebugging
def initialize
@shared_counter = Concurrent::AtomicFixnum.new(0)
@shared_data = Concurrent::Map.new
@debug_mutex = Mutex.new
@debug_semaphore = Mutex.new
end
def concurrent_processing(items)
threads = items.each_slice(10).map.with_index do |batch, thread_id|
Thread.new do
Thread.current[:id] = thread_id
batch.each do |item|
thread_safe_debug(item) if item[:debug]
# Thread-safe operations
@shared_counter.increment
@shared_data[item[:id]] = item
end
end
end
threads.each(&:join)
end
private
def thread_safe_debug(item)
# Ensure only one thread debugs at a time
return unless @debug_semaphore.try_lock
begin
thread_info = {
thread_id: Thread.current[:id],
thread_object_id: Thread.current.object_id,
shared_counter_value: @shared_counter.value,
shared_data_size: @shared_data.size
}
puts "=== THREAD-SAFE DEBUGGING ==="
puts "Thread info: #{thread_info}"
puts "Item: #{item}"
puts "WARNING: Shared state modifications may cause race conditions"
# Create thread-local copy for safe modification
local_item = item.dup
binding.pry
ensure
@debug_semaphore.unlock
end
rescue ThreadError => e
Rails.logger.warn "Thread debugging conflict: #{e.message}"
end
end
Performance degradation from debugging overhead becomes problematic in high-throughput applications when debugging statements remain active in production code. Each binding.pry
call incurs method call overhead and string evaluation costs even when debugging is disabled.
class PerformanceDegradation
def high_frequency_method(data)
# These debugging calls execute even when not debugging
binding.pry if Rails.env.development? # Environment check overhead
binding.pry if data[:debug] # Hash key lookup overhead
binding.pry if should_debug?(data) # Method call overhead
# Performance-critical processing
process_data(data)
end
def should_debug?(data)
# Complex condition evaluation happens every call
data[:user_id] == 'admin' &&
data[:created_at] > 1.hour.ago &&
Rails.cache.read("debug_mode_#{data[:user_id]}")
end
end
# Optimized debugging approach
class OptimizedDebugging
# Class-level debugging configuration
DEBUG_ENABLED = Rails.env.development? && ENV['ENABLE_DEBUG'] == 'true'
DEBUG_CACHE_TTL = 300 # 5 minutes
def initialize
@debug_conditions_cache = {}
@cache_expires_at = {}
end
def high_frequency_method(data)
# Fast path: skip debugging entirely if disabled globally
return process_data(data) unless DEBUG_ENABLED
# Cached condition evaluation
if should_debug_cached?(data)
binding.pry
end
process_data(data)
end
private
def should_debug_cached?(data)
cache_key = "debug_condition_#{data[:user_id]}"
current_time = Time.current
# Check cache expiration
if @cache_expires_at[cache_key] && current_time > @cache_expires_at[cache_key]
@debug_conditions_cache.delete(cache_key)
@cache_expires_at.delete(cache_key)
end
# Return cached result if available
return @debug_conditions_cache[cache_key] if @debug_conditions_cache.key?(cache_key)
# Evaluate condition and cache result
result = evaluate_debug_condition(data)
@debug_conditions_cache[cache_key] = result
@cache_expires_at[cache_key] = current_time + DEBUG_CACHE_TTL
result
end
def evaluate_debug_condition(data)
# Complex condition evaluation only when cache miss occurs
data[:user_id] == 'admin' &&
data[:created_at] > 1.hour.ago &&
Rails.cache.read("debug_mode_#{data[:user_id]}")
end
def process_data(data)
# Performance-critical processing implementation
end
end
# Compile-time debugging removal for production
class ProductionOptimized
if Rails.env.production?
# Define no-op debugging methods for production
def debug_breakpoint; end
def conditional_debug(condition); end
else
def debug_breakpoint
binding.pry
end
def conditional_debug(condition)
binding.pry if condition
end
end
def critical_method(data)
debug_breakpoint # No overhead in production
# Critical processing
process_data(data)
conditional_debug(data[:suspicious]) # No overhead in production
end
end
Reference
Core Debugging Commands
Command | Parameters | Returns | Description |
---|---|---|---|
step |
None | nil |
Execute next line, entering method calls |
next |
None | nil |
Execute next line, stepping over method calls |
continue |
None | nil |
Resume execution until next breakpoint |
finish |
None | nil |
Complete current method and return to caller |
up |
[count] |
nil |
Move up call stack frames |
down |
[count] |
nil |
Move down call stack frames |
break |
file:line | Class#method |
Breakpoint |
Set breakpoint at location |
breakpoints |
None | Array<Breakpoint> |
List all active breakpoints |
Binding Methods
Method | Parameters | Returns | Description |
---|---|---|---|
binding.pry |
None | nil |
Start debugging session at current location |
binding.remote_pry |
host, port |
nil |
Start remote debugging session |
local_variables |
None | Array<Symbol> |
List local variables in current binding |
local_variable_get(name) |
Symbol |
Object |
Get local variable value |
local_variable_set(name, value) |
Symbol, Object |
Object |
Set local variable value |
Breakpoint Management
Method | Parameters | Returns | Description |
---|---|---|---|
Pry::Byebug::Breakpoints.add_file(file, line) |
String, Integer |
Breakpoint |
Add file breakpoint |
Pry::Byebug::Breakpoints.add_method(klass, method) |
Class, Symbol |
Breakpoint |
Add method breakpoint |
Pry::Byebug::Breakpoints.delete(id) |
Integer |
Boolean |
Remove breakpoint by ID |
Pry::Byebug::Breakpoints.disable(id) |
Integer |
Boolean |
Disable breakpoint |
Pry::Byebug::Breakpoints.enable(id) |
Integer |
Boolean |
Enable breakpoint |
Configuration Options
Option | Type | Default | Description |
---|---|---|---|
Pry.config.auto_indent |
Boolean |
true |
Automatic code indentation |
Pry.config.correct_indent |
Boolean |
true |
Correct indentation on paste |
Pry.config.collision_warning |
Boolean |
false |
Warn about method name collisions |
Pry.config.should_load_rc |
Boolean |
true |
Load .pryrc configuration file |
Pry.config.should_load_local_rc |
Boolean |
true |
Load local .pryrc files |
Environment Variables
Variable | Values | Description |
---|---|---|
PRY |
0 , 1 |
Enable/disable pry loading |
PRY_BYEBUG |
0 , 1 |
Enable/disable pry-byebug features |
DISABLE_PRY |
1 |
Disable all pry functionality |
PRY_RESCUE |
0 , 1 |
Enable automatic exception debugging |
Exception Classes
Exception | Description |
---|---|
Pry::CommandError |
Invalid debugging command |
Pry::SyntaxError |
Syntax error in debugging session |
PryByebug::Breakpoints::FileNotFoundError |
Breakpoint file not found |
Debugging Session States
State | Description |
---|---|
running |
Normal program execution |
suspended |
Debugging session active |
stepping |
Step-by-step execution mode |
breaking |
Stopped at breakpoint |
finishing |
Completing current method |
Remote Debugging Configuration
Setting | Type | Description |
---|---|---|
host |
String |
Remote debugging server host |
port |
Integer |
Remote debugging server port |
wait |
Boolean |
Wait for client connection |
quiet |
Boolean |
Suppress connection messages |
Stack Frame Information
Method | Returns | Description |
---|---|---|
frame_description |
String |
Current frame method and location |
frame_type |
Symbol |
Frame type (:method, :block, :rescue) |
source_location |
Array |
File path and line number |