@@ -25,8 +25,8 @@ use crate::hotkey::Hotkey;
2525use crate :: info:: { InfoContext , ProxyMode } ;
2626use crate :: metrics;
2727use crate :: protocol:: redis:: {
28- BlockingKind , MultiDispatch , RedisCommand , RespCodec , RespValue , SlotMap , SubCommand ,
29- SubResponse , SubscriptionKind , SLOT_COUNT ,
28+ BlockingKind , MultiDispatch , RedisCommand , RespCodec , RespValue , RespVersion , SlotMap ,
29+ SubCommand , SubResponse , SubscriptionKind , SLOT_COUNT ,
3030} ;
3131use crate :: slowlog:: Slowlog ;
3232use crate :: utils:: { crc16, trim_hash_tag} ;
@@ -78,6 +78,7 @@ impl ClusterProxy {
7878 runtime. clone ( ) ,
7979 REQUEST_TIMEOUT_MS ,
8080 backend_auth. clone ( ) ,
81+ config. backend_resp_version ,
8182 ) ) ;
8283 let pool = Arc :: new ( ConnectionPool :: new ( cluster. clone ( ) , connector. clone ( ) ) ) ;
8384 let auth = config
@@ -143,17 +144,25 @@ impl ClusterProxy {
143144 let client_id = ClientId :: new ( ) ;
144145 let _guard = FrontConnectionGuard :: new ( & self . cluster ) ;
145146
146- let ( mut sink, stream) = Framed :: new ( socket, RespCodec :: default ( ) ) . split ( ) ;
147+ let framed = Framed :: new ( socket, RespCodec :: default ( ) ) ;
148+ let codec_handle = framed. codec ( ) . clone ( ) ;
149+ let ( mut sink, stream) = framed. split ( ) ;
147150 let mut stream = stream. fuse ( ) ;
148- let mut pending: FuturesOrdered < BoxFuture < ' static , RespValue > > = FuturesOrdered :: new ( ) ;
151+ let mut pending: FuturesOrdered < BoxFuture < ' static , ( RespValue , Option < RespVersion > ) > > =
152+ FuturesOrdered :: new ( ) ;
153+ let mut _resp3_negotiated = codec_handle. version ( ) == RespVersion :: Resp3 ;
149154 let mut inflight = 0usize ;
150155 let mut stream_closed = false ;
151156 let mut auth_state = self . auth . as_ref ( ) . map ( |auth| auth. new_session ( ) ) ;
152157
153158 loop {
154159 tokio:: select! {
155- Some ( resp) = pending. next( ) , if inflight > 0 => {
160+ Some ( ( resp, version ) ) = pending. next( ) , if inflight > 0 => {
156161 inflight -= 1 ;
162+ if let Some ( version) = version {
163+ codec_handle. set_version( version) ;
164+ _resp3_negotiated = version == RespVersion :: Resp3 ;
165+ }
157166 sink. send( resp) . await ?;
158167 }
159168 frame_opt = stream. next( ) , if !stream_closed && inflight < PIPELINE_LIMIT => {
@@ -168,14 +177,13 @@ impl ClusterProxy {
168177 cmd = new_cmd;
169178 }
170179 AuthAction :: Reply ( resp) => {
171- let fut = async move { resp } ;
180+ let fut = async move { ( resp, None ) } ;
172181 pending. push_back( Box :: pin( fut) ) ;
173182 inflight += 1 ;
174183 continue ;
175184 }
176185 }
177186 }
178-
179187 if matches!( cmd. as_subscription( ) , SubscriptionKind :: Channel | SubscriptionKind :: Pattern ) {
180188 let kind_label = cmd. kind_label( ) ;
181189 if cmd. args( ) . len( ) <= 1 {
@@ -230,8 +238,12 @@ impl ClusterProxy {
230238 }
231239 } ;
232240 while inflight > 0 {
233- if let Some ( resp) = pending. next( ) . await {
241+ if let Some ( ( resp, version ) ) = pending. next( ) . await {
234242 inflight -= 1 ;
243+ if let Some ( version) = version {
244+ codec_handle. set_version( version) ;
245+ _resp3_negotiated = version == RespVersion :: Resp3 ;
246+ }
235247 sink. send( resp) . await ?;
236248 } else {
237249 inflight = 0 ;
@@ -291,7 +303,7 @@ impl ClusterProxy {
291303 kind_label,
292304 success,
293305 ) ;
294- let fut = async move { response } ;
306+ let fut = async move { ( response, None ) } ;
295307 pending. push_back( Box :: pin( fut) ) ;
296308 inflight += 1 ;
297309 continue ;
@@ -302,7 +314,7 @@ impl ClusterProxy {
302314 cmd. kind_label( ) ,
303315 true ,
304316 ) ;
305- let fut = async move { response } ;
317+ let fut = async move { ( response, None ) } ;
306318 pending. push_back( Box :: pin( fut) ) ;
307319 inflight += 1 ;
308320 continue ;
@@ -314,7 +326,7 @@ impl ClusterProxy {
314326 cmd. kind_label( ) ,
315327 success,
316328 ) ;
317- let fut = async move { response } ;
329+ let fut = async move { ( response, None ) } ;
318330 pending. push_back( Box :: pin( fut) ) ;
319331 inflight += 1 ;
320332 continue ;
@@ -326,21 +338,31 @@ impl ClusterProxy {
326338 cmd. kind_label( ) ,
327339 success,
328340 ) ;
329- let fut = async move { response } ;
341+ let fut = async move { ( response, None ) } ;
330342 pending. push_back( Box :: pin( fut) ) ;
331343 inflight += 1 ;
332344 continue ;
333345 }
346+ let requested_version = cmd. resp_version_request( ) ;
334347 let guard = self . prepare_dispatch( client_id, cmd) ;
335- pending. push_back( Box :: pin( guard) ) ;
348+ let fut = async move {
349+ let resp = guard. await ;
350+ let version = if !resp. is_error( ) {
351+ requested_version
352+ } else {
353+ None
354+ } ;
355+ ( resp, version)
356+ } ;
357+ pending. push_back( Box :: pin( fut) ) ;
336358 inflight += 1 ;
337359 }
338360 Err ( err) => {
339361 metrics:: global_error_incr( ) ;
340362 metrics:: front_error( self . cluster. as_ref( ) , "parse" ) ;
341363 metrics:: front_command( self . cluster. as_ref( ) , "invalid" , false ) ;
342364 let message = Bytes :: from( format!( "ERR {err}" ) ) ;
343- let fut = async move { RespValue :: Error ( message) } ;
365+ let fut = async move { ( RespValue :: Error ( message) , None ) } ;
344366 pending. push_back( Box :: pin( fut) ) ;
345367 inflight += 1 ;
346368 }
@@ -364,7 +386,11 @@ impl ClusterProxy {
364386 }
365387 }
366388
367- while let Some ( resp) = pending. next ( ) . await {
389+ while let Some ( ( resp, version) ) = pending. next ( ) . await {
390+ if let Some ( version) = version {
391+ codec_handle. set_version ( version) ;
392+ _resp3_negotiated = version == RespVersion :: Resp3 ;
393+ }
368394 sink. send ( resp) . await ?;
369395 }
370396 sink. close ( ) . await ?;
@@ -903,13 +929,15 @@ struct ClusterConnector {
903929 heartbeat_interval : Duration ,
904930 reconnect_base_delay : Duration ,
905931 max_reconnect_attempts : usize ,
932+ backend_resp_version : RespVersion ,
906933}
907934
908935impl ClusterConnector {
909936 fn new (
910937 runtime : Arc < ClusterRuntime > ,
911938 default_timeout_ms : u64 ,
912939 backend_auth : Option < BackendAuth > ,
940+ backend_resp_version : RespVersion ,
913941 ) -> Self {
914942 Self {
915943 runtime,
@@ -918,6 +946,7 @@ impl ClusterConnector {
918946 heartbeat_interval : Duration :: from_secs ( 30 ) ,
919947 reconnect_base_delay : Duration :: from_millis ( 50 ) ,
920948 max_reconnect_attempts : 3 ,
949+ backend_resp_version,
921950 }
922951 }
923952
@@ -950,11 +979,91 @@ impl ClusterConnector {
950979 Ok ( framed)
951980 }
952981
982+ async fn negotiate_resp_version (
983+ & self ,
984+ cluster : & str ,
985+ node : & BackendNode ,
986+ framed : & mut Framed < TcpStream , RespCodec > ,
987+ ) -> Result < RespVersion > {
988+ framed. codec_mut ( ) . set_version ( RespVersion :: Resp2 ) ;
989+ if self . backend_resp_version != RespVersion :: Resp3 {
990+ return Ok ( RespVersion :: Resp2 ) ;
991+ }
992+
993+ let timeout_duration = self . current_timeout ( ) ;
994+ let mut hello_parts = vec ! [
995+ RespValue :: BulkString ( Bytes :: from_static( b"HELLO" ) ) ,
996+ RespValue :: BulkString ( Bytes :: from_static( b"3" ) ) ,
997+ ] ;
998+ if let Some ( auth) = & self . backend_auth {
999+ if let Some ( ( username, password) ) = auth. hello_credentials ( ) {
1000+ hello_parts. push ( RespValue :: BulkString ( Bytes :: from_static ( b"AUTH" ) ) ) ;
1001+ hello_parts. push ( RespValue :: BulkString ( username) ) ;
1002+ hello_parts. push ( RespValue :: BulkString ( password) ) ;
1003+ }
1004+ }
1005+ let hello = RespValue :: Array ( hello_parts) ;
1006+
1007+ match timeout ( timeout_duration, framed. send ( hello) ) . await {
1008+ Ok ( Ok ( ( ) ) ) => { }
1009+ Ok ( Err ( err) ) => {
1010+ metrics:: backend_error ( cluster, node. as_str ( ) , "resp3_handshake" ) ;
1011+ return Err ( err. context ( format ! ( "failed to send RESP3 HELLO to {}" , node. as_str( ) ) ) ) ;
1012+ }
1013+ Err ( _) => {
1014+ metrics:: backend_error ( cluster, node. as_str ( ) , "resp3_handshake" ) ;
1015+ return Err ( anyhow ! (
1016+ "backend {} timed out sending RESP3 HELLO" ,
1017+ node. as_str( )
1018+ ) ) ;
1019+ }
1020+ }
1021+
1022+ let reply = match timeout ( timeout_duration, framed. next ( ) ) . await {
1023+ Ok ( Some ( Ok ( value) ) ) => value,
1024+ Ok ( Some ( Err ( err) ) ) => {
1025+ metrics:: backend_error ( cluster, node. as_str ( ) , "resp3_handshake" ) ;
1026+ return Err ( err. context ( format ! (
1027+ "failed to read RESP3 HELLO reply from {}" ,
1028+ node. as_str( )
1029+ ) ) ) ;
1030+ }
1031+ Ok ( None ) => {
1032+ metrics:: backend_error ( cluster, node. as_str ( ) , "resp3_handshake" ) ;
1033+ return Err ( anyhow ! (
1034+ "backend {} closed connection during RESP3 HELLO" ,
1035+ node. as_str( )
1036+ ) ) ;
1037+ }
1038+ Err ( _) => {
1039+ metrics:: backend_error ( cluster, node. as_str ( ) , "resp3_handshake" ) ;
1040+ return Err ( anyhow ! (
1041+ "backend {} timed out waiting for RESP3 HELLO reply" ,
1042+ node. as_str( )
1043+ ) ) ;
1044+ }
1045+ } ;
1046+
1047+ if reply. is_error ( ) {
1048+ info ! (
1049+ cluster = %cluster,
1050+ backend = %node. as_str( ) ,
1051+ "backend rejected RESP3 HELLO; falling back to RESP2"
1052+ ) ;
1053+ framed. codec_mut ( ) . set_version ( RespVersion :: Resp2 ) ;
1054+ return Ok ( RespVersion :: Resp2 ) ;
1055+ }
1056+
1057+ framed. codec_mut ( ) . set_version ( RespVersion :: Resp3 ) ;
1058+ Ok ( RespVersion :: Resp3 )
1059+ }
1060+
9531061 async fn execute (
9541062 & self ,
9551063 framed : & mut Framed < TcpStream , RespCodec > ,
9561064 command : RedisCommand ,
9571065 ) -> Result < RespValue > {
1066+ let requested_version = command. resp_version_request ( ) ;
9581067 let blocking = command. as_blocking ( ) ;
9591068 if let Ok ( name) = std:: str:: from_utf8 ( command. command_name ( ) ) {
9601069 if name. eq_ignore_ascii_case ( "blpop" ) || name. eq_ignore_ascii_case ( "brpop" ) {
@@ -966,7 +1075,7 @@ impl ClusterConnector {
9661075 . await
9671076 . context ( "timed out sending command" ) ??;
9681077
969- match blocking {
1078+ let response = match blocking {
9701079 BlockingKind :: Queue { .. } | BlockingKind :: Stream { .. } => match framed. next ( ) . await {
9711080 Some ( Ok ( value) ) => Ok ( value) ,
9721081 Some ( Err ( err) ) => Err ( err. into ( ) ) ,
@@ -978,7 +1087,14 @@ impl ClusterConnector {
9781087 Ok ( None ) => Err ( anyhow ! ( "backend closed connection" ) ) ,
9791088 Err ( _) => Err ( anyhow ! ( "timed out waiting for response" ) ) ,
9801089 } ,
1090+ } ?;
1091+
1092+ if let Some ( version) = requested_version {
1093+ if !response. is_error ( ) {
1094+ framed. codec_mut ( ) . set_version ( version) ;
1095+ }
9811096 }
1097+ Ok ( response)
9821098 }
9831099
9841100 async fn connect_with_retry (
@@ -990,15 +1106,33 @@ impl ClusterConnector {
9901106 for attempt in 0 ..self . max_reconnect_attempts {
9911107 let attempt_start = Instant :: now ( ) ;
9921108 match self . open_stream ( node. as_str ( ) ) . await {
993- Ok ( stream) => {
1109+ Ok ( mut stream) => {
9941110 metrics:: backend_probe_duration (
9951111 cluster,
9961112 node. as_str ( ) ,
9971113 "connect" ,
9981114 attempt_start. elapsed ( ) ,
9991115 ) ;
10001116 metrics:: backend_probe_result ( cluster, node. as_str ( ) , "connect" , true ) ;
1001- return Ok ( stream) ;
1117+ match self
1118+ . negotiate_resp_version ( cluster, node, & mut stream)
1119+ . await
1120+ {
1121+ Ok ( _) => return Ok ( stream) ,
1122+ Err ( err) => {
1123+ warn ! (
1124+ cluster = %cluster,
1125+ backend = %node. as_str( ) ,
1126+ attempt = attempt + 1 ,
1127+ error = %err,
1128+ "failed to negotiate RESP version with backend"
1129+ ) ;
1130+ last_error = Some ( err) ;
1131+ if attempt + 1 < self . max_reconnect_attempts {
1132+ sleep ( self . reconnect_base_delay ) . await ;
1133+ }
1134+ }
1135+ }
10021136 }
10031137 Err ( err) => {
10041138 let elapsed = attempt_start. elapsed ( ) ;
0 commit comments