CrackedRuby logo

CrackedRuby

Regexp Timeout

Ruby's Regexp timeout mechanism prevents regular expression operations from consuming excessive CPU time by terminating long-running pattern matches.

Core Built-in Classes Regexp and MatchData
2.7.8

Overview

Regexp timeout functionality in Ruby provides protection against regular expression denial of service (ReDoS) attacks and runaway regex operations. Ruby implements timeouts through the Regexp.timeout= class method and raises Regexp::TimeoutError when operations exceed the configured duration.

The timeout mechanism operates at the regex engine level, monitoring pattern matching operations during execution. Ruby checks the elapsed time periodically during regex processing and terminates the operation when the timeout threshold is exceeded. This protection applies to all regex operations including match, scan, gsub, and string methods that use regular expressions internally.

Regexp.timeout = 1.0  # Set 1 second timeout
pattern = /a+b+c+/
text = "a" * 10000 + "c"  # No 'b' characters

begin
  pattern.match(text)
rescue Regexp::TimeoutError
  puts "Regex operation timed out"
end

The timeout value applies globally to all regex operations within the Ruby process. Ruby does not provide per-regex or per-operation timeout configuration. The timeout checking occurs during the regex engine's internal processing loops, not as a separate thread or signal-based mechanism.

# Check current timeout setting
puts Regexp.timeout  # => nil (no timeout set)

Regexp.timeout = 0.5
puts Regexp.timeout  # => 0.5

# Disable timeout
Regexp.timeout = nil

Ruby's timeout implementation works with both the Onigmo regex engine (Ruby 2.0+) and provides consistent behavior across different Ruby implementations. The timeout precision depends on the regex engine's internal checking frequency, which occurs at specific points during pattern matching rather than continuously.

Basic Usage

Setting a global timeout applies to all subsequent regex operations in the current Ruby process. The timeout value accepts floating-point numbers representing seconds, with sub-second precision supported.

# Set 2-second timeout for all regex operations
Regexp.timeout = 2.0

# This will complete normally
/quick/.match("quick brown fox")  # => #<MatchData "quick">

# This may timeout with catastrophic backtracking
pattern = /^(a+)+b/
dangerous_input = "a" * 1000

begin
  result = pattern.match(dangerous_input)
  puts "Match completed: #{result}"
rescue Regexp::TimeoutError => e
  puts "Operation timed out: #{e.message}"
end

String methods that use regular expressions internally also respect the timeout setting. This includes String#gsub, String#scan, String#split, and String#sub.

Regexp.timeout = 1.0
text = "a" * 5000 + "x"

# These operations are also protected by timeout
begin
  result = text.gsub(/^(a+)+x/, "replaced")
rescue Regexp::TimeoutError
  puts "gsub operation timed out"
end

begin
  matches = text.scan(/a+/)
rescue Regexp::TimeoutError
  puts "scan operation timed out"
end

The timeout setting persists until explicitly changed or the Ruby process terminates. Child processes inherit the parent's timeout setting at the time of forking, but subsequent changes in either process do not affect the other.

Regexp.timeout = 1.0
puts "Parent timeout: #{Regexp.timeout}"

if fork
  # Parent process
  Process.wait
  puts "Parent after fork: #{Regexp.timeout}"  # Still 1.0
else
  # Child process
  puts "Child timeout: #{Regexp.timeout}"      # Inherited: 1.0
  Regexp.timeout = 2.0
  puts "Child modified: #{Regexp.timeout}"     # Now 2.0
  exit
end

Compiled regular expressions created before setting a timeout will still be subject to the current timeout value when executed. Ruby applies timeouts during pattern matching execution, not during regex compilation.

# Compile regex before setting timeout
pattern = /^(a*)*b/

# Set timeout after compilation
Regexp.timeout = 0.5

# Timeout still applies during execution
begin
  pattern.match("a" * 1000)
rescue Regexp::TimeoutError
  puts "Pre-compiled regex still times out"
end

Error Handling & Debugging

Regexp::TimeoutError inherits from RegexpError, which inherits from StandardError. This exception hierarchy allows for granular error handling when dealing with various regex-related problems.

Regexp.timeout = 0.1

begin
  /^(a+)+b/.match("a" * 500)
