2626-define (NAME (NODE_OR_TUPLE ), {client , NODE_OR_TUPLE }).
2727
2828% %% Local state
29- -record (state , {socket :: port (),
29+ -record (state , {socket :: port () | undefined ,
3030 driver :: atom (),
3131 driver_mod :: atom (),
3232 driver_closed :: atom (),
3333 driver_error :: atom (),
3434 max_batch_size :: integer (),
35- keepalive :: tuple ()}).
35+ keepalive :: tuple () | undefined }).
3636
3737% %% Supervisor functions
3838-export ([start_link /1 , stop /1 ]).
5050
5151% %% Behaviour callbacks
5252-export ([init /1 , handle_call /3 , handle_cast /2 ,
53- handle_info /2 , terminate /2 , code_change /3 ]).
53+ handle_info /2 , handle_continue / 2 , terminate /2 , code_change /3 ]).
5454
5555% %% Process exports
5656-export ([async_call_worker /5 , cast_worker /4 ]).
@@ -104,6 +104,12 @@ call(NodeOrTuple, M, F, A, RecvTimeout, SendTimeout) when ?is_node_or_tuple(Node
104104 gen_server :call (Pid , {{call ,M ,F ,A }, SendTimeout }, gen_rpc_helper :get_call_receive_timeout (RecvTimeout ))
105105 catch
106106 exit :{timeout ,_Reason } -> {badrpc ,timeout };
107+ exit :{{shutdown , {badrpc , Reason }}, {gen_server , call , _ }} ->
108+ % % the client gen_server stopped in handle_continue
109+ {badrpc , Reason };
110+ exit :{{shutdown , Reason }, {gen_server , call , _ }} ->
111+ % % the client gen_server stopped with shutdown reason (non-badrpc)
112+ {badrpc , Reason };
107113 exit :OtherReason -> {badrpc , {unknown_error , OtherReason }}
108114 end ;
109115 {error , Reason } ->
@@ -230,12 +236,15 @@ where_is(NodeOrTuple) ->
230236% %% ===================================================
231237% %% Behaviour callbacks
232238% %% ===================================================
233- % % If we're called with a key, remove it, the key is only relevant
234- % % at the process name level
235- init ({{Node ,_Key }}) ->
236- init ({Node });
237-
238- init ({Node }) ->
239+ init ({NodeOrTuple }) ->
240+ % % Set process label for OTP >= 27
241+ ok = set_process_label_if_supported ({? MODULE , NodeOrTuple }),
242+ {Node , Key } = case is_atom (NodeOrTuple ) of
243+ true ->
244+ {NodeOrTuple , undefined };
245+ false ->
246+ NodeOrTuple
247+ end ,
239248 ok = gen_rpc_helper :set_optimal_process_flags (),
240249 case gen_rpc_helper :get_client_config_per_node (Node ) of
241250 {error , Reason } ->
@@ -244,38 +253,51 @@ init({Node}) ->
244253 {Driver , Port } ->
245254 {DriverMod , DriverClosed , DriverError } = gen_rpc_helper :get_client_driver_options (Driver ),
246255 ? log (info , " event=initializing_client driver=~s node=\" ~s \" port=~B " , [Driver , Node , Port ]),
247- case gen_rpc_auth :connect_with_auth (DriverMod , Node , Port ) of
248- {ok , Socket } ->
249- Interval = application :get_env (? APP , keepalive_interval , 60 ), % 60s
250- StatFun = fun () ->
251- case DriverMod :getstat (Socket , [recv_oct ]) of
252- {ok , [{recv_oct , RecvOct }]} -> {ok , RecvOct };
253- {error , Error } -> {error , Error }
254- end
255- end ,
256- case gen_rpc_keepalive :start (StatFun , Interval , {keepalive , check }) of
257- {ok , KeepAlive } ->
258- MaxBatchSize = application :get_env (? APP , max_batch_size , 0 ),
259- {ok , # state {socket = Socket ,
260- driver = Driver ,
261- driver_mod = DriverMod ,
262- driver_closed = DriverClosed ,
263- driver_error = DriverError ,
264- max_batch_size = MaxBatchSize ,
265- keepalive = KeepAlive },
266- gen_rpc_helper :get_inactivity_timeout (? MODULE )};
267- {error , Error } ->
268- ? log (error , " event=start_keepalive_failed driver=~p , reason=\" ~p \" " , [Driver , Error ]),
269- {stop , Error }
270- end ;
271- {error , ReasonTuple } ->
272- ? log (error , " event=client_authentication_failed driver=~s reason=\" ~p \" " , [Driver , ReasonTuple ]),
273- {stop , ReasonTuple };
274- {unreachable , Reason } ->
275- % % This should be badtcp but to conform with
276- % % the RPC library we return badrpc
277- {stop , {badrpc , Reason }}
278- end
256+ MaxBatchSize = application :get_env (? APP , max_batch_size , 0 ),
257+ InitialState = # state {socket = undefined ,
258+ driver = Driver ,
259+ driver_mod = DriverMod ,
260+ driver_closed = DriverClosed ,
261+ driver_error = DriverError ,
262+ max_batch_size = MaxBatchSize ,
263+ keepalive = undefined },
264+ % % Return immediately, connection happens in handle_continue
265+ {ok , InitialState , {continue , {connect , Node , Port , Key }}}
266+ end .
267+
268+ % % Handle async connection initialization
269+ handle_continue ({connect , Node , Port , Key }, # state {driver_mod = DriverMod , driver = Driver } = State ) ->
270+ IdForLog = case Key =:= undefined of
271+ true ->
272+ io_lib :format (" peer=~s ://~s :~p " , [Driver , Node , Port ]);
273+ false ->
274+ io_lib :format (" peer=~s ://~s :~p , key=~p " , [Driver , Node , Port , Key ])
275+ end ,
276+ case gen_rpc_auth :connect_with_auth (DriverMod , Node , Port ) of
277+ {ok , Socket } ->
278+ Interval = application :get_env (? APP , keepalive_interval , 60 ), % 60s
279+ StatFun = fun () ->
280+ case DriverMod :getstat (Socket , [recv_oct ]) of
281+ {ok , [{recv_oct , RecvOct }]} -> {ok , RecvOct };
282+ {error , Error } -> {error , Error }
283+ end
284+ end ,
285+ case gen_rpc_keepalive :start (StatFun , Interval , {keepalive , check }) of
286+ {ok , KeepAlive } ->
287+ ConnectedState = State # state {socket = Socket , keepalive = KeepAlive },
288+ {noreply , ConnectedState , gen_rpc_helper :get_inactivity_timeout (? MODULE )};
289+ {error , Error } ->
290+ ? log (error , " event=start_keepalive_failed ~s reason=\" ~0p \" " , [IdForLog , Error ]),
291+ {stop , {shutdown , Error }, State }
292+ end ;
293+ {error , ReasonTuple } ->
294+ ? log (error , " event=client_authentication_failed ~s reason=\" ~0p \" " , [IdForLog , ReasonTuple ]),
295+ {stop , {shutdown , ReasonTuple }, State };
296+ {unreachable , Reason } ->
297+ % % This should be badtcp but to conform with
298+ % % the RPC library we return badrpc
299+ ? log (error , " event=connect_to_remote_server, ~s , reason=\" ~0p \" " , [IdForLog , Reason ]),
300+ {stop , {shutdown , {badrpc , Reason }}, State }
279301 end .
280302
281303% % This is the actual CALL handler
@@ -288,7 +310,7 @@ handle_call({{call,_M,_F,_A} = PacketTuple, SendTimeout}, Caller, #state{socket=
288310 {error , Reason } ->
289311 ? log (error , " message=call event=transmission_failed driver=~s socket=\" ~s \" caller=\" ~p \" reason=\" ~p \" " ,
290312 [Driver , gen_rpc_helper :socket_to_string (Socket ), Caller , Reason ]),
291- {stop , Reason , Reason , State };
313+ {stop , { shutdown , Reason } , Reason , State };
292314 ok ->
293315 ? log (debug , " message=call event=transmission_succeeded driver=~s socket=\" ~s \" caller=\" ~p \" " ,
294316 [Driver , gen_rpc_helper :socket_to_string (Socket ), Caller ]),
@@ -313,7 +335,7 @@ handle_cast({{async_call,_M,_F,_A} = PacketTuple, Caller, Ref}, #state{socket=So
313335 {error , Reason } ->
314336 ? log (error , " message=async_call event=transmission_failed driver=~s socket=\" ~s \" worker_pid=\" ~p \" call_ref=\" ~p \" reason=\" ~p \" " ,
315337 [Driver , gen_rpc_helper :socket_to_string (Socket ), Caller , Ref , Reason ]),
316- {stop , Reason , Reason , State };
338+ {stop , { shutdown , Reason } , Reason , State };
317339 ok ->
318340 ? log (debug , " message=async_call event=transmission_succeeded driver=~s socket=\" ~s \" worker_pid=\" ~p \" call_ref=\" ~p \" " ,
319341 [Driver , gen_rpc_helper :socket_to_string (Socket ), Caller , Ref ]),
@@ -394,7 +416,7 @@ handle_info({keepalive, check}, #state{driver=Driver, keepalive=KeepAlive} = Sta
394416 {error , Reason } ->
395417 ? log (error , " event=keepalive_check_failed driver=~p , reason=\" ~p \" action=stopping" ,
396418 [Driver , Reason ]),
397- {stop , Reason , State }
419+ {stop , { shutdown , Reason } , State }
398420 end ;
399421
400422handle_info ({inet_reply , _Socket , ok }, State ) ->
@@ -413,10 +435,25 @@ handle_info(Msg, #state{socket=Socket, driver=Driver} = State) ->
413435code_change (_OldVsn , State , _Extra ) ->
414436 {ok , State }.
415437
438+ terminate (_Reason , # state {keepalive = undefined }) ->
439+ ok ;
416440terminate (_Reason , # state {keepalive = KeepAlive }) ->
417441 gen_rpc_keepalive :cancel (KeepAlive ),
418442 ok .
419443
444+ % %% ===================================================
445+ % %% Helper functions
446+ % %% ===================================================
447+
448+ % % Set process label for OTP >= 27
449+ -if (? OTP_RELEASE >= 27 ).
450+ set_process_label_if_supported (Label ) ->
451+ proc_lib :set_label (Label ).
452+ -else .
453+ set_process_label_if_supported (_Label ) ->
454+ ok .
455+ -endif .
456+
420457% %% ===================================================
421458% %% Private functions
422459% %% ===================================================
@@ -437,7 +474,7 @@ send_cast(PacketTuple, #state{socket=Socket, driver=Driver, driver_mod=DriverMod
437474 , driver => Driver
438475 , reason => Reason
439476 }),
440- {stop , Reason , State };
477+ {stop , { shutdown , Reason } , State };
441478 ok ->
442479 ok = case Activate of
443480 true -> DriverMod :activate_socket (Socket );
@@ -455,7 +492,7 @@ send_ping(#state{socket=Socket, driver=Driver, driver_mod=DriverMod} = State) ->
455492 {error , Reason } ->
456493 ? log (error , " message=ping event=transmission_failed driver=~s socket=\" ~s \" reason=\" ~p \" " ,
457494 [Driver , gen_rpc_helper :socket_to_string (Socket ), Reason ]),
458- {stop , Reason , State };
495+ {stop , { shutdown , Reason } , State };
459496 ok ->
460497 ? log (debug , " message=ping event=transmission_succeeded driver=~s socket=\" ~s \" " ,
461498 [Driver , gen_rpc_helper :socket_to_string (Socket )]),
@@ -496,47 +533,54 @@ cast_worker(NodeOrTuple, Cast, Ret, SendTimeout) ->
496533async_call_worker (NodeOrTuple , M , F , A , Ref ) ->
497534 TTL = gen_rpc_helper :get_async_call_inactivity_timeout (),
498535 PidName = ? NAME (NodeOrTuple ),
499- SrvPid = case gen_rpc_registry :whereis_name (PidName ) of
536+ case gen_rpc_registry :whereis_name (PidName ) of
500537 undefined ->
501538 ? log (info , " event=client_process_not_found target=\" ~p \" action=spawning_client" , [NodeOrTuple ]),
502539 case gen_rpc_dispatcher :start_client (NodeOrTuple ) of
503540 {ok , NewPid } ->
541+ % % Monitor the client process in case it dies before handling the cast
542+ MRef = erlang :monitor (process , NewPid ),
504543 ok = gen_server :cast (NewPid , {{async_call ,M ,F ,A }, self (), Ref }),
505- NewPid ;
544+ wait_for_async_reply ( NewPid , MRef , Ref , TTL ) ;
506545 {error , {badrpc ,_ } = RpcError } ->
507- RpcError
546+ wait_for_yield_and_send ( RpcError , Ref , TTL )
508547 end ;
509548 Pid ->
510549 ? log (debug , " event=client_process_found pid=\" ~p \" target=\" ~p \" " , [Pid , NodeOrTuple ]),
550+ % % Monitor the client process in case it dies before handling the cast
551+ MRef = erlang :monitor (process , Pid ),
511552 ok = gen_server :cast (Pid , {{async_call ,M ,F ,A }, self (), Ref }),
512- Pid
513- end ,
514- case SrvPid of
515- SrvPid when is_pid (SrvPid ) ->
516- receive
517- % % Wait for the reply from the node's gen_rpc client process
518- {SrvPid ,Ref ,async_call ,Reply } ->
519- % % Wait for a yield request from the caller
520- receive
521- {YieldPid ,Ref ,yield } ->
522- YieldPid ! {self (), Ref , async_call , Reply }
523- after
524- TTL ->
525- exit ({error , async_call_cleanup_timeout_reached })
526- end
527- after
528- TTL ->
529- exit ({error , async_call_cleanup_timeout_reached })
530- end ;
531- TRpcError ->
532- % % Wait for a yield request from the caller
533- receive
534- {YieldPid ,Ref ,yield } ->
535- YieldPid ! {self (), Ref , async_call , TRpcError }
536- after
537- TTL ->
538- exit ({error , async_call_cleanup_timeout_reached })
539- end
553+ wait_for_async_reply (Pid , MRef , Ref , TTL )
554+ end .
555+
556+ wait_for_async_reply (Pid , MRef , Ref , TTL ) ->
557+ receive
558+ % % Wait for the reply from the node's gen_rpc client process
559+ {Pid ,Ref ,async_call ,Reply } ->
560+ erlang :demonitor (MRef , [flush ]),
561+ wait_for_yield_and_send (Reply , Ref , TTL );
562+ {'DOWN' , MRef , process , Pid , Reason } ->
563+ % % Client process died before handling the cast
564+ ErrorReply = case Reason of
565+ {shutdown , {badrpc , _ } = BadRpc } -> BadRpc ;
566+ {shutdown , ShutdownReason } -> {badrpc , ShutdownReason };
567+ _ -> {badrpc , Reason }
568+ end ,
569+ wait_for_yield_and_send (ErrorReply , Ref , TTL )
570+ after
571+ TTL ->
572+ erlang :demonitor (MRef , [flush ]),
573+ exit ({error , async_call_cleanup_timeout_reached })
574+ end .
575+
576+ wait_for_yield_and_send (Result , Ref , TTL ) ->
577+ % % Wait for a yield request from the caller and send the result
578+ receive
579+ {YieldPid ,Ref ,yield } ->
580+ YieldPid ! {self (), Ref , async_call , Result }
581+ after
582+ TTL ->
583+ exit ({error , async_call_cleanup_timeout_reached })
540584 end .
541585
542586parse_multicall_results (Keys , Nodes , undefined ) ->
0 commit comments