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 |