rescue Regexp::TimeoutError => e
  puts "Timeout error: #{e.class}"        # Regexp::TimeoutError
  puts "Message: #{e.message}"            # Regexp::TimeoutError
  puts "Backtrace: #{e.backtrace.first}"  # Line where timeout occurred
rescue RegexpError => e
  puts "General regex error: #{e.message}"
rescue StandardError => e
  puts "Other error: #{e.message}"
end

The timeout error provides minimal debugging information by default. The error message simply states "Regexp::TimeoutError" without additional context about the specific pattern or input that caused the timeout.

def safe_regex_match(pattern, text, context = nil)
  pattern.match(text)
rescue Regexp::TimeoutError => e
  # Add context to timeout errors for debugging
  error_msg = "Regex timeout"
  error_msg += " in #{context}" if context
  error_msg += " - pattern: #{pattern.inspect}"
  error_msg += " - input length: #{text.length}"
  
  puts error_msg
  nil  # Return nil instead of raising
end

# Usage with debugging context
result = safe_regex_match(/^(a+)+b/, "a" * 1000, "user input validation")

Debugging timeout issues requires identifying problematic patterns and inputs. Catastrophic backtracking often occurs with nested quantifiers, alternation with overlapping alternatives, and patterns that create exponential search spaces.

# Helper method to test regex performance safely
def benchmark_regex(pattern, test_inputs, timeout = 1.0)
  original_timeout = Regexp.timeout
  Regexp.timeout = timeout
  
  test_inputs.each_with_index do |input, index|
    start_time = Time.now
    
    begin
      result = pattern.match(input)
      duration = Time.now - start_time
      puts "Input #{index}: #{duration.round(3)}s - #{result ? 'matched' : 'no match'}"
    rescue Regexp::TimeoutError
      duration = Time.now - start_time
      puts "Input #{index}: #{duration.round(3)}s - TIMEOUT"
    end
  end
ensure
  Regexp.timeout = original_timeout
end

# Test problematic pattern with various inputs
problematic_pattern = /^(a*)*b/
test_cases = [
  "ab",           # Fast case
  "a" * 10 + "b", # Slower case
  "a" * 50 + "b", # Much slower
  "a" * 100       # No 'b' - worst case
]

benchmark_regex(problematic_pattern, test_cases)

Timeout errors in production applications should be logged with sufficient context for debugging. Consider capturing pattern source, input characteristics, and application context when timeouts occur.

class RegexTimeoutLogger
  def self.log_timeout(pattern, input, context = {})
    {
      timestamp: Time.now.iso8601,
      pattern: pattern.source,
      pattern_options: pattern.options,
      input_length: input.length,
      input_encoding: input.encoding.name,
      context: context,
      backtrace: caller(1, 5)  # Limited backtrace for context
    }
  end
end

def protected_match(pattern, input, context = {})
  pattern.match(input)
rescue Regexp::TimeoutError => e
  timeout_info = RegexTimeoutLogger.log_timeout(pattern, input, context)
  Rails.logger.warn("Regex timeout: #{timeout_info}") if defined?(Rails)
  nil
end

Performance & Memory

Regexp timeouts primarily address CPU performance rather than memory consumption. However, certain regex patterns can consume significant memory during execution, particularly those involving large capture groups or extensive backtracking.

The timeout mechanism adds minimal overhead to regex operations. Ruby checks elapsed time at strategic points during pattern matching, typically during backtracking operations and loops within the regex engine. This checking frequency balances timeout responsiveness with performance impact.

require 'benchmark'

# Compare performance with and without timeout
text = "x" * 1000
simple_pattern = /x+/

# Measure overhead of timeout checking
Benchmark.bm(20) do |x|
  x.report("No timeout:") do
    Regexp.timeout = nil
    10000.times { simple_pattern.match(text) }
  end
  
  x.report("With timeout:") do
    Regexp.timeout = 5.0
    10000.times { simple_pattern.match(text) }
  end
end

# Results typically show <5% overhead for simple patterns

Memory usage patterns change significantly when timeouts prevent catastrophic backtracking. Runaway regex operations can allocate substantial memory for backtracking stacks and intermediate results.

