-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}}.