@@ -15,7 +15,8 @@ defmodule Redix.PubSub.Connection do
1515 :connected_address ,
1616 :client_id ,
1717 subscriptions: % { } ,
18- monitors: % { }
18+ monitors: % { } ,
19+ ping_callers: :queue . new ( )
1920 ]
2021
2122 @ backoff_exponent 1.5
@@ -187,6 +188,10 @@ defmodule Redix.PubSub.Connection do
187188 { :keep_state , data }
188189 end
189190
191+ def disconnected ( { :call , from } , :ping , _data ) do
192+ { :keep_state_and_data , { :reply , from , { :error , % ConnectionError { reason: :closed } } } }
193+ end
194+
190195 def disconnected ( { :call , from } , :get_client_id , _data ) do
191196 reply = { :error , % ConnectionError { reason: :closed } }
192197 { :keep_state_and_data , { :reply , from , reply } }
@@ -234,6 +239,18 @@ defmodule Redix.PubSub.Connection do
234239 end
235240 end
236241
242+ def connected ( { :call , from } , :ping , data ) do
243+ data = % { data | ping_callers: :queue . in ( from , data . ping_callers ) }
244+
245+ case data . transport . send ( data . socket , Protocol . pack ( [ "PING" ] ) ) do
246+ :ok ->
247+ { :keep_state , data }
248+
249+ { :error , reason } ->
250+ disconnect ( data , reason , _handle_disconnection? = true )
251+ end
252+ end
253+
237254 def connected ( { :call , from } , :get_client_id , data ) do
238255 reply =
239256 if id = data . client_id do
@@ -345,6 +362,13 @@ defmodule Redix.PubSub.Connection do
345362 end
346363 end
347364
365+ # When a Redis connection is in pub/sub mode (subscribed to at least one
366+ # channel), Redis wraps PING responses as pub/sub messages: ["pong", ""]. When
367+ # not subscribed to anything, Redis returns a plain +PONG\r\n simple string,
368+ # which parses to "PONG".
369+ defp handle_pubsub_msg ( data , [ "pong" , _message ] ) , do: handle_pong ( data )
370+ defp handle_pubsub_msg ( data , "PONG" ) , do: handle_pong ( data )
371+
348372 defp handle_pubsub_msg ( data , [ "message" , channel , payload ] ) do
349373 properties = % { channel: channel , payload: payload }
350374 handle_pubsub_message_with_payload ( data , { :channel , channel } , :message , properties )
@@ -369,6 +393,31 @@ defmodule Redix.PubSub.Connection do
369393 { :ok , data }
370394 end
371395
396+ # Ping handling.
397+
398+ defp handle_pong ( data ) do
399+ { popped , data } = get_and_update_in ( data . ping_callers , & :queue . out / 1 )
400+
401+ # :empty is not supposed to happen but we're not gonna crash the process
402+ # if it does.
403+ case popped do
404+ { :value , from } -> :gen_statem . reply ( from , :ok )
405+ :empty -> :ok
406+ end
407+
408+ { :ok , data }
409+ end
410+
411+ defp drain_ping_callers ( data ) do
412+ error = { :error , % ConnectionError { reason: :closed } }
413+
414+ data . ping_callers
415+ |> :queue . to_list ( )
416+ |> Enum . each ( & :gen_statem . reply ( & 1 , error ) )
417+
418+ % { data | ping_callers: :queue . new ( ) }
419+ end
420+
372421 # Subscribing.
373422
374423 defp subscribe_pid_to_targets ( data , operation , targets , pid ) do
@@ -622,6 +671,7 @@ defmodule Redix.PubSub.Connection do
622671 end
623672
624673 def disconnect ( data , reason , handle_disconnection? ) do
674+ data = drain_ping_callers ( data )
625675 { next_backoff , data } = next_backoff ( data )
626676
627677 if data . socket do
0 commit comments