# Monitor memory usage during regex operations
def memory_usage_mb
  `ps -o pid,rss -p #{Process.pid}`.split("\n").last.split.last.to_i / 1024.0
end

def test_memory_impact(pattern, input, description)
  puts "\n#{description}"
  
  initial_memory = memory_usage_mb
  puts "Initial memory: #{initial_memory.round(1)} MB"
  
  begin
    start_time = Time.now
    result = pattern.match(input)
    duration = Time.now - start_time
    
    peak_memory = memory_usage_mb
    puts "Peak memory: #{peak_memory.round(1)} MB (+#{(peak_memory - initial_memory).round(1)})"
    puts "Duration: #{duration.round(3)}s"
    puts "Result: #{result ? 'matched' : 'no match'}"
    
  rescue Regexp::TimeoutError
    timeout_memory = memory_usage_mb
    puts "Memory at timeout: #{timeout_memory.round(1)} MB (+#{(timeout_memory - initial_memory).round(1)})"
    puts "Operation timed out"
  end
  
  GC.start  # Force garbage collection
  final_memory = memory_usage_mb
  puts "After GC: #{final_memory.round(1)} MB"
end

Regexp.timeout = 2.0

# Test memory usage with different scenarios
test_memory_impact(/^(a+)+b/, "a" * 1000, "Catastrophic backtracking")
test_memory_impact(/a+/, "a" * 100000, "Large simple match")
test_memory_impact(/(.+)*/, "x" * 5000, "Nested quantifiers")

Timeout values should balance user experience with security requirements. Very short timeouts may interrupt legitimate operations, while long timeouts may not effectively prevent attacks.

# Configuration strategy for different environments
class RegexTimeoutConfig
  TIMEOUTS = {
    development: 10.0,    # Generous for debugging
    test: 5.0,           # Moderate for test speed
    production: 1.0      # Strict for security
  }.freeze
  
  def self.configure_for_environment(env = Rails.env)
    timeout_value = TIMEOUTS[env.to_sym] || TIMEOUTS[:production]
    Regexp.timeout = timeout_value
    
    puts "Regex timeout set to #{timeout_value}s for #{env} environment"
  end
end

# Usage in Rails application
RegexTimeoutConfig.configure_for_environment

Performance testing should include timeout scenarios to ensure application resilience. Measure both successful operations and timeout cases to understand the complete performance profile.

def performance_test_suite(patterns_and_inputs)
  results = []
  
  patterns_and_inputs.each do |pattern, inputs, description|
    puts "\nTesting: #{description}"
    
    inputs.each_with_index do |input, index|
      times = []
      timeouts = 0
      
      # Run multiple iterations for statistical significance
      10.times do
        start_time = Time.now
        
        begin
          pattern.match(input)
          times << Time.now - start_time
        rescue Regexp::TimeoutError
          times << Regexp.timeout  # Record timeout duration
          timeouts += 1
        end
      end
      
      avg_time = times.sum / times.length
      results << {
        pattern: pattern.source,
        input_length: input.length,
        avg_time: avg_time,
        timeout_rate: timeouts / 10.0,
        description: "#{description} - input #{index}"
      }
      
      puts "  Input #{index} (#{input.length} chars): #{avg_time.round(4)}s avg, #{timeouts}/10 timeouts"
    end
  end
  
  results
end

Production Patterns

Production applications require systematic approaches to regex timeout configuration and monitoring. Different application types have varying requirements for timeout strictness and error handling strategies.

Web applications commonly set conservative timeout values to prevent request timeouts caused by user-provided input. The timeout should be shorter than the application's overall request timeout to allow for proper error handling and response generation.

# Rails application configuration
class Application < Rails::Application
  config.after_initialize do
    # Set regex timeout to 80% of request timeout
    request_timeout = Rails.application.config.request_timeout || 30
    regex_timeout = [request_timeout * 0.8, 5.0].min
    
    Regexp.timeout = regex_timeout
    Rails.logger.info "Regex timeout configured: #{regex_timeout}s"
  end
end

