Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

283 wiersze
10 KiB

  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.