PageRenderTime 57ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/memcached.erl

https://github.com/fauxsoup/memcached-client
Erlang | 875 lines | 511 code | 139 blank | 225 comment | 8 complexity | 197a87eefaddd14a0f69b40cab7e30b8 MD5 | raw file
  1. %% Copyright (c) 2009-2010 Taro Minowa(Higepon) <higepon@users.sourceforge.jp>
  2. %%
  3. %% Redistribution and use in source and binary forms, with or without
  4. %% modification, are permitted provided that the following conditions
  5. %% are met:
  6. %%
  7. %% 1. Redistributions of source code must retain the above copyright
  8. %% notice, this list of conditions and the following disclaimer.
  9. %%
  10. %% 2. Redistributions in binary form must reproduce the above copyright
  11. %% notice, this list of conditions and the following disclaimer in the
  12. %% documentation and/or other materials provided with the distribution.
  13. %%
  14. %% 3. Neither the name of the authors nor the names of its contributors
  15. %% may be used to endorse or promote products derived from this
  16. %% software without specific prior written permission.
  17. %%
  18. %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. %% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. %% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. %% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. %% OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. %% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  24. %% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  25. %% PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  26. %% LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  27. %% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  28. %% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. %%%-------------------------------------------------------------------
  30. %%% File : memcached.erl
  31. %%% Author : Taro Minowa(Higepon) <higepon@users.sourceforge.jp>
  32. %%% Description : A minimal memcached client library.
  33. %%%
  34. %%% Created : 7 Dec 2009 by higepon <higepon@users.sourceforge.jp>
  35. %%%-------------------------------------------------------------------
  36. -module(memcached).
  37. -behaviour(gen_server).
  38. %% API
  39. -export([connect/1, connect/2, disconnect/1,
  40. set/3, set/5, setb/3, setb/5,
  41. cas/6, casb/6,
  42. get/2, getb/2, gets/2, getsb/2,
  43. get_multi/2, get_multib/2,
  44. gets_multi/2, gets_multib/2,
  45. replace/3, replace/5, replaceb/3, replaceb/5,
  46. add/3, add/5, addb/3, addb/5,
  47. append/3, prepend/3,
  48. delete/2,
  49. incr/3, decr/3,
  50. version/1,
  51. quit/1,
  52. stats/1,
  53. split/1,
  54. flush_all/1, flush_all/2
  55. ]).
  56. %% gen_server callbacks
  57. -export([start_link/4, new_conn/1, init/1, handle_call/3, handle_cast/2, handle_info/2,
  58. terminate/2, code_change/3]).
  59. %%====================================================================
  60. %% Definitions
  61. %%====================================================================
  62. -define(TCP_OPTIONS, [binary, {packet, raw}, {nodelay, true}, {reuseaddr, true}, {active, false},
  63. {sndbuf,16384},{recbuf,4096}]).
  64. -define(TIMEOUT, 10000).
  65. -define(CR, 13).
  66. -define(LF, 10).
  67. %%====================================================================
  68. %% API
  69. %%====================================================================
  70. start_link(Owner, Host, Port, Opts) ->
  71. gen_server:start_link(?MODULE, [Owner, Host, Port, Opts], []).
  72. %start_link(Config) -> connect(Config).
  73. %% @spec connect(Host::string(), Port::integer()) -> {ok, Conn} | {error, Reason}
  74. connect(Host, Port) ->
  75. memcached_sup:connect(self(), Host, Port, []).
  76. %gen_server:start_link(?MODULE, [Host, Port], []).
  77. connect(HostPortSpecs) ->
  78. [{Host, Port} | _More] = HostPortSpecs,
  79. memcached_sup:connect(self(), Host, Port, []).
  80. %gen_server:start_link(?MODULE, [Host, Port], []).
  81. new_conn(HostPortSpecs) ->
  82. [{Host, Port} | _More] = HostPortSpecs,
  83. memcached_sup:connect(self(), Host, Port, []).
  84. %%--------------------------------------------------------------------
  85. %% Function: set
  86. %% Description: set value
  87. %% Returns: ok
  88. %%--------------------------------------------------------------------
  89. set(Conn, Key, Value) ->
  90. setb(Conn, Key, term_to_binary(Value)).
  91. set(Conn, Key, Value, Flags, ExpTime) ->
  92. setb(Conn, Key, term_to_binary(Value), Flags, ExpTime).
  93. %%--------------------------------------------------------------------
  94. %% Function: setb
  95. %% Description: set binary value
  96. %% Returns: ok
  97. %%--------------------------------------------------------------------
  98. setb(Conn, Key, Value) when is_list(Key) andalso is_binary(Value) ->
  99. gen_server:call(Conn, {setb, Key, Value}).
  100. setb(Conn, Key, Value, Flags, ExpTime) when is_list(Key) andalso is_binary(Value) andalso is_integer(Flags) andalso is_integer(ExpTime) ->
  101. gen_server:call(Conn, {setb, Key, Value, Flags, ExpTime}).
  102. %%--------------------------------------------------------------------
  103. %% Function: cas
  104. %% Description: set cas
  105. %% Returns: ok
  106. %%--------------------------------------------------------------------
  107. cas(Conn, Key, Value, Flags, ExpTime, CasUnique64) ->
  108. casb(Conn, Key, term_to_binary(Value), Flags, ExpTime, CasUnique64).
  109. %%--------------------------------------------------------------------
  110. %% Function: casb
  111. %% Description: set cas
  112. %% Returns: ok
  113. %%--------------------------------------------------------------------
  114. casb(Conn, Key, Value, Flags, ExpTime, CasUnique64) when is_binary(Value) ->
  115. gen_server:call(Conn, {casb, Key, Value, Flags, ExpTime, CasUnique64}).
  116. %%--------------------------------------------------------------------
  117. %% Function: replace
  118. %% Description: replace value
  119. %% Returns: ok, {error, not_stored} or {error, Reason}
  120. %%--------------------------------------------------------------------
  121. replace(Conn, Key, Value) when is_list(Key) ->
  122. replaceb(Conn, Key, term_to_binary(Value)).
  123. replace(Conn, Key, Value, Flags, ExpTime) when is_list(Key) andalso is_integer(ExpTime) ->
  124. replaceb(Conn, Key, term_to_binary(Value), Flags, ExpTime).
  125. %%--------------------------------------------------------------------
  126. %% Function: replaceb
  127. %% Description: replace binary value
  128. %% Returns: ok, {error, not_stored} or {error, Reason}
  129. %%--------------------------------------------------------------------
  130. replaceb(Conn, Key, Value) when is_list(Key) ->
  131. gen_server:call(Conn, {replaceb, Key, Value}).
  132. replaceb(Conn, Key, Value, Flags, ExpTime) when is_list(Key) andalso is_integer(ExpTime) ->
  133. gen_server:call(Conn, {replaceb, Key, Value, Flags, ExpTime}).
  134. %%--------------------------------------------------------------------
  135. %% Function: add
  136. %% Description: add value
  137. %% Returns: ok, {error, not_stored} or {error, Reason}
  138. %%--------------------------------------------------------------------
  139. add(Conn, Key, Value) when is_list(Key) ->
  140. addb(Conn, Key, term_to_binary(Value)).
  141. add(Conn, Key, Value, Flags, ExpTime) when is_list(Key) andalso is_integer(ExpTime) ->
  142. addb(Conn, Key, term_to_binary(Value), Flags, ExpTime).
  143. %%--------------------------------------------------------------------
  144. %% Function: addb
  145. %% Description: add binary value
  146. %% Returns: ok, {error, not_stored} or {error, Reason}
  147. %%--------------------------------------------------------------------
  148. addb(Conn, Key, Value) when is_list(Key) ->
  149. gen_server:call(Conn, {addb, Key, Value}).
  150. addb(Conn, Key, Value, Flags, ExpTime) when is_list(Key) andalso is_integer(ExpTime) ->
  151. gen_server:call(Conn, {addb, Key, Value, Flags, ExpTime}).
  152. %%--------------------------------------------------------------------
  153. %% Function: append
  154. %% Description: append value
  155. %% Returns: ok, {error, not_stored} or {error, Reason}
  156. %%--------------------------------------------------------------------
  157. append(Conn, Key, Value) when is_list(Key) ->
  158. gen_server:call(Conn, {append, Key, Value}).
  159. %%--------------------------------------------------------------------
  160. %% Function: prepend
  161. %% Description: prepend value
  162. %% Returns: ok, {error, not_stored} or {error, Reason}
  163. %%--------------------------------------------------------------------
  164. prepend(Conn, Key, Value) when is_list(Key) ->
  165. gen_server:call(Conn, {prepend, Key, Value}).
  166. %%--------------------------------------------------------------------
  167. %% Function: get
  168. %% Description: get value
  169. %% Returns: {ok, Value}, {error, not_found} or {error, Reason}
  170. %%--------------------------------------------------------------------
  171. get(Conn, Key) when is_list(Key) ->
  172. gen_server:call(Conn, {get, Key}).
  173. %%--------------------------------------------------------------------
  174. %% Function: gets
  175. %% Description: get value and cas
  176. %% Returns: {ok, Value, CasUnique64}, {error, not_found} or {error, Reason}
  177. %%--------------------------------------------------------------------
  178. gets(Conn, Key) when is_list(Key) ->
  179. gen_server:call(Conn, {gets, Key}).
  180. %%--------------------------------------------------------------------
  181. %% Function: getb
  182. %% Description: get value as binary
  183. %% Returns: {ok, Value}, {error, not_found} or {error, Reason}
  184. %%--------------------------------------------------------------------
  185. getb(Conn, Key) when is_list(Key) ->
  186. gen_server:call(Conn, {getb, Key}).
  187. %%--------------------------------------------------------------------
  188. %% Function: getsb
  189. %% Description: get value as binary and cas
  190. %% Returns: {ok, Value, CasUnique64}, {error, not_found} or {error, Reason}
  191. %%--------------------------------------------------------------------
  192. getsb(Conn, Key) when is_list(Key) ->
  193. gen_server:call(Conn, {getsb, Key}).
  194. %%--------------------------------------------------------------------
  195. %% Function: get_multi
  196. %% Description: get multiple values
  197. %% Returns: {ok, Values}, Values = list of {Key, Value}.
  198. %%--------------------------------------------------------------------
  199. get_multi(Conn, Keys) when is_list(Keys) ->
  200. gen_server:call(Conn, {get_multi, Keys}).
  201. %%--------------------------------------------------------------------
  202. %% Function: get_multib
  203. %% Description: get multiple binary values
  204. %% Returns: {ok, Values}, Values = list of {Key, Value}.
  205. %%--------------------------------------------------------------------
  206. get_multib(Conn, Keys) when is_list(Keys) ->
  207. gen_server:call(Conn, {get_multib, Keys}).
  208. %%--------------------------------------------------------------------
  209. %% Function: gets_multi
  210. %% Description: get multiple values with cas value
  211. %% Returns: {ok, Values}, Values = list of {Key, Value, CasValue}.
  212. %%--------------------------------------------------------------------
  213. gets_multi(Conn, Keys) when is_list(Keys) ->
  214. gen_server:call(Conn, {gets_multi, Keys}).
  215. %%--------------------------------------------------------------------
  216. %% Function: gets_multib
  217. %% Description: get multiple binary values with cas value
  218. %% Returns: {ok, Values}, Values = list of {Key, Value}.
  219. %%--------------------------------------------------------------------
  220. gets_multib(Conn, Keys) when is_list(Keys) ->
  221. gen_server:call(Conn, {gets_multib, Keys}).
  222. %%--------------------------------------------------------------------
  223. %% Function: delete
  224. %% Description: delete value
  225. %% Returns: ok
  226. %%--------------------------------------------------------------------
  227. delete(Conn, Key) when is_list(Key) ->
  228. gen_server:call(Conn, {delete, Key}).
  229. %%--------------------------------------------------------------------
  230. %% Function: incr
  231. %% Description: incr value
  232. %% Returns: {ok, NewValue}
  233. %%--------------------------------------------------------------------
  234. incr(Conn, Key, Value) when is_integer(Value) ->
  235. gen_server:call(Conn, {incr, Key, Value}).
  236. %%--------------------------------------------------------------------
  237. %% Function: decr
  238. %% Description: decr value
  239. %% Returns: {ok, NewValue}
  240. %%--------------------------------------------------------------------
  241. decr(Conn, Key, Value) when is_integer(Value) ->
  242. gen_server:call(Conn, {decr, Key, Value}).
  243. %%--------------------------------------------------------------------
  244. %% Function: version
  245. %% Description: Returns memcached server version.
  246. %% Returns: Version string
  247. %%--------------------------------------------------------------------
  248. version(Conn) ->
  249. gen_server:call(Conn, version).
  250. %%--------------------------------------------------------------------
  251. %% Function: stats
  252. %% Description: Returns memcached stats
  253. %% Returns: stats string
  254. %%--------------------------------------------------------------------
  255. stats(Conn) ->
  256. gen_server:call(Conn, stats, 10 * 1000).
  257. %%--------------------------------------------------------------------
  258. %% Function: quit
  259. %% Description: Send quit command to server
  260. %% Returns: quite
  261. %%--------------------------------------------------------------------
  262. quit(Conn) ->
  263. gen_server:call(Conn, quit).
  264. %%--------------------------------------------------------------------
  265. %% Function: flush_all
  266. %% Description: Send flush_all command to server
  267. %% Returns: quite
  268. %%--------------------------------------------------------------------
  269. flush_all(Conn) ->
  270. gen_server:call(Conn, flush_all).
  271. flush_all(Conn, Sec) when is_integer(Sec) ->
  272. gen_server:call(Conn, {flush_all, Sec}).
  273. %%--------------------------------------------------------------------
  274. %% Function: disconnect
  275. %% Description: disconnect
  276. %% Returns: ok
  277. %%--------------------------------------------------------------------
  278. disconnect(Conn) ->
  279. gen_server:call(Conn, disconnect),
  280. ok.
  281. init([Super, Host, Port, _Opts]) ->
  282. case gen_tcp:connect(Host, Port, ?TCP_OPTIONS) of
  283. {ok, Socket} ->
  284. erlang:monitor(process, Super),
  285. Server = Host ++ integer_to_list(Port),
  286. CHash = memcached_chash:new(memcached),
  287. ok = memcached_chash:add_node(CHash, Server, Server),
  288. Connections = ets:new(connections, [set, protected]),
  289. true = ets:insert(Connections, {Server, Socket}),
  290. {ok, {Connections, CHash, Socket}};
  291. {error, Reason} ->
  292. {stop, Reason};
  293. Other ->
  294. {stop, Other}
  295. end.
  296. call_with_socket(Fun, Key, Connections, CHash, _Socket) ->
  297. case get_socket(Key, Connections, CHash) of
  298. {ok, Socket, NewConnections} ->
  299. apply(Fun, [Socket, NewConnections]);
  300. {error, Reason} ->
  301. {reply, {error, Reason}, {Connections, CHash, _Socket}}
  302. end.
  303. %%--------------------------------------------------------------------
  304. %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
  305. %% {reply, Reply, State, Timeout} |
  306. %% {noreply, State} |
  307. %% {noreply, State, Timeout} |
  308. %% {stop, Reason, Reply, State} |
  309. %% {stop, Reason, State}
  310. %% Description: Handling call messages
  311. %%--------------------------------------------------------------------
  312. handle_call({get, Key}, _From, {Connections, CHash, _Socket}) ->
  313. call_with_socket(
  314. fun(Socket, NewConnections) ->
  315. case get_command(Socket, "get", [Key]) of
  316. {ok, []} ->
  317. {reply, {error, not_found}, {NewConnections, CHash, Socket}};
  318. {ok, [{_Key, Value, _CasUnique64}]} ->
  319. {reply, {ok, binary_to_term(Value)}, {NewConnections, CHash, Socket}};
  320. {error, Error}=Err when Error == enotconn ->
  321. {stop, normal, Err, {NewConnections, CHash, Socket}};
  322. Other ->
  323. {reply, Other, {NewConnections, CHash, Socket}}
  324. end
  325. end,
  326. Key, Connections, CHash, _Socket);
  327. handle_call({gets, Key}, _From, {Connections, CHash, _Socket}) ->
  328. call_with_socket(
  329. fun(Socket, NewConnections) ->
  330. case get_command(Socket, "gets", [Key]) of
  331. {ok, []} ->
  332. {reply, {error, not_found}, {NewConnections, CHash, Socket}};
  333. {ok, [{_Key, Value, CasUnique64}]} ->
  334. {reply, {ok, binary_to_term(Value), CasUnique64}, {NewConnections, CHash, Socket}};
  335. {error, Error}=Err when Error == enotconn ->
  336. {stop, normal, Err, {NewConnections, CHash, Socket}};
  337. Other ->
  338. {reply, Other, {NewConnections, CHash, Socket}}
  339. end
  340. end,
  341. Key, Connections, CHash, _Socket);
  342. handle_call({getb, Key}, _From, {Connections, CHash, _Socket}) ->
  343. call_with_socket(
  344. fun(Socket, NewConnections) ->
  345. case get_command(Socket, "get", [Key]) of
  346. {ok, []} ->
  347. {reply, {error, not_found}, {NewConnections, CHash, Socket}};
  348. {ok, [{_Key, Value, _CasUnique64}]} ->
  349. {reply, {ok, Value}, {NewConnections, CHash, Socket}};
  350. {error, Error}=Err when Error == enotconn ->
  351. {stop, normal, Err, {NewConnections, CHash, Socket}};
  352. Other ->
  353. {reply, Other, {NewConnections, CHash, Socket}}
  354. end
  355. end,
  356. Key, Connections, CHash, _Socket);
  357. handle_call({getsb, Key}, _From, {Connections, CHash, _Socket}) ->
  358. call_with_socket(
  359. fun(Socket, NewConnections) ->
  360. case get_command(Socket, "gets", [Key]) of
  361. {ok, []} ->
  362. {reply, {error, not_found}, {NewConnections, CHash, Socket}};
  363. {ok, [{_Key, Value, CasUnique64}]} ->
  364. {reply, {ok, Value, CasUnique64}, {NewConnections, CHash, Socket}};
  365. {error, Error}=Err when Error == enotconn ->
  366. {stop, normal, Err, {NewConnections, CHash, Socket}};
  367. Other ->
  368. {reply, Other, {NewConnections, CHash, Socket}}
  369. end
  370. end,
  371. Key, Connections, CHash, _Socket);
  372. handle_call({get_multi, Keys}, _From, {Connections, CHash, Socket}) ->
  373. case get_command(Socket, "get", Keys) of
  374. {ok, BinaryValues} ->
  375. Values = lists:map(fun({Key, Value, _CasUnique64}) -> {Key, binary_to_term(Value)} end, BinaryValues),
  376. {reply, {ok, Values}, {Connections, CHash, Socket}};
  377. {error, Error}=Err when Error == enotconn ->
  378. {stop, normal, Err, {Connections, CHash, Socket}};
  379. Other ->
  380. {reply, Other, {Connections, CHash, Socket}}
  381. end;
  382. handle_call({get_multib, Keys}, _From, {Connections, CHash, Socket}) ->
  383. case get_command(Socket, "get", Keys) of
  384. {ok, BinaryValues} ->
  385. Values = lists:map(fun({Key, BinaryValue, _CasUnique64}) -> {Key, BinaryValue} end, BinaryValues),
  386. {reply, {ok, Values}, {Connections, CHash, Socket}};
  387. {error, Error}=Err when Error == enotconn ->
  388. {stop, normal, Err, {Connections, CHash, Socket}};
  389. Other ->
  390. {reply, Other, {Connections, CHash, Socket}}
  391. end;
  392. handle_call({gets_multi, Keys}, _From, {Connections, CHash, Socket}) ->
  393. case get_command(Socket, "gets", Keys) of
  394. {ok, BinaryValues} ->
  395. Values = lists:map(fun({Key, Value, CasUnique64}) -> {Key, binary_to_term(Value), CasUnique64} end, BinaryValues),
  396. {reply, {ok, Values}, {Connections, CHash, Socket}};
  397. {error, Error}=Err when Error == enotconn ->
  398. {stop, normal, Err, {Connections, CHash, Socket}};
  399. Other ->
  400. {reply, Other, {Connections, CHash, Socket}}
  401. end;
  402. handle_call({gets_multib, Keys}, _From, {Connections, CHash, Socket}) ->
  403. case get_command(Socket, "gets", Keys) of
  404. {ok, BinaryValues} ->
  405. Values = lists:map(fun({Key, Value, CasUnique64}) -> {Key, Value, CasUnique64} end, BinaryValues),
  406. {reply, {ok, Values}, {Connections, CHash, Socket}};
  407. {error, Error}=Err when Error == enotconn ->
  408. {stop, normal, Err, {Connections, CHash, Socket}};
  409. Other ->
  410. {reply, Other, {Connections, CHash, Socket}}
  411. end;
  412. handle_call({setb, Key, Value}, _From, {Connections, CHash, Socket}) ->
  413. storage_command(Connections, CHash, Socket, "set", Key, Value, 0, 0);
  414. handle_call({setb, Key, Value, Flags, ExpTime}, _From, {Connections, CHash, Socket}) ->
  415. storage_command(Connections, CHash, Socket, "set", Key, Value, Flags, ExpTime);
  416. handle_call({casb, Key, Value, Flags, ExpTime, CasUnique64}, _From, {Connections, CHash, Socket}) ->
  417. storage_command(Connections, CHash, Socket, "set", Key, Value, Flags, ExpTime, CasUnique64);
  418. handle_call({replaceb, Key, Value}, _From, {Connections, CHash, Socket}) ->
  419. storage_command(Connections, CHash, Socket, "replace", Key, Value, 0, 0);
  420. handle_call({replaceb, Key, Value, Flags, ExpTime}, _From, {Connections, CHash, Socket}) ->
  421. storage_command(Connections, CHash, Socket, "replace", Key, Value, Flags, ExpTime);
  422. handle_call({addb, Key, Value}, _From, {Connections, CHash, Socket}) ->
  423. storage_command(Connections, CHash, Socket, "add", Key, Value, 0, 0);
  424. handle_call({addb, Key, Value, Flags, ExpTime}, _From, {Connections, CHash, Socket}) ->
  425. storage_command(Connections, CHash, Socket, "add", Key, Value, Flags, ExpTime);
  426. handle_call({append, Key, Value}, _From, {Connections, CHash, Socket}) ->
  427. storage_command(Connections, CHash, Socket, "append", Key, term_to_binary(Value), 0, 0);
  428. handle_call({prepend, Key, Value}, _From, {Connections, CHash, Socket}) ->
  429. storage_command(Connections, CHash, Socket, "prepend", Key, term_to_binary(Value), 0, 0);
  430. handle_call({delete, Key}, _From, {Connections, CHash, Socket}) ->
  431. {reply, delete_command(Socket, Key), {Connections, CHash, Socket}};
  432. handle_call({incr, Key, Value}, _From, {Connections, CHash, Socket}) ->
  433. {reply, incr_decr_command(Socket, "incr", Key, Value), {Connections, CHash, Socket}};
  434. handle_call({decr, Key, Value}, _From, {Connections, CHash, Socket}) ->
  435. {reply, incr_decr_command(Socket, "decr", Key, Value), {Connections, CHash, Socket}};
  436. handle_call(disconnect, _From, {Connections, CHash, Socket}) ->
  437. {stop, normal, ok, {Connections, CHash, Socket}};
  438. handle_call(version, _From, {Connections, CHash, Socket}) ->
  439. gen_tcp:send(Socket, <<"version\r\n">>),
  440. case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
  441. {ok, Packet} ->
  442. case split(binary_to_list(Packet)) of
  443. {Data, []} ->
  444. case Data of
  445. [$V | [$E | [$R | [$S | [$I | [$O | [$N | [32 | Version]]]]]]]] ->
  446. {reply, Version, {Connections, CHash, Socket}};
  447. _ ->
  448. {reply, {error, invalid_reseponse}, {Connections, CHash, Socket}}
  449. end;
  450. Other ->
  451. {reply, Other, {Connections, CHash, Socket}}
  452. end;
  453. {error, Reason} ->
  454. {reply, {error, Reason}, {Connections, CHash, Socket}}
  455. end;
  456. handle_call(stats, _From, {Connections, CHash, Socket}) ->
  457. gen_tcp:send(Socket, <<"stats\r\n">>),
  458. case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
  459. {ok, Packet} ->
  460. Stats = parse_stats(binary_to_list(Packet), []),
  461. {reply, Stats, {Connections, CHash, Socket}};
  462. {error, Reason} ->
  463. {reply, {error, Reason}, {Connections, CHash, Socket}}
  464. end;
  465. handle_call(quit, _From, {Connections, CHash, _Socket}) ->
  466. lists:foreach(fun(Socket) -> gen_tcp:send(Socket, <<"quit\r\n">>) end, all_sockets(Connections)),
  467. {reply, ok, {Connections, CHash, _Socket}};
  468. %% case get_socket(Key, Connections, CHash) of
  469. %% {ok, Socket, NewConnections} ->
  470. %% gen_tcp:send(Socket, <<"quit\r\n">>),
  471. %% {reply, ok, {NewConnections, CHash, Socket}};
  472. %% {error, Reason} ->
  473. %% {reply, {error, Reason}, {NewConnections, CHash, Socket}}
  474. %% end;
  475. handle_call(flush_all, _From, {Connections, CHash, Socket}) ->
  476. gen_tcp:send(Socket, <<"flush_all\r\n">>),
  477. case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
  478. {ok, Packet} ->
  479. case Packet of
  480. <<"OK\r\n">> ->
  481. {reply, ok, {Connections, CHash, Socket}};
  482. Other ->
  483. {reply, {error, Other}, {Connections, CHash, Socket}}
  484. end;
  485. Other ->
  486. {error, Other}
  487. end;
  488. handle_call({flush_all, Sec}, _From, {Connections, CHash, Socket}) ->
  489. Command = iolist_to_binary([<<"flush_all ">>, integer_to_list(Sec), <<"\r\n">>]),
  490. gen_tcp:send(Socket, Command),
  491. case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
  492. {ok, Packet} ->
  493. case Packet of
  494. <<"OK\r\n">> ->
  495. {reply, ok, {Connections, CHash, Socket}};
  496. Other ->
  497. {reply, {error, Other}, {Connections, CHash, Socket}}
  498. end;
  499. Other ->
  500. {error, Other}
  501. end.
  502. %%--------------------------------------------------------------------
  503. %% Function: handle_cast(Msg, State) -> {noreply, State} |
  504. %% {noreply, State, Timeout} |
  505. %% {stop, Reason, State}
  506. %% Description: Handling cast messages
  507. %%--------------------------------------------------------------------
  508. handle_cast(Msg, State) ->
  509. io:format("cast=~p~n", [Msg]),
  510. {noreply, State}.
  511. %%--------------------------------------------------------------------
  512. %% Function: handle_info(Info, State) -> {noreply, State} |
  513. %% {noreply, State, Timeout} |
  514. %% {stop, Reason, State}
  515. %% Description: Handling all non call/cast messages
  516. %%--------------------------------------------------------------------
  517. handle_info(Info, State) ->
  518. io:format("info=~p~n", [Info]),
  519. {noreply, State}.
  520. %%--------------------------------------------------------------------
  521. %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
  522. %% Description: Convert process state when code is changed
  523. %%--------------------------------------------------------------------
  524. code_change(_OldVsn, State, _Extra) ->
  525. {ok, State}.
  526. %%--------------------------------------------------------------------
  527. %% Function: terminate(Reason, State) -> void()
  528. %% Description: This function is called by a gen_server when it is about to
  529. %% terminate. It should be the opposite of Module:init/1 and do any necessary
  530. %% cleaning up. When it returns, the gen_server terminates with Reason.
  531. %% The return value is ignored.
  532. %%--------------------------------------------------------------------
  533. terminate(_Reason, {Connections, _CHash, _Socket}) ->
  534. lists:foreach(fun(Socket) -> gen_tcp:close(Socket) end, all_sockets(Connections)).
  535. %%====================================================================
  536. %% Internal functions
  537. %%====================================================================
  538. parse_single_value(Data) ->
  539. case io_lib:fread("VALUE ~s ~u ~u", Data) of
  540. {ok, [Key, _Flags, Bytes], []} ->
  541. {Key, Bytes, []};
  542. _ ->
  543. case io_lib:fread("VALUE ~s ~u ~u ~u", Data) of
  544. {ok, [Key, _Flags, Bytes, CasUnique64], []} ->
  545. {Key, Bytes, CasUnique64};
  546. Other ->
  547. {error, Other}
  548. end
  549. end.
  550. parse_values(Data, Socket) ->
  551. parse_values(Data, [], Socket).
  552. parse_values(Data, Values, Socket) ->
  553. %% Format: VALUE <key> <flags> <bytes> [<cas unique>]\r\n
  554. case split(Data) of
  555. {error, Reason} ->
  556. {error, Reason};
  557. {"END", []} ->
  558. {ok, lists:reverse(Values)};
  559. {"ERROR", []} ->
  560. {error, unknown_command};
  561. {Head, Tail} ->
  562. case parse_single_value(Head) of
  563. {Key, Bytes, CasUnique64} ->
  564. case Bytes > length(Tail) of
  565. true ->
  566. Offset = Bytes - length(Tail),
  567. case get_remaining(Data, Offset, Socket) of
  568. {ok, NewData} ->
  569. parse_values(NewData, Values, Socket);
  570. {error, Reason} ->
  571. {error, Reason}
  572. end;
  573. false ->
  574. {ValueList, Rest} = lists:split(Bytes, Tail),
  575. Value = list_to_binary(ValueList),
  576. case Rest of
  577. [] -> {ok, lists:reverse([{Key, Value, CasUnique64} | Values])};
  578. [?CR| [?LF | R]] ->
  579. parse_values(R, [{Key, Value, CasUnique64} | Values], Socket)
  580. end
  581. end;
  582. Other ->
  583. Other
  584. end
  585. end.
  586. get_remaining(Data, Offset, Socket) ->
  587. case gen_tcp:recv(Socket, Offset, ?TIMEOUT) of
  588. {ok, Packet0} ->
  589. case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
  590. {ok, Packet1} -> Packets = binary_to_list(iolist_to_binary([Packet0, Packet1])),
  591. {ok, Data ++ Packets};
  592. {error, Reason} -> {error, Reason}
  593. end;
  594. {error, Reason} -> {error, Reason}
  595. end.
  596. parse_stats(Data, Stats) ->
  597. case Data of
  598. "END\r\n" ->
  599. {ok, lists:reverse(Stats)};
  600. _ ->
  601. %% Format: VALUE <key> <flags> <bytes> [<cas unique>]\r\n
  602. case split(Data) of
  603. {error, Reason} ->
  604. {error, Reason};
  605. {Head, Tail} ->
  606. Parsed = io_lib:fread("STAT ~s ", Head),
  607. {ok, [Key], Rest} = Parsed,
  608. parse_stats(Tail, [{Key, Rest} | Stats])
  609. end
  610. end.
  611. storage_command(Connections, CHash, Socket, Command, Key, Value, Flags, ExpTime) when is_integer(Flags) andalso is_integer(ExpTime) ->
  612. EmptyCasUnique64 = <<>>,
  613. storage_command(Connections, CHash, Socket, Command, Key, Value, Flags, ExpTime, EmptyCasUnique64).
  614. storage_command(Connections, CHash, _Socket, Command, Key, Value, Flags, ExpTime, CasUnique64) when is_integer(Flags) andalso is_integer(ExpTime) ->
  615. call_with_socket(
  616. fun(Socket, NewConnections) ->
  617. ValueAsBinary = Value,
  618. Bytes = integer_to_list(size(ValueAsBinary)),
  619. CommandAsBinary = iolist_to_binary([Command, <<" ">>, Key, <<" ">>, integer_to_list(Flags), <<" ">>, integer_to_list(ExpTime), <<" ">>, Bytes, <<" ">>, CasUnique64]),
  620. gen_tcp:send(Socket, <<CommandAsBinary/binary, "\r\n">>),
  621. gen_tcp:send(Socket, <<ValueAsBinary/binary, "\r\n">>),
  622. {reply,
  623. case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
  624. {ok, Packet} ->
  625. case string:tokens(binary_to_list(Packet), "\r\n") of
  626. ["STORED"] ->
  627. ok;
  628. ["NOT_STORED"] ->
  629. {error, not_stored};
  630. ["ERROR"] ->
  631. {error, unknown_command};
  632. %% memcached returns this for append command.
  633. ["ERROR", "ERROR"] ->
  634. {error, unknown_command};
  635. Other ->
  636. io:format("Other=~p~n", [Other]),
  637. {error, Other}
  638. end;
  639. {error, Reason} ->
  640. {error, Reason}
  641. end,
  642. {NewConnections, CHash, _Socket}}
  643. end,
  644. Key, Connections, CHash, _Socket).
  645. %% memcached 1.4.0 or higher doesn't support time argument.
  646. delete_command(Socket, Key) ->
  647. Command = iolist_to_binary([<<"delete ">>, Key]),
  648. gen_tcp:send(Socket, <<Command/binary, "\r\n">>),
  649. case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
  650. {ok, <<"DELETED\r\n">>} ->
  651. ok;
  652. {ok, <<"NOT_FOUND\r\n">>} ->
  653. {error, not_found};
  654. {ok, Other} ->
  655. {error, binary_to_list(Other)};
  656. {error, Reason} ->
  657. {error, Reason}
  658. end.
  659. get_command(Socket, GetCommand, Keys) ->
  660. Command = iolist_to_binary([GetCommand, <<" ">>, string_join(" ", Keys)]),
  661. gen_tcp:send(Socket, <<Command/binary, "\r\n">>),
  662. case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
  663. {ok, Packet} ->
  664. parse_values(binary_to_list(Packet), Socket);
  665. Other ->
  666. Other
  667. end.
  668. incr_decr_command(Socket, IncrDecr, Key, Value) ->
  669. Command = iolist_to_binary([IncrDecr, " ", Key, " ", list_to_binary(integer_to_list(Value)), " "]),
  670. gen_tcp:send(Socket, <<Command/binary, "\r\n">>),
  671. case gen_tcp:recv(Socket, 0, ?TIMEOUT) of
  672. {ok, Packet} ->
  673. case split(binary_to_list(Packet)) of
  674. {error, Reason} ->
  675. {error, Reason};
  676. {"NOT_FOUND", []} ->
  677. {error, not_found};
  678. {NewValueString, []} ->
  679. case io_lib:fread("~u", NewValueString) of
  680. {ok, [NewValue], []} ->
  681. {ok, NewValue};
  682. Other ->
  683. {error, Other}
  684. end;
  685. Other ->
  686. {error, Other}
  687. end
  688. end.
  689. get_socket(Key, Connections, CHash) ->
  690. Server = memcached_chash:get_node(CHash, Key),
  691. case ets:lookup(Connections, Server) of
  692. [{Server, {Host, Port}}] ->
  693. case gen_tcp:connect(Host, Port, ?TCP_OPTIONS) of
  694. {ok, Socket} ->
  695. Server = Host ++ integer_to_list(Port),
  696. true = ets:insert(Connections, {Server, Socket}),
  697. {ok, Socket, Connections};
  698. {error, Reason} ->
  699. {error, Reason};
  700. Other ->
  701. {error, Other}
  702. end;
  703. [{Server, Socket}] ->
  704. {ok, Socket, Connections}
  705. end.
  706. all_sockets(Connections) ->
  707. ets:foldr(fun(X, Accum) ->
  708. case X of
  709. {_Server, Socket} when is_port(Socket) -> [Socket | Accum];
  710. {_Host, _Port} ->
  711. Accum
  712. end
  713. end,
  714. [],
  715. Connections).
  716. %% Borrowed from http://www.trapexit.org/String_join_with
  717. string_join(Join, L) ->
  718. string_join(Join, L, fun(E) -> E end).
  719. string_join(_Join, L=[], _Conv) ->
  720. L;
  721. string_join(Join, [H|Q], Conv) ->
  722. lists:flatten(lists:concat(
  723. [Conv(H)|lists:map(fun(E) -> [Join, Conv(E)] end, Q)]
  724. )).
  725. split(Head, S) ->
  726. case S of
  727. [?CR | [?LF | More]] ->
  728. {lists:reverse(Head), More};
  729. [] ->
  730. {error, not_found};
  731. [H | T] ->
  732. split([H | Head], T)
  733. end.
  734. %% split string with "\r\n"
  735. split(S) ->
  736. split([], S).