# Middleware to handle regex timeouts gracefully
class RegexTimeoutHandler
  def initialize(app)
    @app = app
  end
  
  def call(env)
    @app.call(env)
  rescue Regexp::TimeoutError => e
    # Log the timeout with request context
    request_id = env['HTTP_X_REQUEST_ID']
    user_agent = env['HTTP_USER_AGENT']
    
    Rails.logger.warn(
      "Regex timeout in request #{request_id} " \
      "from #{user_agent}: #{e.backtrace.first}"
    )
    
    # Return appropriate error response
    [
      422,
      { 'Content-Type' => 'application/json' },
      [{ error: 'Input processing timeout', code: 'REGEX_TIMEOUT' }.to_json]
    ]
  end
end

Input validation systems should implement fallback strategies when regex operations timeout. Consider using simpler validation rules or alternative processing methods when timeouts occur.

class InputValidator
  def initialize(timeout: 1.0)
    @original_timeout = Regexp.timeout
    @validation_timeout = timeout
  end
  
  def validate_with_fallback(input, primary_pattern, fallback_pattern = nil)
    set_validation_timeout
    
    # Try primary validation pattern
    result = primary_pattern.match(input)
    { valid: !result.nil?, method: 'primary', pattern: primary_pattern.source }
    
  rescue Regexp::TimeoutError
    return fallback_validation(input, fallback_pattern) if fallback_pattern
    
    # No fallback available - reject input
    { valid: false, method: 'timeout', error: 'Validation timeout' }
    
  ensure
    restore_original_timeout
  end
  
  private
  
  def set_validation_timeout
    Regexp.timeout = @validation_timeout
  end
  
  def restore_original_timeout
    Regexp.timeout = @original_timeout
  end
  
  def fallback_validation(input, fallback_pattern)
    result = fallback_pattern.match(input)
    { valid: !result.nil?, method: 'fallback', pattern: fallback_pattern.source }
  rescue Regexp::TimeoutError
    { valid: false, method: 'fallback_timeout', error: 'Fallback validation timeout' }
  end
end

# Usage with progressive validation complexity
validator = InputValidator.new(timeout: 0.5)

# Complex email validation with simple fallback
complex_email = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
simple_email = /@/

result = validator.validate_with_fallback(
  user_input,
  complex_email,
  simple_email
)

if result[:valid]
  puts "Email validation passed using #{result[:method]} method"
else
  puts "Email validation failed: #{result[:error]}"
end

Monitoring regex timeout occurrences provides insights into application behavior and potential security issues. Track timeout frequency, patterns involved, and input characteristics to identify problematic areas.

class RegexTimeoutMonitor
  def self.start_monitoring
    @timeout_stats = Hash.new(0)
    @pattern_stats = Hash.new { |h, k| h[k] = { count: 0, inputs: [] } }
    
    # Monkey patch to capture timeout events
    original_match = Regexp.instance_method(:match)
    
    Regexp.define_method(:match) do |str|
      result = original_match.bind(self).call(str)
      result
    rescue Regexp::TimeoutError => e
      RegexTimeoutMonitor.record_timeout(self, str)
      raise
    end
  end
  
  def self.record_timeout(pattern, input)
    @timeout_stats[:total] += 1
    @timeout_stats[:this_hour] += 1 if @last_reset.nil? || Time.now - @last_reset < 3600
    
    pattern_key = pattern.source
    @pattern_stats[pattern_key][:count] += 1
    @pattern_stats[pattern_key][:inputs] << {
      length: input.length,
      encoding: input.encoding.name,
      timestamp: Time.now
    }
    
    # Keep only recent samples to prevent memory growth
    samples = @pattern_stats[pattern_key][:inputs]
    @pattern_stats[pattern_key][:inputs] = samples.last(10) if samples.length > 10
  end
  
  def self.stats_report
    return "No timeout data available" if @timeout_stats.empty?
    
    report = "Regex Timeout Statistics:\n"
    report += "Total timeouts: #{@timeout_stats[:total]}\n"
    report += "This hour: #{@timeout_stats[:this_hour]}\n\n"
    
    @pattern_stats.each do |pattern, stats|
      report += "Pattern: #{pattern}\n"
      report += "  Timeouts: #{stats[:count]}\n"
      
      if stats[:inputs].any?
        avg_length = stats[:inputs].map { |i| i[:length] }.sum / stats[:inputs].length
        report += "  Avg input length: #{avg_length}\n"
      end
      
      report += "\n"
    end
    
    report
  end
  
  def self.reset_hourly_stats
    @timeout_stats[:this_hour] = 0
    @last_reset = Time.now
  end
