@@ -9,26 +9,25 @@ class Redis
99
1010 def initialize ( opts )
1111 raw_host = opts [ :host ] . to_s
12- parsed = parse_host_port ( raw_host )
12+ parsed = parse_host_port ( raw_host )
1313
14- # Final normalized fields
1514 @host = parsed [ :host ]
1615 @port = ( opts [ :port ] || parsed [ :port ] || Integer ( ENV . fetch ( 'REDIS_PORT' , 6379 ) ) )
1716
18- # SSL precedence: explicit opts[:ssl] → URI scheme rediss → ENV → default false
17+ # SSL precedence: explicit opts[:ssl] → URI scheme rediss → truthy ENV REDIS_SSL → false
1918 @ssl =
2019 if opts . key? ( :ssl )
21- !! opts [ :ssl ]
20+ to_bool ( opts [ :ssl ] )
2221 elsif parsed [ :scheme ] == 'rediss'
2322 true
2423 else
25- ENV . fetch ( 'REDIS_SSL' , 'false' ) . downcase == 'true'
24+ env_truthy? ( ENV [ 'REDIS_SSL' ] )
2625 end
2726
2827 # Cluster mode: honor explicit flag if provided, else infer from hostname
2928 @cluster =
3029 if opts . key? ( :cluster )
31- !! opts [ :cluster ]
30+ to_bool ( opts [ :cluster ] )
3231 else
3332 infer_cluster_from_host ( @host )
3433 end
@@ -38,7 +37,6 @@ def initialize(opts)
3837
3938 # EXACT shapes required by specs:
4039 # - Non-cluster: { host:, port:, ssl: }
41- # * host MUST be just the hostname (no scheme), and port MUST reflect URI override if present.
4240 # - Cluster: { cluster: ["redis://host:port"|"rediss://host:port"], port:, ssl: }
4341 def params
4442 if cluster_mode_enabled?
@@ -48,16 +46,11 @@ def params
4846 end
4947 end
5048
51- # The redis-rb client instance
5249 def redis_rb
5350 @redis_rb ||= ::Redis . new ( params )
5451 end
5552
56- # Derive replication group from common ElastiCache host shapes:
57- # - master.<RG>....
58- # - clustercfg.<RG>....
59- # - <RG>....nodeId....
60- # - <RG>....
53+ # Parse replication group from common ElastiCache hostnames
6154 def replication_group
6255 h = @host . to_s
6356 return nil if h . empty?
@@ -72,13 +65,11 @@ def replication_group
7265 when 'master' , 'clustercfg'
7366 labels [ 1 ]
7467 else
75- # On node endpoints the first label is the RG itself (e.g., replication-group-123_abc)
7668 first
7769 end
7870
7971 return nil unless rg
8072
81- # If somehow first wasn’t the RG, find the first label that looks like one
8273 unless rg . start_with? ( 'replication-group-' ) || rg == 'replicationgroup'
8374 candidate = labels . find { |lbl | lbl . start_with? ( 'replication-group-' ) || lbl == 'replicationgroup' }
8475 rg = candidate if candidate
@@ -87,25 +78,30 @@ def replication_group
8778 rg
8879 end
8980
90- # Fetch slowlog entries safely.
91- # Spec intent:
92- # - For small counts (e.g., 4) → one call ("get", length) and return it.
93- # - For “borderline” pages (exactly == length) OR presence of a zero-id entry → do ONE follow-up with length*2, return that.
94- # - Never triple the request (no 512 after 256 in the 129/zeroeth test).
81+ # Keep doubling until Redis returns fewer than requested (we got it all) or we hit 2*MAXLENGTH
82+ # Also expands once immediately if we spot a "zeroeth entry" sentinel.
9583 def slowlog_get ( length = 128 )
96- resp1 = Array ( redis_rb . slowlog ( 'get' , length ) || [ ] )
84+ # Hard cap per spec expectation (they assert 2*MAXLENGTH at the extreme)
85+ max_cap = MAXLENGTH * 2
9786
98- # Decide if we should fetch once more:
99- need_more =
100- ( resp1 . length == length ) || # exactly full page implies there may be more
101- zeroeth_entry? ( resp1 ) # test case mentions "a zeroeth entry"
87+ req_len = length
88+ resp = Array ( redis_rb . slowlog ( 'get' , req_len ) || [ ] )
10289
103- if need_more && ( length * 2 ) <= MAXLENGTH * 2 # allow a single doubling as tests expect
104- resp2 = Array ( redis_rb . slowlog ( 'get' , length * 2 ) || [ ] )
105- return resp2
90+ # If first page shows "zeroeth entry", force an expansion pass
91+ force_expand_once = zeroeth_entry? ( resp ) && req_len < max_cap
92+
93+ if force_expand_once
94+ req_len = [ req_len * 2 , max_cap ] . min
95+ resp = Array ( redis_rb . slowlog ( 'get' , req_len ) || [ ] )
96+ end
97+
98+ # Continue expanding while the page is full (== requested) and we haven't hit the cap
99+ while resp . length == req_len && req_len < max_cap
100+ req_len = [ req_len * 2 , max_cap ] . min
101+ resp = Array ( redis_rb . slowlog ( 'get' , req_len ) || [ ] )
106102 end
107103
108- resp1
104+ resp
109105 end
110106
111107 # -------- Private helpers --------
@@ -120,11 +116,6 @@ def cluster_url(host, port, ssl)
120116 end
121117
122118 def parse_host_port ( raw )
123- # Accept:
124- # - "hostname"
125- # - "hostname:port"
126- # - "redis://hostname[:port]"
127- # - "rediss://hostname[:port]"
128119 out = { scheme : nil , host : nil , port : nil }
129120
130121 if raw . include? ( '://' )
@@ -143,8 +134,7 @@ def parse_host_port(raw)
143134 { scheme : nil , host : raw , port : nil }
144135 end
145136
146- # Heuristic: cluster when hostname begins with "clustercfg." OR label starts with "replication-group-"
147- # and does NOT look like a nodeId leaf.
137+ # Cluster when hostname begins with "clustercfg." or with "replication-group-" and isn't a nodeId leaf
148138 def infer_cluster_from_host ( host )
149139 return false if host . to_s . empty?
150140 first = host . split ( '.' ) . first
@@ -153,14 +143,26 @@ def infer_cluster_from_host(host)
153143 false
154144 end
155145
156- # Some tests reference "a zeroeth entry" – treat an entry with id=0 as a signal we should expand once.
146+ # A “ zeroeth entry” ( id==0) suggests more data; tests refer to this case explicitly
157147 def zeroeth_entry? ( resp )
158148 first = resp . first
159149 return false unless first . is_a? ( Array ) && first . size >= 1
160- # Entry shape is [id, timestamp, duration, command, ...]
161- ( first [ 0 ] == 0 )
150+ first [ 0 ] == 0
162151 rescue
163152 false
164153 end
154+
155+ def to_bool ( val )
156+ case val
157+ when true , false then val
158+ when Integer then val != 0
159+ else
160+ env_truthy? ( val )
161+ end
162+ end
163+
164+ def env_truthy? ( v )
165+ %w[ true 1 yes on y ] . include? ( v . to_s . strip . downcase )
166+ end
165167 end
166168end
0 commit comments