CrackedRuby logo

CrackedRuby

Infinite Loops and Loop Control

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 and break 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