Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

283 linhas
10 KiB

17 anos atrás
17 anos atrás
17 anos atrás
17 anos atrás
17 anos atrás
17 anos atrás
17 anos atrás
17 anos atrás
17 anos atrás
17 anos atrás
13 anos atrás
13 anos atrás
17 anos atrás
17 anos atrás
13 anos atrás
13 anos atrás
17 anos atrás
17 anos atrás
17 anos atrás
  1. %%%-------------------------------------------------------------------
  2. %%% File : ibrowse_lb.erl
  3. %%% Author : chandru <chandrashekhar.mullaparthi@t-mobile.co.uk>
  4. %%% Description :
  5. %%%
  6. %%% Created : 6 Mar 2008 by chandru <chandrashekhar.mullaparthi@t-mobile.co.uk>
  7. %%%-------------------------------------------------------------------
  8. -module(ibrowse_lb).
  9. -author(chandru).
  10. -behaviour(gen_server).
  11. %% External exports
  12. -export([
  13. start_link/1,
  14. spawn_connection/6,
  15. stop/1,
  16. report_connection_down/1,
  17. report_request_complete/1
  18. ]).
  19. %% gen_server callbacks
  20. -export([
  21. init/1,
  22. handle_call/3,
  23. handle_cast/2,
  24. handle_info/2,
  25. terminate/2,
  26. code_change/3
  27. ]).
  28. -record(state, {parent_pid,
  29. ets_tid,
  30. host,
  31. port,
  32. max_sessions,
  33. max_pipeline_size,
  34. proc_state}).
  35. -define(PIPELINE_MAX, 99999).
  36. -define(MAX_RETRIES, 3).
  37. -define(KEY_MATCHSPEC_BY_PID(Pid), [{{{'_', '_', Pid}, '_'}, [], ['$_']}]).
  38. -define(KEY_MATCHSPEC_BY_PID_FOR_DELETE(Pid), [{{{'_', '_', Pid}, '_'}, [], [true]}]).
  39. -define(KEY_MATCHSPEC_FOR_DELETE(Key), [{{Key, '_'}, [], [true]}]).
  40. -include("ibrowse.hrl").
  41. %%====================================================================
  42. %% External functions
  43. %%====================================================================
  44. %%--------------------------------------------------------------------
  45. %% Function: start_link/0
  46. %% Description: Starts the server
  47. %%--------------------------------------------------------------------
  48. start_link(Args) ->
  49. gen_server:start_link(?MODULE, Args, []).
  50. spawn_connection(Lb_pid,
  51. Url,
  52. Max_sessions,
  53. Max_pipeline_size,
  54. SSL_options,
  55. Process_options)
  56. when is_pid(Lb_pid),
  57. is_record(Url, url),
  58. is_integer(Max_pipeline_size),
  59. is_integer(Max_sessions) ->
  60. gen_server:call(Lb_pid,
  61. {spawn_connection, Url, Max_sessions, Max_pipeline_size, SSL_options, Process_options}).
  62. stop(Lb_pid) ->
  63. case catch gen_server:call(Lb_pid, stop) of
  64. {'EXIT', {timeout, _}} ->
  65. exit(Lb_pid, kill);
  66. ok ->
  67. ok
  68. end.
  69. report_connection_down(Tid) ->
  70. %% Don't cascade errors since Tid is really managed by other process
  71. catch ets:select_delete(Tid, ?KEY_MATCHSPEC_BY_PID_FOR_DELETE(self())).
  72. report_request_complete(Tid) ->
  73. report_request_complete(Tid, ?MAX_RETRIES).
  74. %%====================================================================
  75. %% Server functions
  76. %%====================================================================
  77. %%--------------------------------------------------------------------
  78. %% Function: init/1
  79. %% Description: Initiates the server
  80. %% Returns: {ok, State} |
  81. %% {ok, State, Timeout} |
  82. %% ignore |
  83. %% {stop, Reason}
  84. %%--------------------------------------------------------------------
  85. init([Host, Port]) ->
  86. process_flag(trap_exit, true),
  87. Max_sessions = ibrowse:get_config_value({max_sessions, Host, Port}, 10),
  88. Max_pipe_sz = ibrowse:get_config_value({max_pipeline_size, Host, Port}, 10),
  89. put(my_trace_flag, ibrowse_lib:get_trace_status(Host, Port)),
  90. put(ibrowse_trace_token, ["LB: ", Host, $:, integer_to_list(Port)]),
  91. {ok, #state{parent_pid = whereis(ibrowse),
  92. host = Host,
  93. port = Port,
  94. max_pipeline_size = Max_pipe_sz,
  95. max_sessions = Max_sessions}}.
  96. %%--------------------------------------------------------------------
  97. %% Function: handle_call/3
  98. %% Description: Handling call messages
  99. %% Returns: {reply, Reply, State} |
  100. %% {reply, Reply, State, Timeout} |
  101. %% {noreply, State} |
  102. %% {noreply, State, Timeout} |
  103. %% {stop, Reason, Reply, State} | (terminate/2 is called)
  104. %% {stop, Reason, State} (terminate/2 is called)
  105. %%--------------------------------------------------------------------
  106. handle_call(stop, _From, #state{ets_tid = undefined} = State) ->
  107. gen_server:reply(_From, ok),
  108. {stop, normal, State};
  109. handle_call(stop, _From, #state{ets_tid = Tid} = State) ->
  110. for_each_connection_pid(Tid, fun(Pid) -> ibrowse_http_client:stop(Pid) end),
  111. gen_server:reply(_From, ok),
  112. {stop, normal, State};
  113. handle_call(_, _From, #state{proc_state = shutting_down} = State) ->
  114. {reply, {error, shutting_down}, State};
  115. handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_options}, _From, State) ->
  116. State_1 = maybe_create_ets(State),
  117. Tid = State_1#state.ets_tid,
  118. Reply = case num_current_connections(Tid) of
  119. X when X >= Max_sess ->
  120. find_best_connection(Tid, Max_pipe);
  121. _ ->
  122. Result = {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options),
  123. record_new_connection(Tid, Pid),
  124. Result
  125. end,
  126. {reply, Reply, State_1#state{max_sessions = Max_sess, max_pipeline_size = Max_pipe}};
  127. handle_call(Request, _From, State) ->
  128. Reply = {unknown_request, Request},
  129. {reply, Reply, State}.
  130. %%--------------------------------------------------------------------
  131. %% Function: handle_cast/2
  132. %% Description: Handling cast messages
  133. %% Returns: {noreply, State} |
  134. %% {noreply, State, Timeout} |
  135. %% {stop, Reason, State} (terminate/2 is called)
  136. %%--------------------------------------------------------------------
  137. handle_cast(_Msg, State) ->
  138. {noreply, State}.
  139. %%--------------------------------------------------------------------
  140. %% Function: handle_info/2
  141. %% Description: Handling all non call/cast messages
  142. %% Returns: {noreply, State} |
  143. %% {noreply, State, Timeout} |
  144. %% {stop, Reason, State} (terminate/2 is called)
  145. %%--------------------------------------------------------------------
  146. handle_info({'EXIT', Parent, _Reason}, #state{parent_pid = Parent} = State) ->
  147. {stop, normal, State};
  148. handle_info({trace, Bool}, #state{ets_tid = undefined} = State) ->
  149. put(my_trace_flag, Bool),
  150. {noreply, State};
  151. handle_info({trace, Bool}, #state{ets_tid = Tid} = State) ->
  152. for_each_connection_pid(Tid, fun(Pid) -> ibrowse_http_client:trace(Pid, Bool) end),
  153. put(my_trace_flag, Bool),
  154. {noreply, State};
  155. handle_info(timeout, State) ->
  156. %% We can't shutdown the process immediately because a request
  157. %% might be in flight. So we first remove the entry from the
  158. %% load balancer named ets table, and then shutdown a couple
  159. %% of seconds later
  160. ets:delete(?LOAD_BALANCER_NAMED_TABLE, {State#state.host, State#state.port}),
  161. erlang:send_after(2000, self(), shutdown),
  162. {noreply, State#state{proc_state = shutting_down}};
  163. handle_info(shutdown, State) ->
  164. {stop, normal, State};
  165. handle_info(_Info, State) ->
  166. {noreply, State}.
  167. %%--------------------------------------------------------------------
  168. %% Function: terminate/2
  169. %% Description: Shutdown the server
  170. %% Returns: any (ignored by gen_server)
  171. %%--------------------------------------------------------------------
  172. terminate(_Reason, #state{host = Host, port = Port}) ->
  173. % Use delete_object instead of delete in case another process for this host/port
  174. % has been spawned, in which case will be deleting the wrong record because pid won't match.
  175. ets:delete_object(?LOAD_BALANCER_NAMED_TABLE, #lb_pid{host_port = {Host, Port}, pid = self()}),
  176. ok.
  177. %%--------------------------------------------------------------------
  178. %% Func: code_change/3
  179. %% Purpose: Convert process state when code is changed
  180. %% Returns: {ok, NewState}
  181. %%--------------------------------------------------------------------
  182. code_change(_OldVsn, State, _Extra) ->
  183. {ok, State}.
  184. %%--------------------------------------------------------------------
  185. %%% Internal functions
  186. %%--------------------------------------------------------------------
  187. find_best_connection(Tid, Max_pipeline_size) ->
  188. find_best_connection(Tid, Max_pipeline_size, ?MAX_RETRIES).
  189. find_best_connection(_Tid, _Max_pipeline_size, 0) ->
  190. {error, retry_later};
  191. find_best_connection(Tid, Max_pipeline_size, RemainingRetries) ->
  192. case ets:first(Tid) of
  193. {Size, _Timestamp, Pid} = Key when Size < Max_pipeline_size ->
  194. case record_request_for_connection(Tid, Key) of
  195. true ->
  196. {ok, Pid};
  197. false ->
  198. find_best_connection(Tid, Max_pipeline_size, RemainingRetries - 1)
  199. end;
  200. _ ->
  201. {error, retry_later}
  202. end.
  203. maybe_create_ets(#state{ets_tid = undefined} = State) ->
  204. Tid = ets:new(?CONNECTIONS_LOCAL_TABLE, [public, ordered_set]),
  205. State#state{ets_tid = Tid};
  206. maybe_create_ets(State) ->
  207. State.
  208. %% Ets connection table utility methods
  209. num_current_connections(Tid) ->
  210. catch ets:info(Tid, size).
  211. record_new_connection(Tid, Pid) ->
  212. catch ets:insert(Tid, {new_key(Pid), undefined}).
  213. record_request_for_connection(Tid, Key) ->
  214. case ets:select_delete(Tid, ?KEY_MATCHSPEC_FOR_DELETE(Key)) of
  215. 1 ->
  216. ets:insert(Tid, {incremented(Key), undefined}),
  217. true;
  218. _ ->
  219. false
  220. end.
  221. report_request_complete(_Tid, 0) ->
  222. false;
  223. report_request_complete(Tid, RemainingRetries) ->
  224. %% Don't cascade errors since Tid is really managed by other process
  225. catch case ets:select(Tid, ?KEY_MATCHSPEC_BY_PID(self())) of
  226. [MatchKey] ->
  227. case ets:select_delete(Tid, ?KEY_MATCHSPEC_FOR_DELETE(MatchKey)) of
  228. 1 ->
  229. ets:insert(Tid, {decremented(MatchKey), undefined}),
  230. true;
  231. _ ->
  232. report_request_complete(Tid, RemainingRetries - 1)
  233. end;
  234. _ ->
  235. false
  236. end.
  237. new_key(Pid) ->
  238. {1, os:timestamp(), Pid}.
  239. incremented({Size, Timestamp, Pid}) ->
  240. {Size + 1, Timestamp, Pid}.
  241. decremented({Size, _Timestamp, Pid}) ->
  242. {Size - 1, os:timestamp(), Pid}.
  243. for_each_connection_pid(Tid, Fun) ->
  244. catch ets:foldl(fun({Pid, _}, _) -> Fun(Pid) end, undefined, Tid),
  245. ok.