end

# Start monitoring in production
RegexTimeoutMonitor.start_monitoring if Rails.env.production?

# Periodic reporting (e.g., via cron job or background worker)
every 1.hour do
  puts RegexTimeoutMonitor.stats_report
  RegexTimeoutMonitor.reset_hourly_stats
end

Load testing should include scenarios that exercise regex timeout behavior. Ensure that timeout handling doesn't create additional bottlenecks or cascade failures in the application.

# Load testing helper for regex-intensive operations
class RegexLoadTester
  def initialize(concurrent_requests: 10, timeout: 1.0)
    @concurrent_requests = concurrent_requests
    @timeout = timeout
  end
  
  def test_endpoint_resilience(url, payloads)
    original_timeout = Regexp.timeout
    Regexp.timeout = @timeout
    
    results = Concurrent::Array.new
    
    # Create thread pool for concurrent requests
    pool = Concurrent::ThreadPoolExecutor.new(
      min_threads: @concurrent_requests,
      max_threads: @concurrent_requests
    )
    
    payloads.each do |payload|
      @concurrent_requests.times do |i|
        pool.post do
          start_time = Time.now
          
          begin
            response = send_request(url, payload)
            duration = Time.now - start_time
            
            results << {
              thread: i,
              payload_size: payload.length,
              duration: duration,
              status: response.status,
              success: response.status == 200
            }
            
          rescue StandardError => e
            duration = Time.now - start_time
            
            results << {
              thread: i,
              payload_size: payload.length,
              duration: duration,
              error: e.class.name,
              success: false
            }
          end
        end
      end
    end
    
    pool.shutdown
    pool.wait_for_termination(60)  # Wait up to 60 seconds
    
    analyze_results(results.to_a)
    
  ensure
    Regexp.timeout = original_timeout
  end
  
  private
  
  def analyze_results(results)
    total_requests = results.length
    successful_requests = results.count { |r| r[:success] }
    avg_duration = results.map { |r| r[:duration] }.sum / total_requests
    
    puts "Load Test Results:"
    puts "Total requests: #{total_requests}"
    puts "Successful: #{successful_requests} (#{(successful_requests.to_f / total_requests * 100).round(1)}%)"
    puts "Average duration: #{avg_duration.round(3)}s"
    
    # Group errors by type
    errors = results.reject { |r| r[:success] }.group_by { |r| r[:error] }
    errors.each do |error_type, instances|
      puts "#{error_type}: #{instances.length} occurrences"
    end
  end
end

Common Pitfalls

Regex timeout configuration affects all regex operations in the Ruby process, including libraries and gems that use regular expressions internally. This global nature can cause unexpected timeouts in third-party code that was not designed to handle regex timeouts.

# Timeout affects gem code that uses regex
Regexp.timeout = 0.1  # Very short timeout

require 'uri'

# This may timeout if URI parsing uses complex regex
begin
  uri = URI.parse("https://example.com/path?param=value")
rescue Regexp::TimeoutError
  puts "URI parsing timed out - timeout too strict"
end

# JSON parsing may also be affected
require 'json'

begin
  data = JSON.parse('{"key": "value"}')
rescue Regexp::TimeoutError
  puts "JSON parsing timed out"
end

Thread safety issues arise when multiple threads modify the global timeout setting simultaneously. The timeout value is process-global, not thread-local, leading to race conditions in multi-threaded applications.

# Problematic: Multiple threads changing timeout
Thread.new do
  Regexp.timeout = 1.0
  # Thread expects 1 second timeout
  /complex+pattern/.match(user_input)
end

Thread.new do
  Regexp.timeout = 0.1  # This affects ALL threads
  # Previous thread now has 0.1s timeout unexpectedly
  /other+pattern/.match(other_input)
end

A safer approach uses timeout restoration or thread-specific timeout management:

class ThreadSafeRegexTimeout
  def self.with_timeout(timeout_value)
    original_timeout = Regexp.timeout
    Regexp.timeout = timeout_value
    
    yield
  ensure
    Regexp.timeout = original_timeout
  end
