25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 

305 satır
10 KiB

-module(agTcpAgency).
-include("agHttpCli.hrl").
-compile(inline).
-compile({inline_size, 512}).
-export([
%% 内部行为API
start_link/3,
init_it/3,
system_code_change/4,
system_continue/3,
system_get_state/1,
system_terminate/4,
init/1,
handle_msg/4,
terminate/2
]).
-record(srvState, {
initOpts :: initOpts(),
ip :: inet:ip_address() | inet:hostname(),
name :: serverName(),
pool_name :: pool_name(),
port :: inet:port_number(),
reconnect_state :: undefined | reconnect_state(),
socket :: undefined | inet:socket(),
socket_options :: [gen_tcp:connect_option()],
timer_ref :: undefined | reference()
}).
-record(cliState, {
initOpts :: initOpts(),
ip :: inet:ip_address() | inet:hostname(),
name :: serverName(),
pool_name :: pool_name(),
port :: inet:port_number(),
reconnect_state :: undefined | reconnect_state(),
socket :: undefined | inet:socket(),
socket_options :: [gen_tcp:connect_option()],
timer_ref :: undefined | reference()
}).
-type state() :: #srvState {}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% genActor start %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec start_link(module(), atom(), term(), [proc_lib:spawn_option()]) -> {ok, pid()}.
start_link(ServerName, Args, SpawnOpts) ->
proc_lib:start_link(?MODULE, init_it, [ServerName, self(), Args], infinity, SpawnOpts).
init_it(ServerName, Parent, Args) ->
case safeRegister(ServerName) of
true ->
process_flag(trap_exit, true),
moduleInit(Parent, Args);
{false, Pid} ->
proc_lib:init_ack(Parent, {error, {already_started, Pid}})
end.
%% sys callbacks
-spec system_code_change(term(), module(), undefined | term(), term()) -> {ok, term()}.
system_code_change(MiscState, _Module, _OldVsn, _Extra) ->
{ok, MiscState}.
-spec system_continue(pid(), [], {module(), atom(), pid(), term()}) -> ok.
system_continue(_Parent, _Debug, {Parent, SrvState, CliState}) ->
loop(Parent, SrvState, CliState).
-spec system_get_state(term()) -> {ok, term()}.
system_get_state({_Parent, SrvState, _CliState}) ->
{ok, SrvState}.
-spec system_terminate(term(), pid(), [], term()) -> none().
system_terminate(Reason, _Parent, _Debug, {_Parent, SrvState, CliState}) ->
terminate(Reason, SrvState, CliState).
safeRegister(Name) ->
try register(Name, self()) of
true -> true
catch
_:_ -> {false, whereis(Name)}
end.
moduleInit(Parent, Args) ->
case ?MODULE:init(Args) of
{ok, SrvState, CliState} ->
proc_lib:init_ack(Parent, {ok, self()}),
loop(Parent, SrvState, CliState);
{stop, Reason} ->
proc_lib:init_ack(Parent, {error, Reason}),
exit(Reason)
end.
loop(Parent, SrvState, CliState) ->
receive
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {Parent, SrvState, CliState});
{'EXIT', Parent, Reason} ->
terminate(Reason, SrvState, CliState);
Msg ->
{ok, NewSrvState, NewCliState} = ?MODULE:handleMsg(Msg, SrvState, CliState),
loop(Parent, NewSrvState, NewCliState)
end.
terminate(Reason, SrvState, CliState) ->
?MODULE:terminate(Reason, SrvState, CliState),
exit(Reason).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% genActor end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec init(clientOpts()) -> no_return().
init(ClientOpts) ->
self() ! ?MSG_CONNECT,
%%ok = shackle_backlog:new(Name),
InitOptions = ?GET_FROM_LIST(initOpts, ClientOpts, ?DEFAULT_INIT_OPTS),
Protocol = ?GET_FROM_LIST(protocol, ClientOpts, ?DEFAULT_PROTOCOL),
Ip = ?GET_FROM_LIST(ip, ClientOpts, ?DEFAULT_IP),
Port = ?GET_FROM_LIST(port, ClientOpts, ?DEFAULT_PORTO(Protocol)),
ReconnectState = agAgencyUtils:initReconnectState(ClientOpts),
SocketOptions = ?GET_FROM_LIST(socketOpts, ClientOptions, ?DEFAULT_SOCKET_OPTS),
{ok, #srvState{initOpts = InitOptions, ip = Ip, port = Port, reconnect_state = ReconnectState, socket_options = SocketOptions}, undefined}.
-spec handleMsg(term(), {state(), client_state()}) -> {ok, term()}.
handleMsg({_, #request{} = Cast}, #srvState{socket = undefined, name = Name} = SrvState, CliState) ->
agAgencyUtils:agencyReply(Name, {error, no_socket}, Cast),
{ok, {SrvState, CliState}};
handleMsg({Request, #request{timeout = Timeout} = Cast},
#srvState{name = Name, pool_name = PoolName, socket = Socket} = State,
ClientState) ->
try agNetCli:handleRequest(Request, ClientState) of
{ok, ExtRequestId, Data, ClientState2} ->
case gen_tcp:send(Socket, Data) of
ok ->
Msg = {timeout, ExtRequestId},
TimerRef = erlang:send_after(Timeout, self(), Msg),
shackle_queue:add(ExtRequestId, Cast, TimerRef),
{ok, {State, ClientState2}};
{error, Reason} ->
?WARN(PoolName, "send error: ~p", [Reason]),
gen_tcp:close(Socket),
agAgencyUtils:agencyReply(Name, {error, socket_closed}, Cast),
close(State, ClientState2)
end
catch
?EXCEPTION(E, R, Stacktrace) ->
?WARN(PoolName, "handleRequest crash: ~p:~p~n~p~n",
[E, R, ?GET_STACK(Stacktrace)]),
agAgencyUtils:agencyReply(Name, {error, client_crash}, Cast),
{ok, {State, ClientState}}
end;
handleMsg({tcp, Socket, Data},
#srvState{name = Name, pool_name = PoolName, socket = Socket} = SrvState,
CliState) ->
try agNetCli:handleData(Data, CliState) of
{ok, Replies, ClientState2} ->
agAgencyUtils:agencyResponses(Replies, Name),
{ok, SrvState, ClientState2};
{error, Reason, ClientState2} ->
?WARN(PoolName, "handleData error: ~p", [Reason]),
gen_tcp:close(Socket),
close(State, ClientState2)
catch
?EXCEPTION(E, R, Stacktrace) ->
?WARN(PoolName, "handleData crash: ~p:~p~n~p~n",
[E, R, ?GET_STACK(Stacktrace)]),
gen_tcp:close(Socket),
close(State, ClientState)
end;
handleMsg({timeout, ExtRequestId}, {#srvState{
name = Name
} = State, ClientState}) ->
case shackle_queue:remove(Name, ExtRequestId) of
{ok, Cast, _TimerRef} ->
agAgencyUtils:agencyReply(Name, {error, timeout}, Cast);
{error, not_found} ->
ok
end,
{ok, {State, ClientState}};
handleMsg({tcp_closed, Socket}, {#srvState{
socket = Socket,
pool_name = PoolName
} = State, ClientState}) ->
?WARN(PoolName, "connection closed", []),
close(State, ClientState);
handleMsg({tcp_error, Socket, Reason}, {#srvState{
socket = Socket,
pool_name = PoolName
} = State, ClientState}) ->
?WARN(PoolName, "connection error: ~p", [Reason]),
gen_tcp:close(Socket),
close(State, ClientState);
handleMsg(?MSG_CONNECT, {#srvState{
client = Client,
initOpts = Init,
ip = Ip,
pool_name = PoolName,
port = Port,
reconnect_state = ReconnectState,
socket_options = SocketOptions
} = State, ClientState}) ->
case connect(PoolName, Ip, Port, SocketOptions) of
{ok, Socket} ->
ClientState2 = agHttpProtocol:bin_patterns(),
ReconnectState2 = agAgencyUtils:resetReconnectState(ReconnectState),
{ok, {State#srvState{
reconnect_state = ReconnectState2,
socket = Socket
}, ClientState2}};
{error, _Reason} ->
reconnect(State, ClientState)
end;
handleMsg(Msg, {#srvState{
pool_name = PoolName
} = State, ClientState}) ->
?WARN(PoolName, "unknown msg: ~p", [Msg]),
{ok, {State, ClientState}}.
-spec terminate(term(), term()) ->
ok.
terminate(_Reason, {#srvState{
client = Client,
name = Name,
pool_name = PoolName,
timer_ref = TimerRef
}, ClientState}) ->
agAgencyUtils:cancel_timer(TimerRef),
try agNetCli:terminate(ClientState)
catch
?EXCEPTION(E, R, Stacktrace) ->
?WARN(PoolName, "terminate crash: ~p:~p~n~p~n",
[E, R, ?GET_STACK(Stacktrace)])
end,
agAgencyUtils:agencyReplyAll(Name, {error, shutdown}),
shackle_backlog:delete(Name),
ok.
%% private
close(#srvState{name = Name} = State, ClientState) ->
agAgencyUtils:agencyReplyAll(Name, {error, socket_closed}),
reconnect(State, ClientState).
connect(PoolName, Ip, Port, SocketOptions) ->
case inet:getaddrs(Ip, inet) of
{ok, Addrs} ->
Ip2 = agMiscUtils:randomElement(Addrs),
case gen_tcp:connect(Ip2, Port, SocketOptions,
?DEFAULT_CONNECT_TIMEOUT) of
{ok, Socket} ->
{ok, Socket};
{error, Reason} ->
?WARN(PoolName, "connect error: ~p", [Reason]),
{error, Reason}
end;
{error, Reason} ->
?WARN(PoolName, "getaddrs error: ~p", [Reason]),
{error, Reason}
end.
reconnect(State, undefined) ->
reconnect_timer(State, undefined);
reconnect(#srvState{
client = Client,
pool_name = PoolName
} = State, ClientState) ->
try agNetCli:terminate(ClientState)
catch
?EXCEPTION(E, R, Stacktrace) ->
?WARN(PoolName, "terminate crash: ~p:~p~n~p~n",
[E, R, ?GET_STACK(Stacktrace)])
end,
reconnect_timer(State, ClientState).
reconnect_timer(#srvState{
reconnect_state = undefined
} = State, ClientState) ->
{ok, {State#srvState{
socket = undefined
}, ClientState}};
reconnect_timer(#srvState{
reconnect_state = ReconnectState
} = State, ClientState) ->
ReconnectState2 = shackle_backoff:timeout(ReconnectState),
#reconnect_state {current = Current} = ReconnectState2,
TimerRef = erlang:send_after(Current, self(), ?MSG_CONNECT),
{ok, {State#srvState{
reconnect_state = ReconnectState2,
socket = undefined,
timer_ref = TimerRef
}, ClientState}}.