PageRenderTime 56ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/src/mysql/mysql_recv.erl

http://github.com/evanmiller/ChicagoBoss
Erlang | 173 lines | 83 code | 11 blank | 79 comment | 0 complexity | 347e76d638286fc66e94fa997caae325 MD5 | raw file
  1. %%%-------------------------------------------------------------------
  2. %%% File : mysql_recv.erl
  3. %%% Author : Fredrik Thulin <ft@it.su.se>
  4. %%% Descrip.: Handles data being received on a MySQL socket. Decodes
  5. %%% per-row framing and sends each row to parent.
  6. %%%
  7. %%% Created : 4 Aug 2005 by Fredrik Thulin <ft@it.su.se>
  8. %%%
  9. %%% Note : All MySQL code was written by Magnus Ahltorp, originally
  10. %%% in the file mysql.erl - I just moved it here.
  11. %%%
  12. %%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan
  13. %%% See the file COPYING
  14. %%%
  15. %%% Signals this receiver process can send to it's parent
  16. %%% (the parent is a mysql_conn connection handler) :
  17. %%%
  18. %%% {mysql_recv, self(), data, Packet, Num}
  19. %%% {mysql_recv, self(), closed, {error, Reason}}
  20. %%% {mysql_recv, self(), closed, normal}
  21. %%%
  22. %%% Internally (from inside init/4 to start_link/4) the
  23. %%% following signals may be sent to the parent process :
  24. %%%
  25. %%% {mysql_recv, self(), init, {ok, Sock}}
  26. %%% {mysql_recv, self(), init, {error, E}}
  27. %%%
  28. %%%-------------------------------------------------------------------
  29. -module(mysql_recv).
  30. %%--------------------------------------------------------------------
  31. %% External exports (should only be used by the 'mysql_conn' module)
  32. %%--------------------------------------------------------------------
  33. -export([start_link/4
  34. ]).
  35. -record(state, {
  36. socket,
  37. parent,
  38. log_fun,
  39. data
  40. }).
  41. -define(SECURE_CONNECTION, 32768).
  42. -define(CONNECT_TIMEOUT, 5000).
  43. %%====================================================================
  44. %% External functions
  45. %%====================================================================
  46. %%--------------------------------------------------------------------
  47. %% Function: start_link(Host, Port, LogFun, Parent)
  48. %% Host = string()
  49. %% Port = integer()
  50. %% LogFun = undefined | function() of arity 3
  51. %% Parent = pid(), process that should get received frames
  52. %% Descrip.: Start a process that connects to Host:Port and waits for
  53. %% data. When it has received a MySQL frame, it sends it to
  54. %% Parent and waits for the next frame.
  55. %% Returns : {ok, RecvPid, Socket} |
  56. %% {error, Reason}
  57. %% RecvPid = pid(), receiver process pid
  58. %% Socket = term(), gen_tcp socket
  59. %% Reason = atom() | string()
  60. %%--------------------------------------------------------------------
  61. start_link(Host, Port, LogFun, Parent) when is_list(Host), is_integer(Port) ->
  62. RecvPid =
  63. spawn_link(fun () ->
  64. init(Host, Port, LogFun, Parent)
  65. end),
  66. %% wait for the socket from the spawned pid
  67. receive
  68. {mysql_recv, RecvPid, init, {error, E}} ->
  69. {error, E};
  70. {mysql_recv, RecvPid, init, {ok, Socket}} ->
  71. {ok, RecvPid, Socket}
  72. after ?CONNECT_TIMEOUT ->
  73. catch exit(RecvPid, kill),
  74. {error, "timeout"}
  75. end.
  76. %%====================================================================
  77. %% Internal functions
  78. %%====================================================================
  79. %%--------------------------------------------------------------------
  80. %% Function: init((Host, Port, LogFun, Parent)
  81. %% Host = string()
  82. %% Port = integer()
  83. %% LogFun = undefined | function() of arity 3
  84. %% Parent = pid(), process that should get received frames
  85. %% Descrip.: Connect to Host:Port and then enter receive-loop.
  86. %% Returns : error | never returns
  87. %%--------------------------------------------------------------------
  88. init(Host, Port, LogFun, Parent) ->
  89. case gen_tcp:connect(Host, Port, [binary, {packet, 0}]) of
  90. {ok, Sock} ->
  91. Parent ! {mysql_recv, self(), init, {ok, Sock}},
  92. State = #state{socket = Sock,
  93. parent = Parent,
  94. log_fun = LogFun,
  95. data = <<>>
  96. },
  97. loop(State);
  98. E ->
  99. LogFun(?MODULE, ?LINE, error,
  100. fun() ->
  101. {"mysql_recv: Failed connecting to ~p:~p : ~p",
  102. [Host, Port, E]}
  103. end),
  104. Msg = lists:flatten(io_lib:format("connect failed : ~p", [E])),
  105. Parent ! {mysql_recv, self(), init, {error, Msg}}
  106. end.
  107. %%--------------------------------------------------------------------
  108. %% Function: loop(State)
  109. %% State = state record()
  110. %% Descrip.: The main loop. Wait for data from our TCP socket and act
  111. %% on received data or signals that our socket was closed.
  112. %% Returns : error | never returns
  113. %%--------------------------------------------------------------------
  114. loop(State) ->
  115. Sock = State#state.socket,
  116. receive
  117. {tcp, Sock, InData} ->
  118. NewData = list_to_binary([State#state.data, InData]),
  119. %% send data to parent if we have enough data
  120. Rest = sendpacket(State#state.parent, NewData),
  121. loop(State#state{data = Rest});
  122. {tcp_error, Sock, Reason} ->
  123. LogFun = State#state.log_fun,
  124. LogFun(?MODULE, ?LINE, error,
  125. fun() ->
  126. {"mysql_recv: Socket ~p closed : ~p",
  127. [Sock, Reason]}
  128. end),
  129. State#state.parent ! {mysql_recv, self(), closed, {error, Reason}},
  130. error;
  131. {tcp_closed, Sock} ->
  132. LogFun = State#state.log_fun,
  133. LogFun(?MODULE, ?LINE, debug,
  134. fun() ->
  135. {"mysql_recv: Socket ~p closed", [Sock]}
  136. end),
  137. State#state.parent ! {mysql_recv, self(), closed, normal},
  138. error
  139. end.
  140. %%--------------------------------------------------------------------
  141. %% Function: sendpacket(Parent, Data)
  142. %% Parent = pid()
  143. %% Data = binary()
  144. %% Descrip.: Check if we have received one or more complete frames by
  145. %% now, and if so - send them to Parent.
  146. %% Returns : Rest = binary()
  147. %%--------------------------------------------------------------------
  148. %% send data to parent if we have enough data
  149. sendpacket(Parent, Data) ->
  150. case Data of
  151. <<Length:24/little, Num:8, D/binary>> ->
  152. if
  153. Length =< size(D) ->
  154. {Packet, Rest} = split_binary(D, Length),
  155. Parent ! {mysql_recv, self(), data, Packet, Num},
  156. sendpacket(Parent, Rest);
  157. true ->
  158. Data
  159. end;
  160. _ ->
  161. Data
  162. end.