You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

283 rivejä
10 KiB

17 vuotta sitten
17 vuotta sitten
17 vuotta sitten
17 vuotta sitten
17 vuotta sitten
17 vuotta sitten
17 vuotta sitten
17 vuotta sitten
17 vuotta sitten
17 vuotta sitten
13 vuotta sitten
13 vuotta sitten
17 vuotta sitten
17 vuotta sitten
13 vuotta sitten
13 vuotta sitten
17 vuotta sitten
17 vuotta sitten
17 vuotta sitten
  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.