Overview
Infinite loops are loops that continue executing indefinitely until explicitly terminated through break statements, exceptions, or external intervention. Ruby provides several mechanisms for creating and controlling infinite loops, each with distinct characteristics and use cases.
Ruby's loop control operates through several key constructs:
- Basic infinite loops:
loop
,while true
,until false
- Break statements:
break
andbreak value
- Next statements:
next
for continuing to next iteration - Redo statements:
redo
for repeating current iteration - Exception handling: Using rescue blocks within loops
# Basic infinite loop with break condition
loop do
user_input = gets.chomp
break if user_input == "quit"
puts "You entered: #{user_input}"
end
# While loop with complex break condition
counter = 0
while true
counter += 1
next if counter.even?
puts "Odd number: #{counter}"
break if counter > 10
end
Ruby's loop control flow allows for sophisticated program structures, particularly useful in interactive applications, background processing, and event-driven programming scenarios.
Basic Usage
Creating Infinite Loops
Ruby offers multiple approaches to create infinite loops, each with specific advantages:
# Method 1: Using loop block (recommended)
loop do
puts "This runs forever"
sleep 1
# Add break condition when needed
end
# Method 2: While true pattern
while true
user_command = gets.chomp
case user_command
when "help"
puts "Available commands: help, status, quit"
when "status"
puts "System running normally"
when "quit"
puts "Goodbye!"
break
else
puts "Unknown command: #{user_command}"
end
end
# Method 3: Until false pattern (less common)
until false
current_time = Time.now
puts "Current time: #{current_time}"
sleep 5
break if current_time.hour >= 17 # Stop after 5 PM
end
Loop Control Statements
Break statements terminate loop execution and optionally return values:
# Basic break
result = loop do
number = rand(100)
puts "Generated: #{number}"
break number if number > 90 # Return the number
end
puts "Final result: #{result}"
Next statements skip the remainder of the current iteration:
# Processing with next
(1..20).each do |i|
next if i.even? # Skip even numbers
next if i < 5 # Skip numbers less than 5
puts "Processing: #{i}"
end
Redo statements restart the current iteration from the beginning:
# User input validation with redo
loop do
print "Enter a positive number: "
input = gets.chomp.to_i
if input <= 0
puts "Invalid input, please try again"
redo
end
puts "You entered: #{input}"
break
end
Nested Loop Control
Managing control flow in nested loops requires careful consideration:
# Labeled breaks using catch/throw
catch :outer_loop do
(1..5).each do |i|
(1..5).each do |j|
product = i * j
if product > 15
puts "Breaking outer loop at #{i} * #{j}"
throw :outer_loop
end
puts "#{i} * #{j} = #{product}"
end
end
end
Advanced Usage
Loop Control with Blocks and Iterators
Ruby's block-based iteration provides sophisticated control mechanisms:
# Custom iterator with loop control
def process_data(data)
data.each_with_index do |item, index|
begin
result = complex_processing(item)
yield result, index if block_given?
rescue StandardError => e
puts "Error processing item #{index}: #{e.message}"
next # Continue with next item
end
end
end
# Usage
data = ["valid", nil, "another_valid", ""]
process_data(data) do |result, index|
puts "Processed item #{index}: #{result}"
end
State Management in Infinite Loops
Managing state across iterations requires careful design:
class LoopController
def initialize
@state = :waiting
@data_buffer = []
@retry_count = 0
end
def run
loop do
case @state
when :waiting
handle_waiting_state
when :processing
handle_processing_state
when :error
handle_error_state
when :shutdown
puts "Shutting down..."
break
end
sleep 0.1 # Prevent CPU spinning
end
end
private
def handle_waiting_state
if data_available?
@data_buffer = fetch_data
@state = :processing
end
end
def handle_processing_state
begin
process_buffer
@state = :waiting
@retry_count = 0
rescue ProcessingError => e
@retry_count += 1
@state = @retry_count < 3 ? :error : :shutdown
end
end
def handle_error_state
puts "Error occurred, retrying in #{@retry_count} seconds..."
sleep @retry_count
@state = :processing
end
end
Concurrent Loop Control
Thread-safe loop control requires synchronization mechanisms:
require 'thread'
class ThreadSafeLoop
def initialize
@running = true
@mutex = Mutex.new
@condition = ConditionVariable.new
end
def start_worker
Thread.new do
loop do
@mutex.synchronize do
@condition.wait(@mutex) unless @running
break unless @running
end
perform_work
sleep 1
end
end
end
def stop
@mutex.synchronize do
@running = false
@condition.broadcast
end
end
private
def perform_work
# Actual work implementation
puts "Working... #{Time.now}"
end
end
Loop Optimization Patterns
Performance-conscious loop design:
# Batch processing pattern
def process_in_batches(data, batch_size = 100)
data.each_slice(batch_size) do |batch|
begin
process_batch(batch)
rescue BatchProcessingError => e
puts "Batch failed, processing individually"
batch.each do |item|
begin
process_single_item(item)
rescue ItemProcessingError => e
puts "Item failed: #{e.message}"
next
end
end
end
end
end
# Memory-efficient streaming pattern
def process_stream
loop do
chunk = read_next_chunk
break if chunk.nil? || chunk.empty?
process_chunk(chunk)
# Explicit garbage collection hint for large data processing
GC.start if should_trigger_gc?
end
end
Error Handling & Debugging
Exception Handling in Loops
Robust error handling prevents infinite loops from crashing applications:
def robust_processing_loop
retry_count = 0
max_retries = 3
loop do
begin
data = fetch_data_from_source
process_data(data)
retry_count = 0 # Reset on success
rescue NetworkError => e
retry_count += 1
if retry_count <= max_retries
puts "Network error (#{retry_count}/#{max_retries}): #{e.message}"
sleep(retry_count * 2) # Exponential backoff
redo
else
puts "Max retries exceeded, skipping iteration"
next
end
rescue FatalError => e
puts "Fatal error encountered: #{e.message}"
break
rescue StandardError => e
puts "Unexpected error: #{e.message}"
puts e.backtrace.join("\n")
next # Continue processing
end
sleep 1 # Normal delay between iterations
end
end
Debugging Infinite Loops
Techniques for debugging problematic loops:
class LoopDebugger
def initialize(debug: false)
@debug = debug
@iteration_count = 0
@start_time = Time.now
end
def debug_loop
loop do
@iteration_count += 1
if @debug
log_debug_info
check_performance_warnings
end
# Main loop logic
result = perform_iteration
# Termination conditions
break if should_terminate?(result)
# Safety valve for runaway loops
if @iteration_count > 10000
puts "WARNING: Loop exceeded 10000 iterations"
break
end
end
end
private
def log_debug_info
if @iteration_count % 100 == 0
elapsed = Time.now - @start_time
puts "Iteration #{@iteration_count}, elapsed: #{elapsed.round(2)}s"
end
end
def check_performance_warnings
memory_usage = get_memory_usage
if memory_usage > 100_000_000 # 100MB
puts "WARNING: High memory usage detected: #{memory_usage} bytes"
end
end
end
Timeout and Circuit Breaker Patterns
Preventing infinite loops from hanging systems:
require 'timeout'
def safe_loop_with_timeout
Timeout::timeout(30) do # 30 second timeout
loop do
operation_start = Time.now
begin
perform_potentially_hanging_operation
rescue Timeout::Error
puts "Operation timed out"
next
end
# Prevent tight loops
elapsed = Time.now - operation_start
sleep(0.1) if elapsed < 0.1
end
end
rescue Timeout::Error
puts "Entire loop timed out after 30 seconds"
end
# Circuit breaker pattern
class CircuitBreaker
def initialize(failure_threshold: 5, timeout: 60)
@failure_count = 0
@failure_threshold = failure_threshold
@timeout = timeout
@last_failure_time = nil
@state = :closed # :closed, :open, :half_open
end
def call
case @state
when :closed
execute_with_monitoring { yield }
when :open
if Time.now - @last_failure_time > @timeout
@state = :half_open
execute_with_monitoring { yield }
else
raise CircuitOpenError
end
when :half_open
execute_with_monitoring { yield }
end
end
private
def execute_with_monitoring
begin
result = yield
@failure_count = 0
@state = :closed
result
rescue StandardError => e
@failure_count += 1
@last_failure_time = Time.now
@state = :open if @failure_count >= @failure_threshold
raise e
end
end
end
Production Patterns
Background Processing Loops
Production-ready background processing implementations:
class BackgroundProcessor
def initialize(logger: Logger.new(STDOUT))
@logger = logger
@shutdown = false
@stats = {
processed: 0,
errors: 0,
start_time: Time.now
}
setup_signal_handlers
end
def start
@logger.info "Starting background processor"
loop do
break if @shutdown
begin
job = fetch_next_job
if job
process_job(job)
@stats[:processed] += 1
else
sleep 1 # No jobs available
end
rescue StandardError => e
@stats[:errors] += 1
@logger.error "Job processing failed: #{e.message}"
@logger.error e.backtrace.join("\n")
# Exponential backoff on errors
sleep [2 ** @stats[:errors], 30].min
end
log_stats_periodically
end
@logger.info "Background processor stopped"
end
private
def setup_signal_handlers
Signal.trap("TERM") do
@logger.info "Received TERM signal, initiating shutdown"
@shutdown = true
end
Signal.trap("INT") do
@logger.info "Received INT signal, initiating shutdown"
@shutdown = true
end
end
def log_stats_periodically
if @stats[:processed] % 100 == 0
uptime = Time.now - @stats[:start_time]
rate = @stats[:processed] / uptime
@logger.info "Stats: #{@stats[:processed]} processed, " \
"#{@stats[:errors]} errors, " \
"#{rate.round(2)} jobs/second"
end
end
end
Health Check and Monitoring Integration
Monitoring loop health in production systems:
class MonitoredLoop
include Singleton
def initialize
@health_status = :healthy
@last_activity = Time.now
@metrics = {}
start_health_check_server
end
def run_main_loop
loop do
begin
@last_activity = Time.now
iteration_start = Time.now
perform_main_work
# Record metrics
iteration_time = Time.now - iteration_start
record_metric(:iteration_time, iteration_time)
@health_status = :healthy
rescue StandardError => e
@health_status = :degraded
record_metric(:error_count, 1)
handle_error(e)
end
# Check for shutdown signals
break if shutdown_requested?
adaptive_sleep
end
end
private
def start_health_check_server
Thread.new do
server = WEBrick::HTTPServer.new(Port: 8080)
server.mount_proc '/health' do |req, res|
res.content_type = 'application/json'
res.body = health_check_response.to_json
end
server.mount_proc '/metrics' do |req, res|
res.content_type = 'application/json'
res.body = @metrics.to_json
end
server.start
end
end
def health_check_response
time_since_activity = Time.now - @last_activity
{
status: @health_status,
last_activity: @last_activity.iso8601,
seconds_since_activity: time_since_activity.round(2),
healthy: time_since_activity < 30 && @health_status != :unhealthy
}
end
def adaptive_sleep
error_rate = @metrics[:error_count] || 0
base_sleep = error_rate > 5 ? 5 : 1
sleep base_sleep
end
end
Graceful Shutdown Patterns
Implementing clean shutdown procedures:
class GracefulShutdownLoop
def initialize
@shutdown_requested = false
@current_work = nil
@shutdown_timeout = 30
setup_shutdown_handlers
end
def start
puts "Starting main loop with graceful shutdown support"
loop do
break if @shutdown_requested
@current_work = fetch_work_item
if @current_work
process_with_shutdown_check(@current_work)
@current_work = nil
else
interruptible_sleep(5)
end
end
perform_cleanup
puts "Graceful shutdown completed"
end
private
def setup_shutdown_handlers
%w[TERM INT].each do |signal|
Signal.trap(signal) do
puts "Received #{signal} signal, initiating graceful shutdown"
@shutdown_requested = true
# Force shutdown after timeout
Thread.new do
sleep @shutdown_timeout
puts "Shutdown timeout exceeded, forcing exit"
exit! 1
end
end
end
end
def process_with_shutdown_check(work_item)
start_time = Time.now
loop do
# Process work in small chunks
chunk_processed = process_work_chunk(work_item)
# Check for shutdown between chunks
if @shutdown_requested
puts "Shutdown requested, saving work progress"
save_work_progress(work_item)
break
end
# Exit when work is complete
break if chunk_processed == :complete
# Prevent infinite processing
if Time.now - start_time > 300 # 5 minutes max
puts "Work item exceeded time limit"
break
end
end
end
def interruptible_sleep(duration)
duration.times do
return if @shutdown_requested
sleep 1
end
end
def perform_cleanup
puts "Performing cleanup operations"
# Close database connections
# Flush logs
# Save state
# Notify monitoring systems
if @current_work
puts "Saving incomplete work for later processing"
save_work_progress(@current_work)
end
end
end
Common Pitfalls
Infinite Loop Anti-Patterns
Common mistakes that lead to problematic infinite loops:
# PITFALL 1: Missing break condition
# BAD: Loop never terminates
counter = 0
loop do
counter += 1
puts counter
# Missing: break if counter >= 10
end
# GOOD: Clear termination condition
counter = 0
loop do
counter += 1
puts counter
break if counter >= 10
end
# PITFALL 2: Unreachable break condition
# BAD: Counter never reaches break condition
counter = 10
loop do
counter += 1 # Counter starts at 10 and keeps growing
puts counter
break if counter == 5 # This will never be true
end
# GOOD: Logical break condition
counter = 0
loop do
counter += 1
puts counter
break if counter >= 10
end
Resource Leak Prevention
Preventing resource leaks in long-running loops:
# PITFALL 3: Resource leaks in loops
# BAD: Files not closed properly
loop do
file = File.open("data.txt")
data = file.read
process_data(data)
# file never closed - resource leak!
break if should_stop?
end
# GOOD: Proper resource management
loop do
File.open("data.txt") do |file|
data = file.read
process_data(data)
end
break if should_stop?
end
# PITFALL 4: Memory accumulation
# BAD: Growing arrays in loops
results = []
loop do
result = expensive_calculation
results << result # Array grows indefinitely
break if some_condition?
end
# GOOD: Bounded collections or periodic cleanup
results = []
loop do
result = expensive_calculation
results << result
# Periodic cleanup
if results.size > 1000
process_results(results)
results.clear
end
break if some_condition?
end
Threading and Concurrency Issues
Thread safety problems in loops:
# PITFALL 5: Race conditions in shared state
# BAD: Unsafe shared counter
@counter = 0
threads = []
5.times do
threads << Thread.new do
loop do
@counter += 1 # Race condition!
puts "Thread #{Thread.current.object_id}: #{@counter}"
break if @counter >= 100
end
end
end
threads.each(&:join)
# GOOD: Thread-safe counter
require 'concurrent'
counter = Concurrent::AtomicFixnum.new(0)
threads = []
5.times do
threads << Thread.new do
loop do
current_value = counter.increment
puts "Thread #{Thread.current.object_id}: #{current_value}"
break if current_value >= 100
end
end
end
threads.each(&:join)
Performance Gotchas
Common performance problems in loops:
# PITFALL 6: Expensive operations in tight loops
# BAD: Expensive operations without throttling
loop do
# Database query on every iteration
users = User.all
process_users(users)
# No delay - excessive CPU usage
break if should_stop?
end
# GOOD: Throttling and caching
user_cache_time = nil
cached_users = nil
loop do
now = Time.now
# Cache users for 5 seconds
if user_cache_time.nil? || now - user_cache_time > 5
cached_users = User.all
user_cache_time = now
end
process_users(cached_users)
# Prevent excessive CPU usage
sleep 0.1
break if should_stop?
end
# PITFALL 7: String concatenation in loops
# BAD: Inefficient string building
result = ""
loop do
data = fetch_data
result += data # Creates new string object each time
break if data.empty?
end
# GOOD: Using string buffer
result = []
loop do
data = fetch_data
result << data # Append to array
break if data.empty?
end
final_result = result.join
Reference
Loop Control Methods
Method | Purpose | Returns | Usage |
---|---|---|---|
loop { block } |
Execute block indefinitely | Last expression or break value | loop { break if condition } |
break |
Exit current loop | nil or specified value |
break or break value |
break value |
Exit loop with return value | Specified value | result = loop { break 42 if done? } |
next |
Skip to next iteration | Continues loop | next if condition |
next value |
Skip with value (limited use) | Continues iteration | next processed_value |
redo |
Restart current iteration | Repeats current iteration | redo if retry_needed? |
Loop Constructs Comparison
Construct | Syntax | Best For | Memory Impact |
---|---|---|---|
loop do...end |
loop { block } |
General infinite loops | Minimal |
while true |
while condition |
Condition-based loops | Minimal |
until false |
until condition |
Negative condition loops | Minimal |
for i in (1..) |
for var in range |
Infinite ranges | Higher (range object) |
Exception Handling in Loops
Pattern | Purpose | Example |
---|---|---|
rescue inside loop |
Handle errors per iteration | begin...rescue...end |
rescue outside loop |
Handle loop-level errors | loop do...end rescue ErrorType |
ensure in loop |
Cleanup per iteration | begin...ensure...end |
retry in loop |
Retry failed iteration | rescue SomeError; retry |
Signal Handling
Signal | Purpose | Handler Pattern |
---|---|---|
TERM |
Graceful shutdown | Signal.trap("TERM") { @shutdown = true } |
INT |
Interrupt (Ctrl+C) | Signal.trap("INT") { @shutdown = true } |
HUP |
Reload configuration | Signal.trap("HUP") { reload_config } |
USR1 |
Custom signal 1 | Signal.trap("USR1") { toggle_debug } |
USR2 |
Custom signal 2 | Signal.trap("USR2") { dump_stats } |
Thread Safety Patterns
Pattern | Thread Safety | Performance | Complexity |
---|---|---|---|
Mutex#synchronize |
High | Medium | Low |
Queue operations |
High | High | Low |
Concurrent::AtomicFixnum |
High | High | Low |
Thread.current[:var] |
High | High | Medium |
Instance variables | None | High | Low |
Memory Management
Technique | Memory Impact | Use Case |
---|---|---|
Explicit GC.start |
Reduces usage | Large data processing |
Array clearing | Immediate release | Bounded collections |
Block-local variables | Automatic cleanup | Resource management |
Weak references | Prevents retention | Observer patterns |
Object pooling | Stable usage | High allocation loops |
Performance Optimization
Optimization | Impact | Complexity | Trade-off |
---|---|---|---|
Sleep delays | High CPU reduction | Low | Responsiveness |
Batch processing | High throughput | Medium | Memory usage |
Adaptive timing | Balanced performance | High | Implementation complexity |
Connection pooling | High I/O efficiency | Medium | Resource management |
Caching strategies | Variable improvement | Medium | Memory vs. freshness |