end

# Usage ensures timeout restoration
ThreadSafeRegexTimeout.with_timeout(2.0) do
  dangerous_pattern.match(user_input)
end

# Original timeout value is restored after block

Timeout precision varies based on regex complexity and engine implementation. Ruby doesn't guarantee exact timeout precision - the actual timeout may be longer than specified, particularly for simple patterns that complete quickly between timeout checks.

# Timeout checking frequency demonstration
def measure_actual_timeout(pattern, input, expected_timeout)
  Regexp.timeout = expected_timeout
  start_time = Time.now
  
  begin
    pattern.match(input)
  rescue Regexp::TimeoutError
    actual_timeout = Time.now - start_time
    puts "Expected: #{expected_timeout}s, Actual: #{actual_timeout.round(3)}s"
    return actual_timeout
  end
  
  puts "Operation completed before timeout"
  nil
end

# Test with different pattern complexities
patterns = [
  [/^(a+)+b/, "Simple catastrophic backtracking"],
  [/^(a*)*b/, "Complex catastrophic backtracking"],
  [/(a|b)*abb/, "Alternation with backtracking"]
]

patterns.each do |pattern, description|
  puts "\n#{description}:"
  measure_actual_timeout(pattern, "a" * 100, 0.5)
end

String interpolation in regex patterns can create unexpected timeout behavior when the interpolated content contains regex metacharacters. Timeout applies to the final pattern, not the interpolation process.

# Dangerous: User input in regex pattern
user_input = "a" * 1000
pattern_string = "^(#{user_input})+b"  # Creates problematic pattern

begin
  pattern = Regexp.new(pattern_string)
  pattern.match("test")
rescue Regexp::TimeoutError
  puts "Pattern created from user input caused timeout"
end

# Safer: Escape user input or use literal matching
escaped_pattern = "^(#{Regexp.escape(user_input)})+b"
safe_pattern = Regexp.new(escaped_pattern)

Capturing groups and backreferences can amplify timeout issues by increasing the complexity of backtracking operations. Patterns with many capture groups may timeout faster than equivalent non-capturing patterns.

# Compare capturing vs non-capturing groups
Regexp.timeout = 1.0
input = "a" * 500

# Many capturing groups - more likely to timeout
capturing = /^(a+)(b+)(c+)(d+)*$/
begin
  capturing.match(input)
  puts "Capturing groups: completed"
rescue Regexp::TimeoutError
  puts "Capturing groups: timeout"
end

# Non-capturing groups - less backtracking overhead
non_capturing = /^(?:a+)(?:b+)(?:c+)(?:d+)*$/
begin
  non_capturing.match(input)
  puts "Non-capturing groups: completed"
rescue Regexp::TimeoutError
  puts "Non-capturing groups: timeout"
end

Exception handling for timeout errors must consider that timeouts can occur in unexpected locations. Regex operations embedded in string methods, case statements, and other Ruby constructs can all trigger timeouts.

# Timeout can occur in unexpected places
Regexp.timeout = 0.1

user_input = "a" * 1000
problematic_data = {
  "a" * 500 => "value1",
  "b" * 500 => "value2"
}

begin
  # Case statement uses === internally, which may use regex
  case user_input
  when /^(a+)+$/
    puts "Matched first pattern"
  when /^(b+)+$/
    puts "Matched second pattern"
  else
    puts "No match"
  end
rescue Regexp::TimeoutError
  puts "Timeout in case statement"
end

begin
  # Hash key lookup with regex can timeout
  result = problematic_data.find { |key, value| key =~ /^(a+)+x/ }
rescue Regexp::TimeoutError
  puts "Timeout in hash lookup"
end

Testing regex timeout behavior requires careful consideration of timing variability and system load. Tests should account for timeout precision limitations and system performance differences.

