@@ -350,7 +350,7 @@ defmodule Postgrex.Protocol do
350350 | { :error , % ArgumentError { } | Postgrex.Error . t ( ) , state }
351351 | { :error , % DBConnection.TransactionError { } , state }
352352 | { :disconnect , % RuntimeError { } , state }
353- | { :disconnect , % DBConnection.ConnectionError { } , state }
353+ | { :disconnect | :disconnect_and_retry , % DBConnection.ConnectionError { } , state }
354354 def handle_prepare ( % Query { } = query , _ , % { postgres: { _ , _ } } = s ) do
355355 lock_error ( s , :prepare , query )
356356 end
@@ -365,15 +365,18 @@ defmodule Postgrex.Protocol do
365365 def handle_prepare ( % Query { name: "" } = query , opts , s ) do
366366 prepare = Keyword . get ( opts , :postgrex_prepare , false )
367367 status = new_status ( opts , prepare: prepare )
368+ comment = Keyword . get ( opts , :comment )
368369
369- case prepare do
370- true ->
371- parse_describe_close ( s , status , query )
370+ result =
371+ case prepare do
372+ true ->
373+ parse_describe_close ( s , status , query )
372374
373- false ->
374- comment = Keyword . get ( opts , :comment )
375- parse_describe_flush ( s , status , query , comment )
376- end
375+ false ->
376+ parse_describe_flush ( s , status , query , comment )
377+ end
378+
379+ handle_disconnect_retry ( result )
377380 end
378381
379382 def handle_prepare ( % Query { } = query , opts , % { queries: nil } = s ) do
@@ -395,8 +398,9 @@ defmodule Postgrex.Protocol do
395398 false -> close_parse_describe_flush ( s , status , query , comment )
396399 end
397400
398- with { :ok , query , s } <- result do
399- { :ok , query , % { s | messages: [ ] } }
401+ case result do
402+ { :ok , query , s } -> { :ok , query , % { s | messages: [ ] } }
403+ other -> handle_disconnect_retry ( other )
400404 end
401405 end
402406 end
@@ -422,11 +426,14 @@ defmodule Postgrex.Protocol do
422426 | { :error , % ArgumentError { } | Postgrex.Error . t ( ) , state }
423427 | { :error , % DBConnection.TransactionError { } , state }
424428 | { :disconnect , % RuntimeError { } , state }
425- | { :disconnect , % DBConnection.ConnectionError { } , state }
429+ | { :disconnect | :disconnect_and_retry , % DBConnection.ConnectionError { } , state }
426430 def handle_execute ( % Query { } = query , params , opts , s ) do
427431 case Keyword . get ( opts , :postgrex_copy , false ) do
428- true -> handle_execute_copy ( query , params , opts , s )
429- false -> handle_execute_result ( query , params , opts , s )
432+ true ->
433+ handle_execute_copy ( query , params , opts , s )
434+
435+ false ->
436+ handle_execute_result ( query , params , opts , s )
430437 end
431438 end
432439
@@ -503,17 +510,19 @@ defmodule Postgrex.Protocol do
503510 { :ok , Postgrex.Result . t ( ) , state }
504511 | { :error , % ArgumentError { } | Postgrex.Error . t ( ) , state }
505512 | { :disconnect , % RuntimeError { } , state }
506- | { :disconnect , % DBConnection.ConnectionError { } , state }
513+ | { :disconnect | :disconnect_and_retry , % DBConnection.ConnectionError { } , state }
507514 def handle_close ( % Query { ref: ref } = query , opts , % { postgres: { _ , ref } } = s ) do
508- flushed_close ( s , new_status ( opts ) , query )
515+ result = flushed_close ( s , new_status ( opts ) , query )
516+ handle_disconnect_retry ( result )
509517 end
510518
511519 def handle_close ( % Query { } = query , _ , % { postgres: { _ , _ } } = s ) do
512520 lock_error ( s , :close , query )
513521 end
514522
515523 def handle_close ( % Query { } = query , opts , s ) do
516- close ( s , new_status ( opts ) , query )
524+ result = close ( s , new_status ( opts ) , query )
525+ handle_disconnect_retry ( result )
517526 end
518527
519528 @ impl true
@@ -582,7 +591,8 @@ defmodule Postgrex.Protocol do
582591 { :ok , Postgrex.Result . t ( ) , state }
583592 | { DBConnection . status ( ) , state }
584593 | { :disconnect , % RuntimeError { } , state }
585- | { :disconnect , % DBConnection.ConnectionError { } | Postgrex.Error . t ( ) , state }
594+ | { :disconnect | :disconnect_and_retry ,
595+ % DBConnection.ConnectionError { } | Postgrex.Error . t ( ) , state }
586596 def handle_begin ( _ , % { postgres: { _ , _ } } = s ) do
587597 lock_error ( s , :begin )
588598 end
@@ -591,7 +601,8 @@ defmodule Postgrex.Protocol do
591601 case Keyword . get ( opts , :mode , :transaction ) do
592602 :transaction when postgres == :idle ->
593603 statement = "BEGIN"
594- handle_transaction ( statement , opts , s )
604+ result = handle_transaction ( statement , opts , s )
605+ handle_disconnect_retry ( result )
595606
596607 :savepoint when postgres == :transaction ->
597608 statement = "SAVEPOINT postgrex_savepoint"
@@ -2081,7 +2092,7 @@ defmodule Postgrex.Protocol do
20812092 bind_execute_close ( s , status , query , params )
20822093
20832094 { error , _ , _ } = other when error in [ :error , :disconnect ] ->
2084- other
2095+ handle_disconnect_retry ( other )
20852096 end
20862097 end
20872098
@@ -2093,7 +2104,7 @@ defmodule Postgrex.Protocol do
20932104 bind_execute ( s , status , query , params )
20942105
20952106 { error , _ , _ } = other when error in [ :error , :disconnect ] ->
2096- other
2107+ handle_disconnect_retry ( other )
20972108 end
20982109 end
20992110
@@ -2114,8 +2125,8 @@ defmodule Postgrex.Protocol do
21142125 msg_sync ( )
21152126 ]
21162127
2117- with :ok <- msg_send ( % { s | buffer: nil } , msgs , buffer ) ,
2118- { :ok , s , buffer } <- recv_bind ( s , status , buffer ) ,
2128+ with :ok <- msg_send ( % { s | buffer: nil } , msgs , buffer ) |> handle_disconnect_retry ( ) ,
2129+ { :ok , s , buffer } <- recv_bind ( s , status , buffer ) |> handle_disconnect_retry ( ) ,
21192130 { :ok , result , s , buffer } <- recv_execute ( s , status , query , buffer ) ,
21202131 { :ok , s , buffer } <- recv_close ( s , status , buffer ) ,
21212132 { :ok , s } <- recv_ready ( s , status , buffer ) do
@@ -2125,7 +2136,7 @@ defmodule Postgrex.Protocol do
21252136 error_ready ( s , status , err , buffer )
21262137 |> maybe_disconnect ( )
21272138
2128- { :disconnect , _err , _s } = disconnect ->
2139+ { _disconnect_or_retry , _err , _s } = disconnect ->
21292140 disconnect
21302141 end
21312142 end
@@ -2151,8 +2162,8 @@ defmodule Postgrex.Protocol do
21512162 msg_sync ( )
21522163 ]
21532164
2154- with :ok <- msg_send ( % { s | buffer: nil } , msgs , buffer ) ,
2155- { :ok , s , buffer } <- recv_bind ( s , status , buffer ) ,
2165+ with :ok <- msg_send ( % { s | buffer: nil } , msgs , buffer ) |> handle_disconnect_retry ( ) ,
2166+ { :ok , s , buffer } <- recv_bind ( s , status , buffer ) |> handle_disconnect_retry ( ) ,
21562167 { :ok , result , s , buffer } <- recv_execute ( s , status , query , buffer ) ,
21572168 { :ok , s } <- recv_ready ( s , status , buffer ) do
21582169 { :ok , query , result , s }
@@ -2163,7 +2174,7 @@ defmodule Postgrex.Protocol do
21632174 error_ready ( s , status , err , buffer )
21642175 |> maybe_disconnect ( )
21652176
2166- { :disconnect , _err , _s } = disconnect ->
2177+ { _disconnect_or_retry , _err , _s } = disconnect ->
21672178 disconnect
21682179 end
21692180 end
@@ -3391,7 +3402,13 @@ defmodule Postgrex.Protocol do
33913402 end
33923403
33933404 defp conn_error ( mod , action , reason ) when reason in @ nonposix_errors do
3394- conn_error ( "#{ mod } #{ action } : #{ reason } " )
3405+ msg = "#{ mod } #{ action } : #{ reason } "
3406+
3407+ if reason == :closed do
3408+ conn_error ( msg , :closed )
3409+ else
3410+ conn_error ( msg )
3411+ end
33953412 end
33963413
33973414 defp conn_error ( :tcp , action , reason ) do
@@ -3404,6 +3421,10 @@ defmodule Postgrex.Protocol do
34043421 conn_error ( "ssl #{ action } : #{ formatted_reason } - #{ inspect ( reason ) } " )
34053422 end
34063423
3424+ defp conn_error ( message , reason ) do
3425+ DBConnection.ConnectionError . exception ( message: message , reason: reason )
3426+ end
3427+
34073428 defp conn_error ( message ) do
34083429 DBConnection.ConnectionError . exception ( message )
34093430 end
@@ -3416,6 +3437,16 @@ defmodule Postgrex.Protocol do
34163437 { :disconnect , err , % { s | buffer: buffer } }
34173438 end
34183439
3440+ # This function is used in two ways:
3441+ #
3442+ # * When we know the operation is fully retriable, we invoke it at the top
3443+ # * When only part is retriable (such as bind in execute or begin in a transaction),
3444+ # we invoke it at the specific instructions
3445+ defp handle_disconnect_retry ( { :disconnect , % { reason: :closed } = err , s } ) ,
3446+ do: { :disconnect_and_retry , err , s }
3447+
3448+ defp handle_disconnect_retry ( other ) , do: other
3449+
34193450 defp sync_recv ( s , status , buffer ) do
34203451 % { postgres: postgres , transactions: transactions } = s
34213452
0 commit comments