Class: Reputable::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/reputable/connection.rb

Overview

Redis connection pool manager with robust error handling

Constant Summary collapse

CIRCUIT_THRESHOLD =

Circuit breaker settings

5
CIRCUIT_TIMEOUT =

Failures before opening circuit

30

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.circuit_openObject

Returns the value of attribute circuit_open.



20
21
22
# File 'lib/reputable/connection.rb', line 20

def circuit_open
  @circuit_open
end

.circuit_opened_atObject

Returns the value of attribute circuit_opened_at.



20
21
22
# File 'lib/reputable/connection.rb', line 20

def circuit_opened_at
  @circuit_opened_at
end

.failure_countObject

Returns the value of attribute failure_count.



20
21
22
# File 'lib/reputable/connection.rb', line 20

def failure_count
  @failure_count
end

Class Method Details

.circuit_open?Boolean

Check if circuit breaker is open (we should skip operations)

Returns:

  • (Boolean)


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/reputable/connection.rb', line 80

def circuit_open?
  return false unless @circuit_open
  
  # Check if enough time has passed to try again (half-open state)
  if @circuit_opened_at && (Time.now - @circuit_opened_at) > CIRCUIT_TIMEOUT
    # Enter half-open state: allow one probe request through.
    # Set failure_count to threshold - 1 so a single failure immediately
    # re-opens the circuit instead of requiring another 5 failures.
    @circuit_open = false
    @failure_count = CIRCUIT_THRESHOLD - 1
    Reputable.logger&.info("Reputable circuit breaker: attempting reconnection")
    return false
  end
  
  true
end

.poolObject



22
23
24
25
26
27
28
29
# File 'lib/reputable/connection.rb', line 22

def pool
  @pool ||= ConnectionPool.new(
    size: Reputable.configuration.pool_size,
    timeout: Reputable.configuration.pool_timeout
  ) do
    build_redis_client
  end
end

.reset!Object



71
72
73
74
75
76
77
# File 'lib/reputable/connection.rb', line 71

def reset!
  @pool&.shutdown(&:close)
  @pool = nil
  @circuit_open = false
  @circuit_opened_at = nil
  @failure_count = 0
end

.safe_with(default: nil, context: "redis_operation") { ... } ⇒ Object

Safe wrapper that handles all errors gracefully

Parameters:

  • default (Object) (defaults to: nil)

    Value to return on failure

  • context (String) (defaults to: "redis_operation")

    Description for error logging

Yields:

  • Block to execute with Redis connection

Returns:

  • (Object)

    Result of block or default value



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/reputable/connection.rb', line 36

def safe_with(default: nil, context: "redis_operation")
  return default unless Reputable.enabled?
  return default if circuit_open?
  
  result = Timeout.timeout(operation_timeout) do
    pool.with do |redis|
      yield redis
    end
  end
  
  record_success
  result
rescue Timeout::Error, ConnectionPool::TimeoutError => e
  record_failure(e, context)
  default
rescue Redis::BaseError, Errno::ECONNREFUSED, Errno::ETIMEDOUT, 
       Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ECONNRESET,
       Errno::EPIPE, SocketError, IOError => e
  record_failure(e, context)
  default
rescue OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError => e
  # Catch all SSL/TLS errors - certificate issues, handshake failures, etc.
  record_failure(e, context)
  default
rescue StandardError => e
  # Catch-all for any unexpected errors
  record_failure(e, context)
  default
end

.with(&block) ⇒ Object

Legacy method for backward compatibility



67
68
69
# File 'lib/reputable/connection.rb', line 67

def with(&block)
  safe_with(default: nil, context: "legacy_with", &block)
end