# Robust timeout testing approach
class RegexTimeoutTest
  def self.test_timeout_behavior(pattern, input, expected_timeout, tolerance: 0.2)
    original_timeout = Regexp.timeout
    Regexp.timeout = expected_timeout
    
    measurements = []
    timeout_occurred = false
    
    # Multiple measurements for reliability
    5.times do
      start_time = Time.now
      
      begin
        pattern.match(input)
        measurements << Time.now - start_time
      rescue Regexp::TimeoutError
        measurements << Time.now - start_time
        timeout_occurred = true
      end
    end
    
    avg_time = measurements.sum / measurements.length
    
    {
      timeout_expected: timeout_occurred,
      average_time: avg_time,
      within_tolerance: (avg_time - expected_timeout).abs <= tolerance,
      measurements: measurements
    }
  ensure
    Regexp.timeout = original_timeout
  end
end

# Test with known problematic pattern
result = RegexTimeoutTest.test_timeout_behavior(
  /^(a+)+b/,
  "a" * 100,
  1.0
)

puts "Timeout occurred: #{result[:timeout_expected]}"
puts "Average time: #{result[:average_time].round(3)}s"
puts "Within tolerance: #{result[:within_tolerance]}"

Reference

Core Methods

Method Parameters Returns Description
Regexp.timeout none Float or nil Returns current global timeout value in seconds
Regexp.timeout= timeout (Float or nil) Float or nil Sets global timeout for regex operations

Exception Hierarchy

StandardError
└── RegexpError
    └── Regexp::TimeoutError

Timeout Behavior

Scenario Timeout Applied Notes
Regexp#match Yes Direct regex matching operations
String#match Yes String methods using regex internally
String#gsub Yes Replacement operations with regex
String#scan Yes Global regex matching
String#split Yes When using regex as delimiter
case/when with regex Yes Pattern matching in case statements
Regex compilation No Only matching operations are timed

Configuration Values

Timeout Value Behavior Use Case
nil No timeout (default) Development, trusted input
0.1 - 0.5 Very strict High-security environments
1.0 - 5.0 Moderate Production web applications
10.0+ Lenient Batch processing, complex patterns

Common Timeout-Prone Patterns

# Catastrophic backtracking patterns
/^(a+)+b/                    # Nested quantifiers
/^(a*)*b/                    # Nested optional quantifiers  
/(a|a)*b/                    # Redundant alternation
/^(a+)+$/                    # Anchored nested quantifiers
/^.*a.*b.*$/                 # Multiple .* with anchors

# Safer alternatives
/^a+b/                       # Remove nesting
/^a*b/                       # Single quantifier
/^a*b/                       # Remove redundancy
/^a+$/                       # Single quantifier with anchor
/^[^a]*a[^b]*b[^$]*$/       # Character classes instead of .*

Environment-Specific Configurations

# Development
Regexp.timeout = nil          # No timeout for debugging

# Testing  
Regexp.timeout = 5.0         # Generous timeout for test stability

# Staging
Regexp.timeout = 2.0         # Production-like with tolerance

# Production
Regexp.timeout = 1.0         # Strict timeout for security

Monitoring and Debugging

# Basic timeout detection wrapper
def safe_regex_operation(pattern, input, context = {})
  start_time = Time.now
  
  begin
    result = yield(pattern, input)
    duration = Time.now - start_time
    
    { success: true, result: result, duration: duration }
  rescue Regexp::TimeoutError => e
    duration = Time.now - start_time
    
    {
      success: false,
      error: 'timeout',
      duration: duration,
      pattern: pattern.source,
      input_length: input.length,
      context: context
    }
  end
end

# Usage
result = safe_regex_operation(pattern, user_input, { user_id: 123 }) do |p, i|
  p.match(i)
end

Thread Safety Considerations

# Thread-safe timeout management
class RegexTimeoutManager
  @mutex = Mutex.new
  
  def self.with_timeout(value)
    @mutex.synchronize do
      original = Regexp.timeout
      Regexp.timeout = value
      
      begin
        yield
      ensure
        Regexp.timeout = original
      end
    end
  end
end

Performance Impact

Operation Type Timeout Overhead Notes
Simple patterns < 1% Minimal checking
Complex patterns 1-5% More frequent checks
Backtracking operations 5-10% Regular timeout verification
Timeout occurrences Variable Exception handling cost

Error Recovery Strategies

Strategy Implementation Use Case
Fallback patterns Try simpler regex on timeout Input validation
Input truncation Limit input length User content processing
Alternative algorithms Use non-regex approaches Performance-critical code
Graceful degradation Continue with reduced functionality Non-critical regex operations