From 9e739f3f8ac448a06ddff72d5c83f93c8dec2f1f Mon Sep 17 00:00:00 2001 From: Joe Williams Date: Fri, 19 Mar 2010 12:00:13 -0700 Subject: [PATCH 01/65] added another merle project to the readme. --- README | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README b/README index 7da6990..b7c7a6d 100644 --- a/README +++ b/README @@ -21,9 +21,10 @@ Notes: Merle Based Projects: -http://github.com/cstar/merle/tree/master -http://github.com/issuu/merle/tree/master -http://github.com/0lvin/merle/tree/master +http://github.com/cstar/merle +http://github.com/issuu/merle +http://github.com/0lvin/merle +http://github.com/ppolv/merle Usage: From 8742789c2ac89ac2937b82b956f01a5b33b07183 Mon Sep 17 00:00:00 2001 From: joewilliams Date: Wed, 5 May 2010 07:21:09 -0700 Subject: [PATCH 02/65] gen_server2 upgrade --- src/gen_server2.erl | 439 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 361 insertions(+), 78 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index 11bb66d..94a23fb 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -1,4 +1,4 @@ -%% This file is a copy of gen_server.erl from the R11B-5 Erlang/OTP +%% This file is a copy of gen_server.erl from the R13B-1 Erlang/OTP %% distribution, with the following modifications: %% %% 1) the module name is gen_server2 @@ -16,7 +16,48 @@ %% The original code could reorder messages when communicating with a %% process on a remote node that was not currently connected. %% -%% All modifications are (C) 2009 LShift Ltd. +%% 4) The new functions gen_server2:pcall/3, pcall/4, and pcast/3 +%% allow callers to attach priorities to requests. Requests with +%% higher priorities are processed before requests with lower +%% priorities. The default priority is 0. +%% +%% 5) The callback module can optionally implement +%% handle_pre_hibernate/1 and handle_post_hibernate/1. These will be +%% called immediately prior to and post hibernation, respectively. If +%% handle_pre_hibernate returns {hibernate, NewState} then the process +%% will hibernate. If the module does not implement +%% handle_pre_hibernate/1 then the default action is to hibernate. +%% +%% 6) init can return a 4th arg, {backoff, InitialTimeout, +%% MinimumTimeout, DesiredHibernatePeriod} (all in +%% milliseconds). Then, on all callbacks which can return a timeout +%% (including init), timeout can be 'hibernate'. When this is the +%% case, the current timeout value will be used (initially, the +%% InitialTimeout supplied from init). After this timeout has +%% occurred, hibernation will occur as normal. Upon awaking, a new +%% current timeout value will be calculated. +%% +%% The purpose is that the gen_server2 takes care of adjusting the +%% current timeout value such that the process will increase the +%% timeout value repeatedly if it is unable to sleep for the +%% DesiredHibernatePeriod. If it is able to sleep for the +%% DesiredHibernatePeriod it will decrease the current timeout down to +%% the MinimumTimeout, so that the process is put to sleep sooner (and +%% hopefully stays asleep for longer). In short, should a process +%% using this receive a burst of messages, it should not hibernate +%% between those messages, but as the messages become less frequent, +%% the process will not only hibernate, it will do so sooner after +%% each message. +%% +%% When using this backoff mechanism, normal timeout values (i.e. not +%% 'hibernate') can still be used, and if they are used then the +%% handle_info(timeout, State) will be called as normal. In this case, +%% returning 'hibernate' from handle_info(timeout, State) will not +%% hibernate the process immediately, as it would if backoff wasn't +%% being used. Instead it'll wait for the current timeout as described +%% above. + +%% All modifications are (C) 2009-2010 LShift Ltd. %% ``The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -50,6 +91,7 @@ %%% init(Args) %%% ==> {ok, State} %%% {ok, State, Timeout} +%%% {ok, State, Timeout, Backoff} %%% ignore %%% {stop, Reason} %%% @@ -81,6 +123,17 @@ %%% %%% ==> ok %%% +%%% handle_pre_hibernate(State) +%%% +%%% ==> {hibernate, State} +%%% {stop, Reason, State} +%%% Reason = normal | shutdown | Term, terminate(State) is called +%%% +%%% handle_post_hibernate(State) +%%% +%%% ==> {noreply, State} +%%% {stop, Reason, State} +%%% Reason = normal | shutdown | Term, terminate(State) is called %%% %%% The work flow (of the server) can be described as follows: %%% @@ -107,11 +160,11 @@ %% API -export([start/3, start/4, start_link/3, start_link/4, - call/2, call/3, - cast/2, reply/2, + call/2, call/3, pcall/3, pcall/4, + cast/2, pcast/3, reply/2, abcast/2, abcast/3, multi_call/2, multi_call/3, multi_call/4, - enter_loop/3, enter_loop/4, enter_loop/5]). + enter_loop/3, enter_loop/4, enter_loop/5, wake_hib/7]). -export([behaviour_info/1]). @@ -126,6 +179,20 @@ -import(error_logger, [format/2]). +%%%========================================================================= +%%% Specs. These exist only to shut up dialyzer's warnings +%%%========================================================================= + +-ifdef(use_specs). + +-spec(handle_common_termination/6 :: + (any(), any(), any(), atom(), any(), any()) -> no_return()). + +-spec(hibernate/7 :: + (pid(), any(), any(), atom(), any(), queue(), any()) -> no_return()). + +-endif. + %%%========================================================================= %%% API %%%========================================================================= @@ -188,6 +255,22 @@ call(Name, Request, Timeout) -> exit({Reason, {?MODULE, call, [Name, Request, Timeout]}}) end. +pcall(Name, Priority, Request) -> + case catch gen:call(Name, '$gen_pcall', {Priority, Request}) of + {ok,Res} -> + Res; + {'EXIT',Reason} -> + exit({Reason, {?MODULE, pcall, [Name, Priority, Request]}}) + end. + +pcall(Name, Priority, Request, Timeout) -> + case catch gen:call(Name, '$gen_pcall', {Priority, Request}, Timeout) of + {ok,Res} -> + Res; + {'EXIT',Reason} -> + exit({Reason, {?MODULE, pcall, [Name, Priority, Request, Timeout]}}) + end. + %% ----------------------------------------------------------------- %% Make a cast to a generic server. %% ----------------------------------------------------------------- @@ -207,6 +290,22 @@ do_cast(Dest, Request) -> cast_msg(Request) -> {'$gen_cast',Request}. +pcast({global,Name}, Priority, Request) -> + catch global:send(Name, cast_msg(Priority, Request)), + ok; +pcast({Name,Node}=Dest, Priority, Request) when is_atom(Name), is_atom(Node) -> + do_cast(Dest, Priority, Request); +pcast(Dest, Priority, Request) when is_atom(Dest) -> + do_cast(Dest, Priority, Request); +pcast(Dest, Priority, Request) when is_pid(Dest) -> + do_cast(Dest, Priority, Request). + +do_cast(Dest, Priority, Request) -> + do_send(Dest, cast_msg(Priority, Request)), + ok. + +cast_msg(Priority, Request) -> {'$gen_pcast', {Priority, Request}}. + %% ----------------------------------------------------------------- %% Send a reply to the client. %% ----------------------------------------------------------------- @@ -253,7 +352,7 @@ multi_call(Nodes, Name, Req, Timeout) %%----------------------------------------------------------------- -%% enter_loop(Mod, Options, State, , ) ->_ +%% enter_loop(Mod, Options, State, , , ) ->_ %% %% Description: Makes an existing process into a gen_server. %% The calling process will enter the gen_server receive @@ -264,20 +363,30 @@ multi_call(Nodes, Name, Req, Timeout) %% process, including registering a name for it. %%----------------------------------------------------------------- enter_loop(Mod, Options, State) -> - enter_loop(Mod, Options, State, self(), infinity). + enter_loop(Mod, Options, State, self(), infinity, undefined). + +enter_loop(Mod, Options, State, Backoff = {backoff, _, _ , _}) -> + enter_loop(Mod, Options, State, self(), infinity, Backoff); enter_loop(Mod, Options, State, ServerName = {_, _}) -> - enter_loop(Mod, Options, State, ServerName, infinity); + enter_loop(Mod, Options, State, ServerName, infinity, undefined); enter_loop(Mod, Options, State, Timeout) -> - enter_loop(Mod, Options, State, self(), Timeout). + enter_loop(Mod, Options, State, self(), Timeout, undefined). + +enter_loop(Mod, Options, State, ServerName, Backoff = {backoff, _, _, _}) -> + enter_loop(Mod, Options, State, ServerName, infinity, Backoff); enter_loop(Mod, Options, State, ServerName, Timeout) -> + enter_loop(Mod, Options, State, ServerName, Timeout, undefined). + +enter_loop(Mod, Options, State, ServerName, Timeout, Backoff) -> Name = get_proc_name(ServerName), Parent = get_parent(), Debug = debug_options(Name, Options), - Queue = queue:new(), - loop(Parent, Name, State, Mod, Timeout, Queue, Debug). + Queue = priority_queue:new(), + Backoff1 = extend_backoff(Backoff), + loop(Parent, Name, State, Mod, Timeout, Backoff1, Queue, Debug). %%%======================================================================== %%% Gen-callback functions @@ -292,23 +401,37 @@ enter_loop(Mod, Options, State, ServerName, Timeout) -> %%% --------------------------------------------------- init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); -init_it(Starter, Parent, Name, Mod, Args, Options) -> +init_it(Starter, Parent, Name0, Mod, Args, Options) -> + Name = name(Name0), Debug = debug_options(Name, Options), - Queue = queue:new(), + Queue = priority_queue:new(), case catch Mod:init(Args) of {ok, State} -> proc_lib:init_ack(Starter, {ok, self()}), - loop(Parent, Name, State, Mod, infinity, Queue, Debug); + loop(Parent, Name, State, Mod, infinity, undefined, Queue, Debug); {ok, State, Timeout} -> - proc_lib:init_ack(Starter, {ok, self()}), - loop(Parent, Name, State, Mod, Timeout, Queue, Debug); + proc_lib:init_ack(Starter, {ok, self()}), + loop(Parent, Name, State, Mod, Timeout, undefined, Queue, Debug); + {ok, State, Timeout, Backoff = {backoff, _, _, _}} -> + Backoff1 = extend_backoff(Backoff), + proc_lib:init_ack(Starter, {ok, self()}), + loop(Parent, Name, State, Mod, Timeout, Backoff1, Queue, Debug); {stop, Reason} -> + %% For consistency, we must make sure that the + %% registered name (if any) is unregistered before + %% the parent process is notified about the failure. + %% (Otherwise, the parent process could get + %% an 'already_started' error if it immediately + %% tried starting the process again.) + unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); ignore -> + unregister_name(Name0), proc_lib:init_ack(Starter, ignore), exit(normal); {'EXIT', Reason} -> + unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); Else -> @@ -317,46 +440,188 @@ init_it(Starter, Parent, Name, Mod, Args, Options) -> exit(Error) end. +name({local,Name}) -> Name; +name({global,Name}) -> Name; +%% name(Pid) when is_pid(Pid) -> Pid; +%% when R11 goes away, drop the line beneath and uncomment the line above +name(Name) -> Name. + +unregister_name({local,Name}) -> + _ = (catch unregister(Name)); +unregister_name({global,Name}) -> + _ = global:unregister_name(Name); +unregister_name(Pid) when is_pid(Pid) -> + Pid; +% Under R12 let's just ignore it, as we have a single term as Name. +% On R13 it will never get here, as we get tuple with 'local/global' atom. +unregister_name(_Name) -> ok. + +extend_backoff(undefined) -> + undefined; +extend_backoff({backoff, InitialTimeout, MinimumTimeout, DesiredHibPeriod}) -> + {backoff, InitialTimeout, MinimumTimeout, DesiredHibPeriod, now()}. + %%%======================================================================== %%% Internal functions %%%======================================================================== %%% --------------------------------------------------- %%% The MAIN loop. %%% --------------------------------------------------- -loop(Parent, Name, State, Mod, Time, Queue, Debug) -> +loop(Parent, Name, State, Mod, hibernate, undefined, Queue, Debug) -> + pre_hibernate(Parent, Name, State, Mod, undefined, Queue, Debug); +loop(Parent, Name, State, Mod, Time, TimeoutState, Queue, Debug) -> + process_next_msg(Parent, Name, State, Mod, Time, TimeoutState, + drain(Queue), Debug). + +drain(Queue) -> receive - Input -> loop(Parent, Name, State, Mod, - Time, queue:in(Input, Queue), Debug) - after 0 -> - case queue:out(Queue) of - {{value, Msg}, Queue1} -> - process_msg(Parent, Name, State, Mod, - Time, Queue1, Debug, Msg); - {empty, Queue1} -> - receive - Input -> - loop(Parent, Name, State, Mod, - Time, queue:in(Input, Queue1), Debug) - after Time -> - process_msg(Parent, Name, State, Mod, - Time, Queue1, Debug, timeout) + Input -> drain(in(Input, Queue)) + after 0 -> Queue + end. + +process_next_msg(Parent, Name, State, Mod, Time, TimeoutState, Queue, Debug) -> + case priority_queue:out(Queue) of + {{value, Msg}, Queue1} -> + process_msg(Parent, Name, State, Mod, + Time, TimeoutState, Queue1, Debug, Msg); + {empty, Queue1} -> + {Time1, HibOnTimeout} + = case {Time, TimeoutState} of + {hibernate, {backoff, Current, _Min, _Desired, _RSt}} -> + {Current, true}; + {hibernate, _} -> + %% wake_hib/7 will set Time to hibernate. If + %% we were woken and didn't receive a msg + %% then we will get here and need a sensible + %% value for Time1, otherwise we crash. + %% R13B1 always waits infinitely when waking + %% from hibernation, so that's what we do + %% here too. + {infinity, false}; + _ -> {Time, false} + end, + receive + Input -> + %% Time could be 'hibernate' here, so *don't* call loop + process_next_msg( + Parent, Name, State, Mod, Time, TimeoutState, + drain(in(Input, Queue1)), Debug) + after Time1 -> + case HibOnTimeout of + true -> + pre_hibernate( + Parent, Name, State, Mod, TimeoutState, Queue1, + Debug); + false -> + process_msg( + Parent, Name, State, Mod, Time, TimeoutState, + Queue1, Debug, timeout) end end end. - -process_msg(Parent, Name, State, Mod, Time, Queue, Debug, Msg) -> + +wake_hib(Parent, Name, State, Mod, TS, Queue, Debug) -> + TimeoutState1 = case TS of + undefined -> + undefined; + {SleptAt, TimeoutState} -> + adjust_timeout_state(SleptAt, now(), TimeoutState) + end, + post_hibernate(Parent, Name, State, Mod, TimeoutState1, + drain(Queue), Debug). + +hibernate(Parent, Name, State, Mod, TimeoutState, Queue, Debug) -> + TS = case TimeoutState of + undefined -> undefined; + {backoff, _, _, _, _} -> {now(), TimeoutState} + end, + proc_lib:hibernate(?MODULE, wake_hib, [Parent, Name, State, Mod, + TS, Queue, Debug]). + +pre_hibernate(Parent, Name, State, Mod, TimeoutState, Queue, Debug) -> + case erlang:function_exported(Mod, handle_pre_hibernate, 1) of + true -> + case catch Mod:handle_pre_hibernate(State) of + {hibernate, NState} -> + hibernate(Parent, Name, NState, Mod, TimeoutState, Queue, + Debug); + Reply -> + handle_common_termination(Reply, Name, pre_hibernate, + Mod, State, Debug) + end; + false -> + hibernate(Parent, Name, State, Mod, TimeoutState, Queue, Debug) + end. + +post_hibernate(Parent, Name, State, Mod, TimeoutState, Queue, Debug) -> + case erlang:function_exported(Mod, handle_post_hibernate, 1) of + true -> + case catch Mod:handle_post_hibernate(State) of + {noreply, NState} -> + process_next_msg(Parent, Name, NState, Mod, infinity, + TimeoutState, Queue, Debug); + {noreply, NState, Time} -> + process_next_msg(Parent, Name, NState, Mod, Time, + TimeoutState, Queue, Debug); + Reply -> + handle_common_termination(Reply, Name, post_hibernate, + Mod, State, Debug) + end; + false -> + %% use hibernate here, not infinity. This matches + %% R13B. The key is that we should be able to get through + %% to process_msg calling sys:handle_system_msg with Time + %% still set to hibernate, iff that msg is the very msg + %% that woke us up (or the first msg we receive after + %% waking up). + process_next_msg(Parent, Name, State, Mod, hibernate, + TimeoutState, Queue, Debug) + end. + +adjust_timeout_state(SleptAt, AwokeAt, {backoff, CurrentTO, MinimumTO, + DesiredHibPeriod, RandomState}) -> + NapLengthMicros = timer:now_diff(AwokeAt, SleptAt), + CurrentMicros = CurrentTO * 1000, + MinimumMicros = MinimumTO * 1000, + DesiredHibMicros = DesiredHibPeriod * 1000, + GapBetweenMessagesMicros = NapLengthMicros + CurrentMicros, + Base = + %% If enough time has passed between the last two messages then we + %% should consider sleeping sooner. Otherwise stay awake longer. + case GapBetweenMessagesMicros > (MinimumMicros + DesiredHibMicros) of + true -> lists:max([MinimumTO, CurrentTO div 2]); + false -> CurrentTO + end, + {Extra, RandomState1} = random:uniform_s(Base, RandomState), + CurrentTO1 = Base + Extra, + {backoff, CurrentTO1, MinimumTO, DesiredHibPeriod, RandomState1}. + +in({'$gen_pcast', {Priority, Msg}}, Queue) -> + priority_queue:in({'$gen_cast', Msg}, Priority, Queue); +in({'$gen_pcall', From, {Priority, Msg}}, Queue) -> + priority_queue:in({'$gen_call', From, Msg}, Priority, Queue); +in(Input, Queue) -> + priority_queue:in(Input, Queue). + +process_msg(Parent, Name, State, Mod, Time, TimeoutState, Queue, + Debug, Msg) -> case Msg of {system, From, Req} -> - sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, - [Name, State, Mod, Time, Queue]); + sys:handle_system_msg( + Req, From, Parent, ?MODULE, Debug, + [Name, State, Mod, Time, TimeoutState, Queue]); + %% gen_server puts Hib on the end as the 7th arg, but that + %% version of the function seems not to be documented so + %% leaving out for now. {'EXIT', Parent, Reason} -> terminate(Reason, Name, Msg, Mod, State, Debug); _Msg when Debug =:= [] -> - handle_msg(Msg, Parent, Name, State, Mod, Time, Queue); + handle_msg(Msg, Parent, Name, State, Mod, TimeoutState, Queue); _Msg -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {in, Msg}), - handle_msg(Msg, Parent, Name, State, Mod, Time, Queue, Debug1) + handle_msg(Msg, Parent, Name, State, Mod, TimeoutState, Queue, + Debug1) end. %%% --------------------------------------------------- @@ -554,87 +819,95 @@ dispatch(Info, Mod, State) -> Mod:handle_info(Info, State). handle_msg({'$gen_call', From, Msg}, - Parent, Name, State, Mod, _Time, Queue) -> + Parent, Name, State, Mod, TimeoutState, Queue) -> case catch Mod:handle_call(Msg, From, State) of {reply, Reply, NState} -> reply(From, Reply), - loop(Parent, Name, NState, Mod, infinity, Queue, []); + loop(Parent, Name, NState, Mod, infinity, TimeoutState, Queue, []); {reply, Reply, NState, Time1} -> reply(From, Reply), - loop(Parent, Name, NState, Mod, Time1, Queue, []); + loop(Parent, Name, NState, Mod, Time1, TimeoutState, Queue, []); {noreply, NState} -> - loop(Parent, Name, NState, Mod, infinity, Queue, []); + loop(Parent, Name, NState, Mod, infinity, TimeoutState, Queue, []); {noreply, NState, Time1} -> - loop(Parent, Name, NState, Mod, Time1, Queue, []); + loop(Parent, Name, NState, Mod, Time1, TimeoutState, Queue, []); {stop, Reason, Reply, NState} -> {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, NState, [])), reply(From, Reply), exit(R); - Other -> handle_common_reply(Other, - Parent, Name, Msg, Mod, State, Queue) + Other -> handle_common_reply(Other, Parent, Name, Msg, Mod, State, + TimeoutState, Queue) end; handle_msg(Msg, - Parent, Name, State, Mod, _Time, Queue) -> + Parent, Name, State, Mod, TimeoutState, Queue) -> Reply = (catch dispatch(Msg, Mod, State)), - handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Queue). + handle_common_reply(Reply, Parent, Name, Msg, Mod, State, + TimeoutState, Queue). handle_msg({'$gen_call', From, Msg}, - Parent, Name, State, Mod, _Time, Queue, Debug) -> + Parent, Name, State, Mod, TimeoutState, Queue, Debug) -> case catch Mod:handle_call(Msg, From, State) of {reply, Reply, NState} -> Debug1 = reply(Name, From, Reply, NState, Debug), - loop(Parent, Name, NState, Mod, infinity, Queue, Debug1); + loop(Parent, Name, NState, Mod, infinity, TimeoutState, Queue, + Debug1); {reply, Reply, NState, Time1} -> Debug1 = reply(Name, From, Reply, NState, Debug), - loop(Parent, Name, NState, Mod, Time1, Queue, Debug1); + loop(Parent, Name, NState, Mod, Time1, TimeoutState, Queue, Debug1); {noreply, NState} -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {noreply, NState}), - loop(Parent, Name, NState, Mod, infinity, Queue, Debug1); + loop(Parent, Name, NState, Mod, infinity, TimeoutState, Queue, + Debug1); {noreply, NState, Time1} -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {noreply, NState}), - loop(Parent, Name, NState, Mod, Time1, Queue, Debug1); + loop(Parent, Name, NState, Mod, Time1, TimeoutState, Queue, Debug1); {stop, Reason, Reply, NState} -> {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, NState, Debug)), reply(Name, From, Reply, NState, Debug), exit(R); Other -> - handle_common_reply(Other, - Parent, Name, Msg, Mod, State, Queue, Debug) + handle_common_reply(Other, Parent, Name, Msg, Mod, State, + TimeoutState, Queue, Debug) end; handle_msg(Msg, - Parent, Name, State, Mod, _Time, Queue, Debug) -> + Parent, Name, State, Mod, TimeoutState, Queue, Debug) -> Reply = (catch dispatch(Msg, Mod, State)), - handle_common_reply(Reply, - Parent, Name, Msg, Mod, State, Queue, Debug). + handle_common_reply(Reply, Parent, Name, Msg, Mod, State, + TimeoutState, Queue, Debug). -handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Queue) -> +handle_common_reply(Reply, Parent, Name, Msg, Mod, State, + TimeoutState, Queue) -> case Reply of {noreply, NState} -> - loop(Parent, Name, NState, Mod, infinity, Queue, []); + loop(Parent, Name, NState, Mod, infinity, TimeoutState, Queue, []); {noreply, NState, Time1} -> - loop(Parent, Name, NState, Mod, Time1, Queue, []); - {stop, Reason, NState} -> - terminate(Reason, Name, Msg, Mod, NState, []); - {'EXIT', What} -> - terminate(What, Name, Msg, Mod, State, []); - _ -> - terminate({bad_return_value, Reply}, Name, Msg, Mod, State, []) + loop(Parent, Name, NState, Mod, Time1, TimeoutState, Queue, []); + _ -> + handle_common_termination(Reply, Name, Msg, Mod, State, []) end. -handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Queue, Debug) -> +handle_common_reply(Reply, Parent, Name, Msg, Mod, State, TimeoutState, Queue, + Debug) -> case Reply of {noreply, NState} -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {noreply, NState}), - loop(Parent, Name, NState, Mod, infinity, Queue, Debug1); + loop(Parent, Name, NState, Mod, infinity, TimeoutState, Queue, + Debug1); {noreply, NState, Time1} -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {noreply, NState}), - loop(Parent, Name, NState, Mod, Time1, Queue, Debug1); + loop(Parent, Name, NState, Mod, Time1, TimeoutState, Queue, Debug1); + _ -> + handle_common_termination(Reply, Name, Msg, Mod, State, Debug) + end. + +handle_common_termination(Reply, Name, Msg, Mod, State, Debug) -> + case Reply of {stop, Reason, NState} -> terminate(Reason, Name, Msg, Mod, NState, Debug); {'EXIT', What} -> @@ -652,16 +925,24 @@ reply(Name, {To, Tag}, Reply, State, Debug) -> %%----------------------------------------------------------------- %% Callback functions for system messages handling. %%----------------------------------------------------------------- -system_continue(Parent, Debug, [Name, State, Mod, Time, Queue]) -> - loop(Parent, Name, State, Mod, Time, Queue, Debug). +system_continue(Parent, Debug, [Name, State, Mod, Time, TimeoutState, Queue]) -> + loop(Parent, Name, State, Mod, Time, TimeoutState, Queue, Debug). + +-ifdef(use_specs). +-spec system_terminate(_, _, _, [_]) -> no_return(). +-endif. -system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time, _Queue]) -> +system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time, + _TimeoutState, _Queue]) -> terminate(Reason, Name, [], Mod, State, Debug). -system_code_change([Name, State, Mod, Time, Queue], _Module, OldVsn, Extra) -> +system_code_change([Name, State, Mod, Time, TimeoutState, Queue], _Module, + OldVsn, Extra) -> case catch Mod:code_change(OldVsn, State, Extra) of - {ok, NewState} -> {ok, [Name, NewState, Mod, Time, Queue]}; - Else -> Else + {ok, NewState} -> + {ok, [Name, NewState, Mod, Time, TimeoutState, Queue]}; + Else -> + Else end. %%----------------------------------------------------------------- @@ -703,6 +984,8 @@ terminate(Reason, Name, Msg, Mod, State, Debug) -> exit(normal); shutdown -> exit(shutdown); + {shutdown,_}=Shutdown -> + exit(Shutdown); _ -> error_info(Reason, Name, Msg, State, Debug), exit(Reason) @@ -827,8 +1110,8 @@ name_to_pid(Name) -> %% Status information %%----------------------------------------------------------------- format_status(Opt, StatusData) -> - [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time, Queue]] = - StatusData, + [PDict, SysState, Parent, Debug, + [Name, State, Mod, _Time, _TimeoutState, Queue]] = StatusData, NameTag = if is_pid(Name) -> pid_to_list(Name); is_atom(Name) -> @@ -850,5 +1133,5 @@ format_status(Opt, StatusData) -> {data, [{"Status", SysState}, {"Parent", Parent}, {"Logged events", Log}, - {"Queued messages", queue:to_list(Queue)}]} | + {"Queued messages", priority_queue:to_list(Queue)}]} | Specfic]. From 9e6098787363ddb7a83c4073fe41469d6844fb84 Mon Sep 17 00:00:00 2001 From: joewilliams Date: Wed, 5 May 2010 22:26:22 -0700 Subject: [PATCH 03/65] added priority queue for gen_server2 --- src/priority_queue.erl | 191 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 src/priority_queue.erl diff --git a/src/priority_queue.erl b/src/priority_queue.erl new file mode 100644 index 0000000..1e481ca --- /dev/null +++ b/src/priority_queue.erl @@ -0,0 +1,191 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd, +%% Cohesive Financial Technologies LLC, and Rabbit Technologies Ltd. +%% +%% Portions created before 22-Nov-2008 00:00:00 GMT by LShift Ltd, +%% Cohesive Financial Technologies LLC, or Rabbit Technologies Ltd +%% are Copyright (C) 2007-2008 LShift Ltd, Cohesive Financial +%% Technologies LLC, and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd are Copyright (C) 2007-2010 LShift +%% Ltd. Portions created by Cohesive Financial Technologies LLC are +%% Copyright (C) 2007-2010 Cohesive Financial Technologies +%% LLC. Portions created by Rabbit Technologies Ltd are Copyright +%% (C) 2007-2010 Rabbit Technologies Ltd. +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +%% Priority queues have essentially the same interface as ordinary +%% queues, except that a) there is an in/3 that takes a priority, and +%% b) we have only implemented the core API we need. +%% +%% Priorities should be integers - the higher the value the higher the +%% priority - but we don't actually check that. +%% +%% in/2 inserts items with priority 0. +%% +%% We optimise the case where a priority queue is being used just like +%% an ordinary queue. When that is the case we represent the priority +%% queue as an ordinary queue. We could just call into the 'queue' +%% module for that, but for efficiency we implement the relevant +%% functions directly in here, thus saving on inter-module calls and +%% eliminating a level of boxing. +%% +%% When the queue contains items with non-zero priorities, it is +%% represented as a sorted kv list with the inverted Priority as the +%% key and an ordinary queue as the value. Here again we use our own +%% ordinary queue implemention for efficiency, often making recursive +%% calls into the same function knowing that ordinary queues represent +%% a base case. + + +-module(priority_queue). + +-export([new/0, is_queue/1, is_empty/1, len/1, to_list/1, in/2, in/3, + out/1, join/2]). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(priority() :: integer()). +-type(squeue() :: {queue, [any()], [any()]}). +-type(pqueue() :: squeue() | {pqueue, [{priority(), squeue()}]}). + +-spec(new/0 :: () -> pqueue()). +-spec(is_queue/1 :: (any()) -> boolean()). +-spec(is_empty/1 :: (pqueue()) -> boolean()). +-spec(len/1 :: (pqueue()) -> non_neg_integer()). +-spec(to_list/1 :: (pqueue()) -> [{priority(), any()}]). +-spec(in/2 :: (any(), pqueue()) -> pqueue()). +-spec(in/3 :: (any(), priority(), pqueue()) -> pqueue()). +-spec(out/1 :: (pqueue()) -> {empty | {value, any()}, pqueue()}). +-spec(join/2 :: (pqueue(), pqueue()) -> pqueue()). + +-endif. + +%%---------------------------------------------------------------------------- + +new() -> + {queue, [], []}. + +is_queue({queue, R, F}) when is_list(R), is_list(F) -> + true; +is_queue({pqueue, Queues}) when is_list(Queues) -> + lists:all(fun ({P, Q}) -> is_integer(P) andalso is_queue(Q) end, + Queues); +is_queue(_) -> + false. + +is_empty({queue, [], []}) -> + true; +is_empty(_) -> + false. + +len({queue, R, F}) when is_list(R), is_list(F) -> + length(R) + length(F); +len({pqueue, Queues}) -> + lists:sum([len(Q) || {_, Q} <- Queues]). + +to_list({queue, In, Out}) when is_list(In), is_list(Out) -> + [{0, V} || V <- Out ++ lists:reverse(In, [])]; +to_list({pqueue, Queues}) -> + [{-P, V} || {P, Q} <- Queues, {0, V} <- to_list(Q)]. + +in(Item, Q) -> + in(Item, 0, Q). + +in(X, 0, {queue, [_] = In, []}) -> + {queue, [X], In}; +in(X, 0, {queue, In, Out}) when is_list(In), is_list(Out) -> + {queue, [X|In], Out}; +in(X, Priority, _Q = {queue, [], []}) -> + in(X, Priority, {pqueue, []}); +in(X, Priority, Q = {queue, _, _}) -> + in(X, Priority, {pqueue, [{0, Q}]}); +in(X, Priority, {pqueue, Queues}) -> + P = -Priority, + {pqueue, case lists:keysearch(P, 1, Queues) of + {value, {_, Q}} -> + lists:keyreplace(P, 1, Queues, {P, in(X, Q)}); + false -> + lists:keysort(1, [{P, {queue, [X], []}} | Queues]) + end}. + +out({queue, [], []} = Q) -> + {empty, Q}; +out({queue, [V], []}) -> + {{value, V}, {queue, [], []}}; +out({queue, [Y|In], []}) -> + [V|Out] = lists:reverse(In, []), + {{value, V}, {queue, [Y], Out}}; +out({queue, In, [V]}) when is_list(In) -> + {{value,V}, r2f(In)}; +out({queue, In,[V|Out]}) when is_list(In) -> + {{value, V}, {queue, In, Out}}; +out({pqueue, [{P, Q} | Queues]}) -> + {R, Q1} = out(Q), + NewQ = case is_empty(Q1) of + true -> case Queues of + [] -> {queue, [], []}; + [{0, OnlyQ}] -> OnlyQ; + [_|_] -> {pqueue, Queues} + end; + false -> {pqueue, [{P, Q1} | Queues]} + end, + {R, NewQ}. + +join(A, {queue, [], []}) -> + A; +join({queue, [], []}, B) -> + B; +join({queue, AIn, AOut}, {queue, BIn, BOut}) -> + {queue, BIn, AOut ++ lists:reverse(AIn, BOut)}; +join(A = {queue, _, _}, {pqueue, BPQ}) -> + {Pre, Post} = lists:splitwith(fun ({P, _}) -> P < 0 end, BPQ), + Post1 = case Post of + [] -> [ {0, A} ]; + [ {0, ZeroQueue} | Rest ] -> [ {0, join(A, ZeroQueue)} | Rest ]; + _ -> [ {0, A} | Post ] + end, + {pqueue, Pre ++ Post1}; +join({pqueue, APQ}, B = {queue, _, _}) -> + {Pre, Post} = lists:splitwith(fun ({P, _}) -> P < 0 end, APQ), + Post1 = case Post of + [] -> [ {0, B} ]; + [ {0, ZeroQueue} | Rest ] -> [ {0, join(ZeroQueue, B)} | Rest ]; + _ -> [ {0, B} | Post ] + end, + {pqueue, Pre ++ Post1}; +join({pqueue, APQ}, {pqueue, BPQ}) -> + {pqueue, merge(APQ, BPQ, [])}. + +merge([], BPQ, Acc) -> + lists:reverse(Acc, BPQ); +merge(APQ, [], Acc) -> + lists:reverse(Acc, APQ); +merge([{P, A}|As], [{P, B}|Bs], Acc) -> + merge(As, Bs, [ {P, join(A, B)} | Acc ]); +merge([{PA, A}|As], Bs = [{PB, _}|_], Acc) when PA < PB -> + merge(As, Bs, [ {PA, A} | Acc ]); +merge(As = [{_, _}|_], [{PB, B}|Bs], Acc) -> + merge(As, Bs, [ {PB, B} | Acc ]). + +r2f([]) -> {queue, [], []}; +r2f([_] = R) -> {queue, [], R}; +r2f([X,Y]) -> {queue, [X], [Y]}; +r2f([X,Y|R]) -> {queue, [X,Y], lists:reverse(R, [])}. From ab4bccc629820cc0aafb0cb95219593c316f86b9 Mon Sep 17 00:00:00 2001 From: joewilliams Date: Wed, 5 May 2010 22:29:01 -0700 Subject: [PATCH 04/65] s/get/getkey in t02 --- t/merle_t02.t | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/merle_t02.t b/t/merle_t02.t index 5662d31..9f98e30 100644 --- a/t/merle_t02.t +++ b/t/merle_t02.t @@ -10,10 +10,10 @@ main(_) -> Key = rnd_key(), Key2 = rnd_key(), etap:is(merle:set(Key, "1", "0", "bar"), ok, "Set data"), - etap:is(merle:get(Key), "bar", "Get data"), - etap:is(merle:get(rnd_key()), undefined, "Get invalid data"), + etap:is(merle:getkey(Key), "bar", "Get data"), + etap:is(merle:getkey(rnd_key()), undefined, "Get invalid data"), etap:is(merle:set(Key2, "1", "0", {foo, bar}), ok, "Set data"), - etap:is(merle:get(Key2), {foo, bar}, "Get data"), + etap:is(merle:getkey(Key2), {foo, bar}, "Get data"), etap:end_tests(). rnd_key() -> @@ -23,4 +23,4 @@ rnd_key() -> [[random:uniform(25) + 96] || _ <-lists:seq(1,5)], [[random:uniform(9) + 47] || _ <-lists:seq(1,3)] ]). - \ No newline at end of file + From 1fc2c4f4db78a512cc69939b92f2493a00c23058 Mon Sep 17 00:00:00 2001 From: joewilliams Date: Mon, 28 Jun 2010 10:46:57 -0700 Subject: [PATCH 05/65] better disconnect procedure, thanks zabrane --- NOTICES | 1 + src/merle.erl | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/NOTICES b/NOTICES index 769be90..805996a 100644 --- a/NOTICES +++ b/NOTICES @@ -6,3 +6,4 @@ Contributors and Copyright holders: * Copyright 2009, Joe Williams * Copyright 2009, Nick Gerakines + * Copyright 2010, Zabrane Mikael diff --git a/src/merle.erl b/src/merle.erl index 1b547d2..5d6fc62 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -238,8 +238,7 @@ connect(Host, Port) -> %% @doc disconnect from memcached disconnect() -> - gen_server2:call(?SERVER, {stop}), - ok. + gen_server2:call(?SERVER, stop). %% @private start_link(Host, Port) -> @@ -249,9 +248,6 @@ start_link(Host, Port) -> init([Host, Port]) -> gen_tcp:connect(Host, Port, ?TCP_OPTS). -handle_call({stop}, _From, Socket) -> - {stop, requested_disconnect, Socket}; - handle_call({stats}, _From, Socket) -> Reply = send_generic_cmd(Socket, iolist_to_binary([<<"stats">>])), {reply, Reply, Socket}; @@ -342,6 +338,9 @@ handle_call({cas, {Key, Flag, ExpTime, CasUniq, Value}}, _From, Socket) -> {reply, Reply, Socket}. %% @private +handle_cast(stop, State) -> + {stop, normal, State}; + handle_cast(_Msg, State) -> {noreply, State}. %% @private From 60b4251026825860ce3827ba1e6b15dab8e68fd9 Mon Sep 17 00:00:00 2001 From: joewilliams Date: Mon, 28 Jun 2010 12:32:36 -0700 Subject: [PATCH 06/65] disconnect should be a cast not call --- src/merle.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/merle.erl b/src/merle.erl index 5d6fc62..87e799b 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -238,7 +238,7 @@ connect(Host, Port) -> %% @doc disconnect from memcached disconnect() -> - gen_server2:call(?SERVER, stop). + gen_server2:cast(?SERVER, stop). %% @private start_link(Host, Port) -> From d31257277cc500c7a7566b7a705ea3018f041154 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 22 Mar 2012 13:12:22 -0700 Subject: [PATCH 07/65] added incr/2, decr/2, addcounter/1, literal/1 incr/2 implements the increment command for memcached, decr implements the decrement command. addcounter/1 uses a simple (non-CAS) set to initialized a counter set at zero, which can then be fiddled with via incr/2 and decr/2. This was necessary because merle functions mainly as a key/value store for arbitrary erlang data instead of a general data-store, such as we want for cacti purposes. literal/1 allows the user to submit literal strings over the socket to memcached. This should be used with extreme caution as memcached is intolerant of malformed input, but it is useful for determining what strings sent to memcached should look like and checking assumptions about its behavior. TODO: (optional) need a Check-And-Set version of addcounter so that distributed applications can update the same value with incr/2 and decr/2. This would save client code from having to try changing a value and then making a separate call to initialize it if it does not exist. --- src/merle.erl | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index 87e799b..c3a46d0 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -51,7 +51,8 @@ -export([ stats/0, stats/1, version/0, getkey/1, delete/2, set/4, add/4, replace/2, replace/4, cas/5, set/2, flushall/0, flushall/1, verbosity/1, add/2, - cas/3, getskey/1, connect/0, connect/2, delete/1, disconnect/0 + cas/3, getskey/1, connect/0, connect/2, delete/1, disconnect/0, + incr/2, decr/2, addcounter/1, literal/1 ]). %% gen_server callbacks @@ -228,6 +229,22 @@ cas(Key, Flag, ExpTime, CasUniq, Value) -> [X] -> X end. +literal(Str) when is_binary(Str) -> + gen_server2:call(?SERVER, {literal, Str}). + +addcounter(Key) -> + Flag = random:uniform(?RANDOM_MAX), + gen_server2:call(?SERVER, {addcounter, {Key,integer_to_list(Flag),"0"}}), + ok. + +incr(Key,Value) when is_integer(Value) -> + gen_server2:call(?SERVER, {incr, {Key, integer_to_list(Value)}}), + ok. + +decr(Key,Value) when is_integer(Value) -> + gen_server2:call(?SERVER, {decr, {Key, integer_to_list(Value)}}), + ok. + %% @doc connect to memcached with defaults connect() -> connect(?DEFAULT_HOST, ?DEFAULT_PORT). @@ -298,6 +315,20 @@ handle_call({set, {Key, Flag, ExpTime, Value}}, _From, Socket) -> Bin ), {reply, Reply, Socket}; +%% special clause to add a counter to memcached instead of serialized +%% erlang data (literal 0xFFFFFFFF instead of the erlang bitstring +%% <<131,98,255,255,255,255,255,255,255,255,0>>) +handle_call({addcounter, {Key, Flag, ExpTime}}, _From, Socket) -> + Bytes = <<"8">>, + Bin = <<"FFFFFFFF">>, %% rolls over to 0, happily + Reply = send_storage_cmd( + Socket, + iolist_to_binary([ + <<"set ">>, Key, <<" ">>, Flag, <<" ">>, ExpTime, <<" ">>, Bytes + ]), + Bin + ), + {reply, Reply, Socket}; handle_call({add, {Key, Flag, ExpTime, Value}}, _From, Socket) -> Bin = term_to_binary(Value), @@ -335,7 +366,22 @@ handle_call({cas, {Key, Flag, ExpTime, CasUniq, Value}}, _From, Socket) -> ]), Bin ), - {reply, Reply, Socket}. + {reply, Reply, Socket}; +%% Added by Jeremy D. Acord, March 2012 +handle_call({incr, {Key,Value}}, _From, Socket) when is_list(Value) -> + io:fwrite("trying to increment ~s by ~s~n",[Key,Value]), + CMD = iolist_to_binary([<<"incr ">>,Key,<<" ">>,Value, <<" noreply">>]), + io:fwrite("created comand ~s~n",[CMD]), + Reply = send_generic_cmd(Socket,CMD), + {reply,Reply,Socket}; +handle_call({decr, {Key,Value}}, _From, Socket) when is_list(Value) -> + Reply = send_generic_cmd( %% gonna hack together the whole line + Socket, + iolist_to_binary([<<"decr ">>,Key,<<" ">>,Value, <<" noreply">>])), + {reply,Reply,Socket}; +handle_call({literal,Str}, _From, Socket) -> %% for testing + Reply = send_generic_cmd(Socket,Str), + {reply,Reply,Socket}. %% @private handle_cast(stop, State) -> From 8aeacc7fe7df6dbb6d516a62dba0398ed7cf5e99 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 22 Mar 2012 15:46:06 -0700 Subject: [PATCH 08/65] better addcounter/1, incr/2 and decr/2 All three of the above functions now return more erlang-ish results, in keeping with the rest of merle. addcounter/1 also uses incr/2 to determine if a prospective key exists before creating it. This should be replaced with a CAS-version in the future. --- src/merle.erl | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index c3a46d0..72c0914 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -232,18 +232,33 @@ cas(Key, Flag, ExpTime, CasUniq, Value) -> literal(Str) when is_binary(Str) -> gen_server2:call(?SERVER, {literal, Str}). +%% currently checks via incr to see if a counter exists before +%% creating it. This is a subsitute for using a cas operation to +%% initialize the counter. addcounter(Key) -> Flag = random:uniform(?RANDOM_MAX), - gen_server2:call(?SERVER, {addcounter, {Key,integer_to_list(Flag),"0"}}), - ok. + case incr(Key,0) of + not_found -> + case gen_server2:call(?SERVER, + {addcounter, {Key,integer_to_list(Flag),"0"}}) of + ["STORED"] -> ok; + ["NOT_STORED"] -> not_stored; + [X] -> X + end; + _ -> ok + end. incr(Key,Value) when is_integer(Value) -> - gen_server2:call(?SERVER, {incr, {Key, integer_to_list(Value)}}), - ok. - + case gen_server2:call(?SERVER, {incr, {Key, integer_to_list(Value)}}) of + ["NOT_FOUND"] -> not_found; + [Str] -> {ok,list_to_integer(Str)} + end. + decr(Key,Value) when is_integer(Value) -> - gen_server2:call(?SERVER, {decr, {Key, integer_to_list(Value)}}), - ok. + case gen_server2:call(?SERVER, {decr, {Key, integer_to_list(Value)}}) of + ["NOT_FOUND"] -> not_found; + [Str] -> {ok,list_to_integer(Str)} + end. %% @doc connect to memcached with defaults connect() -> @@ -319,8 +334,8 @@ handle_call({set, {Key, Flag, ExpTime, Value}}, _From, Socket) -> %% erlang data (literal 0xFFFFFFFF instead of the erlang bitstring %% <<131,98,255,255,255,255,255,255,255,255,0>>) handle_call({addcounter, {Key, Flag, ExpTime}}, _From, Socket) -> - Bytes = <<"8">>, Bin = <<"FFFFFFFF">>, %% rolls over to 0, happily + Bytes = <<"8">>, Reply = send_storage_cmd( Socket, iolist_to_binary([ @@ -369,15 +384,12 @@ handle_call({cas, {Key, Flag, ExpTime, CasUniq, Value}}, _From, Socket) -> {reply, Reply, Socket}; %% Added by Jeremy D. Acord, March 2012 handle_call({incr, {Key,Value}}, _From, Socket) when is_list(Value) -> - io:fwrite("trying to increment ~s by ~s~n",[Key,Value]), - CMD = iolist_to_binary([<<"incr ">>,Key,<<" ">>,Value, <<" noreply">>]), - io:fwrite("created comand ~s~n",[CMD]), + CMD = iolist_to_binary([<<"incr ">>,Key,<<" ">>,Value]), Reply = send_generic_cmd(Socket,CMD), {reply,Reply,Socket}; handle_call({decr, {Key,Value}}, _From, Socket) when is_list(Value) -> - Reply = send_generic_cmd( %% gonna hack together the whole line - Socket, - iolist_to_binary([<<"decr ">>,Key,<<" ">>,Value, <<" noreply">>])), + CMD = iolist_to_binary([<<"decr ">>,Key,<<" ">>,Value]), + Reply = send_generic_cmd(Socket,CMD), {reply,Reply,Socket}; handle_call({literal,Str}, _From, Socket) -> %% for testing Reply = send_generic_cmd(Socket,Str), From 57194fedfde1c4554946ef8925817a99a77ffea1 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 26 Mar 2012 13:46:07 -0700 Subject: [PATCH 09/65] Wrapped merle:literal/1 in conditional compilation; documentation merle:literal/1 is rough and possibly dangerous to leave in production code (why would we let random erlang clients submit arbitrary strings to memcached?), so it is now compiled only when the DEV flag is defined to erlc. Added EDoc segments for addcounter/1, incr/2 and decr/2 have been added --- src/merle.erl | 84 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index 72c0914..aeb7cc0 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -47,14 +47,15 @@ binary, {packet, raw}, {nodelay, true},{reuseaddr, true}, {active, true} ]). -%% gen_server API --export([ - stats/0, stats/1, version/0, getkey/1, delete/2, set/4, add/4, replace/2, - replace/4, cas/5, set/2, flushall/0, flushall/1, verbosity/1, add/2, - cas/3, getskey/1, connect/0, connect/2, delete/1, disconnect/0, - incr/2, decr/2, addcounter/1, literal/1 -]). - +%% gen_server API +-export([stats/0, stats/1, version/0, getkey/1, delete/2, set/4, add/4, + replace/2, replace/4, cas/5, set/2, flushall/0, flushall/1, + verbosity/1, add/2, cas/3, getskey/1, connect/0, connect/2, delete/1, + disconnect/0, incr/2, decr/2, addcounter/1 ]). + +-ifdef(DEV). +-export([literal/1]). +-endif. %% gen_server callbacks -export([ init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, @@ -229,12 +230,47 @@ cas(Key, Flag, ExpTime, CasUniq, Value) -> [X] -> X end. +-ifdef(DEV). +%% @doc Submit an arbitrary string to memcached using send_generic_cmd/2. This +%% is a utility function that allows a developer to experiment with the strings +%% they will submit to memcached. Memcached is intolerant of malformed input, so +%% this is not meant to be used in a serious application. Hence, it is only +%% included in merle.erl if compiled with the DEV variable defined. +%% +%% The string submitted to literal can be a multiline command; simply include +%% CRLF (<<"\r\n">> or <<10,13>>) to delimit the end of the first line. The +%% argument to literal/1 SHOULD NOT be terminated with CRLF, as this will be +%% appended by merle:send_generic_cmd/2 +%% +%% @see merle:send_generic_cmd/2 +%% @spec (string()) -> [string()] +%% @private literal(Str) when is_binary(Str) -> gen_server2:call(?SERVER, {literal, Str}). - -%% currently checks via incr to see if a counter exists before -%% creating it. This is a subsitute for using a cas operation to -%% initialize the counter. +-endif. + +%% @doc Add a key to memcached which can be used as a counter via incr/decr. +%% Currently, addcounter/1 checks via incr to see if a counter exists before +%% creating it. This is a subsitute for using a cas operation to initialize the +%% counter. +%% +%% Unlike the rest of merle, which traffics in serialized erlang data types, +%% addcounter/1 should create a value which non-erlang memcached clients can +%% work with. +%% +%% To this effect, addcounter/1 uses a new clause merle:handle_call/2 which +%% sends +%% +%% ``` +%% set 0 8\r\nFFFFFFFF\r\n''' +%% +%% to memcached via send_storage_cmd/2 (which adds the terminal CRLF and +%% generates the Flag parameter), thus creating a key with name Key with 64-bits +%% of space allocated for an integer value. FFFFFFFF is a negative value, not +%% acceptable by memcached, and so is coerced to zero. Hey presto! A counter. +%% +%% @TODO need an addcounter/2 to to accept an expiration time for the key and +%% maintain addcounter/1 as a convenience function (@equiv addcounter(Key,0)). addcounter(Key) -> Flag = random:uniform(?RANDOM_MAX), case incr(Key,0) of @@ -248,12 +284,32 @@ addcounter(Key) -> _ -> ok end. +%% @doc Interface to the incr method in memcached's protocol. +%% +%% incr/2 and decr/2 both use erlang:interger_to_list/1 to convert their integer +%% arguments into a decimal string representation, which is then submitted to +%% memcached. +%% +%% @spec incr(Key::list(),Value::integer()) -> (not_found | {ok,NewValue::integer()}) +%% @see merle:decr/2 incr(Key,Value) when is_integer(Value) -> case gen_server2:call(?SERVER, {incr, {Key, integer_to_list(Value)}}) of ["NOT_FOUND"] -> not_found; [Str] -> {ok,list_to_integer(Str)} end. - + +%% @doc Interface to the decr method in memcached's protocol. +%% +%% incr/2 and decr/2 both use erlang:interger_to_list/1 to convert their integer +%% arguments into a decimal string representation, which is then submitted to +%% memcached. +%% +%% Since incr and decr in memcached are defined to operate on the binary +%% representations of 64-bit unsigned integers, it is not possible to decrement +%% a value in memcached to below zero. +%% +%% @spec decr(Key::list(),Value::integer()) -> (not_found | {ok,NewValue::integer()}) +%% @see merle:incr/2 decr(Key,Value) when is_integer(Value) -> case gen_server2:call(?SERVER, {decr, {Key, integer_to_list(Value)}}) of ["NOT_FOUND"] -> not_found; @@ -334,7 +390,7 @@ handle_call({set, {Key, Flag, ExpTime, Value}}, _From, Socket) -> %% erlang data (literal 0xFFFFFFFF instead of the erlang bitstring %% <<131,98,255,255,255,255,255,255,255,255,0>>) handle_call({addcounter, {Key, Flag, ExpTime}}, _From, Socket) -> - Bin = <<"FFFFFFFF">>, %% rolls over to 0, happily + Bin = <<"FFFFFFFF">>, %% coerced to 0, happily Bytes = <<"8">>, Reply = send_storage_cmd( Socket, From da6a303cfef5e1e1900026cead2b0684c1ee0ef3 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 26 Mar 2012 16:38:05 -0700 Subject: [PATCH 10/65] README update * added incr and decr to listed features * added note to the effect that counters aren't erlang terms * demonstration of incr, decr and addcounter * examples of delete with counters and incr/decr on counters that don't exist --- README | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README b/README index b7c7a6d..91a83aa 100644 --- a/README +++ b/README @@ -14,10 +14,11 @@ This code is available as Open Source Software under the MIT license. Features: -* Support for stats, version, getkey, getskey, delete, set, add, replace, cas, flushall, verbosity +* Support for stats, version, getkey, getskey, delete, set, add, replace, cas, flushall, verbosity, incr and decr (with specially set "counter" keys) Notes: * Uses term_to_binary and binary_to_term to serialize/deserialize Erlang terms before sending/receiving them. This allows for native Erlang terms to be returned from memcached but doesn't play well using other languages after setting values with merle or using merle to get values set by other languages. +* getkey and getskey currently don't work on keys initialized with addcounter/1. The binary data you get back won't be a serialized erlang integer, and so the implicity binary_to_term done by getkey and getskey provokes disaster. Merle Based Projects: @@ -81,3 +82,23 @@ ok "STAT evictions 0","STAT bytes_read 216", "STAT bytes_written 468","STAT limit_maxbytes 67108864", "STAT threads 1","END"] + +> merle:addcounter("my_counter"). +ok +> merle:incr("my_counter",0). +{ok,0} +> merle:incr("my_counter",12). +{ok,12} +> merle:addcounter("my_counter"). +ok +> merle:incr("my_counter",12). +{ok,24} +> merle:decr("my_counter",30). +{ok,0} +> merle:incr("nonexistent_counter",0). +not_found +> merle:delete("my_counter"). +ok +> merle:delete("my_counter"). +not_found + From 3c0798a33fd10473213c02de84e2fc87cdd37fda Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 26 Mar 2012 16:49:07 -0700 Subject: [PATCH 11/65] Adding myself to contributer list in NOTICES --- NOTICES | 1 + 1 file changed, 1 insertion(+) diff --git a/NOTICES b/NOTICES index 805996a..77a320a 100644 --- a/NOTICES +++ b/NOTICES @@ -7,3 +7,4 @@ Contributors and Copyright holders: * Copyright 2009, Joe Williams * Copyright 2009, Nick Gerakines * Copyright 2010, Zabrane Mikael + * Copyright 2012, Jeremy D. Acord m \ No newline at end of file From 00538e0c36c2aac3e44561a565001920036b5337 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 26 Mar 2012 16:51:44 -0700 Subject: [PATCH 12/65] Revert "Adding myself to contributer list in NOTICES" This reverts commit 3c0798a33fd10473213c02de84e2fc87cdd37fda. --- NOTICES | 1 - 1 file changed, 1 deletion(-) diff --git a/NOTICES b/NOTICES index 77a320a..805996a 100644 --- a/NOTICES +++ b/NOTICES @@ -7,4 +7,3 @@ Contributors and Copyright holders: * Copyright 2009, Joe Williams * Copyright 2009, Nick Gerakines * Copyright 2010, Zabrane Mikael - * Copyright 2012, Jeremy D. Acord m \ No newline at end of file From 03148eb6035be7e41da15b110f5bb3ebe1397c03 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 26 Mar 2012 17:07:57 -0700 Subject: [PATCH 13/65] Revert "Revert "Adding myself to contributer list in NOTICES"" This reverts commit 00538e0c36c2aac3e44561a565001920036b5337. --- NOTICES | 1 + 1 file changed, 1 insertion(+) diff --git a/NOTICES b/NOTICES index 805996a..77a320a 100644 --- a/NOTICES +++ b/NOTICES @@ -7,3 +7,4 @@ Contributors and Copyright holders: * Copyright 2009, Joe Williams * Copyright 2009, Nick Gerakines * Copyright 2010, Zabrane Mikael + * Copyright 2012, Jeremy D. Acord m \ No newline at end of file From ab1386e0c1054d787d09eb636e4cece358c06aa2 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 26 Mar 2012 17:09:55 -0700 Subject: [PATCH 14/65] adding LICENSE.txt to the project merle is distributed under the MIT license, but doesn't seem to have copy of the text of the MIT license with the code. With the addition of LICENCE.txt, that little wrinkle should be cleared up. --- LICENSE.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..a4f3b6c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,9 @@ +Copyright (c) 2009 Joe Williams +Copyright (c) 2009 Nick Gerakines +Copyright (c) 2010 Zabrane Mikael + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 655f4c0a537a5d549d4aa69a56963972ca878f6f Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 18 May 2012 10:47:34 -0700 Subject: [PATCH 15/65] Trying to rebarify merle --- rebar | Bin 0 -> 101515 bytes src/merle.app.src | 13 +++++++++++++ src/merle_app.erl | 16 ++++++++++++++++ src/merle_sup.erl | 29 +++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+) create mode 100755 rebar create mode 100644 src/merle.app.src create mode 100644 src/merle_app.erl create mode 100644 src/merle_sup.erl diff --git a/rebar b/rebar new file mode 100755 index 0000000000000000000000000000000000000000..77abae6f3d390972ba56040645c0a770acf007df GIT binary patch literal 101515 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 j|xmlpl_iv_ZW? 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 + merle_sup:start_link(). + +stop(_State) -> + ok. diff --git a/src/merle_sup.erl b/src/merle_sup.erl new file mode 100644 index 0000000..62a5f5a --- /dev/null +++ b/src/merle_sup.erl @@ -0,0 +1,29 @@ + +-module(merle_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +%% Helper macro for declaring children of supervisor +-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). + +%% =================================================================== +%% API functions +%% =================================================================== + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% =================================================================== +%% Supervisor callbacks +%% =================================================================== + +init([]) -> + Merle = ?CHILD(merle, worker), + {ok, { {one_for_one, 5, 10}, [Merle]} }. + From cd05757c1e37032c4b25f4756deb1371425a0831 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 18 May 2012 14:25:10 -0700 Subject: [PATCH 16/65] Adding a more convenient set method that can skip the creation of arbitrary flag --- src/merle.erl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index aeb7cc0..7ebff9e 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -49,7 +49,7 @@ %% gen_server API -export([stats/0, stats/1, version/0, getkey/1, delete/2, set/4, add/4, - replace/2, replace/4, cas/5, set/2, flushall/0, flushall/1, + replace/2, replace/4, cas/5, set/2, set/3, flushall/0, flushall/1, verbosity/1, add/2, cas/3, getskey/1, connect/0, connect/2, delete/1, disconnect/0, incr/2, decr/2, addcounter/1 ]). @@ -158,8 +158,11 @@ delete(Key, Time) -> %% @doc Store a key/value pair. set(Key, Value) -> + set(Key, "0", Value). + +set(Key, ExpTime Value) -> Flag = random:uniform(?RANDOM_MAX), - set(Key, integer_to_list(Flag), "0", Value). + set(Key, integer_to_list(Flag), ExpTime, Value). set(Key, Flag, ExpTime, Value) when is_atom(Key) -> set(atom_to_list(Key), Flag, ExpTime, Value); From 210caa02d3d90ff5711ca3da5d32a1d12f886e14 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 18 May 2012 14:28:30 -0700 Subject: [PATCH 17/65] Fixed stupid comp error miss --- ebin/gen_server2.beam | Bin 0 -> 16384 bytes ebin/merle.app | 8 ++++++++ ebin/merle.beam | Bin 0 -> 8564 bytes ebin/merle_app.beam | Bin 0 -> 648 bytes ebin/merle_sup.beam | Bin 0 -> 788 bytes ebin/priority_queue.beam | Bin 0 -> 4128 bytes src/merle.erl | 2 +- 7 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 ebin/gen_server2.beam create mode 100644 ebin/merle.app create mode 100644 ebin/merle.beam create mode 100644 ebin/merle_app.beam create mode 100644 ebin/merle_sup.beam create mode 100644 ebin/priority_queue.beam diff --git a/ebin/gen_server2.beam b/ebin/gen_server2.beam new file mode 100644 index 0000000000000000000000000000000000000000..e6c3225448eb57718c40cbabf79eccde25277790 GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/ebin/merle.app b/ebin/merle.app new file mode 100644 index 0000000..0a5c0d3 --- /dev/null +++ b/ebin/merle.app @@ -0,0 +1,8 @@ +{application,merle, + [{description,"Erlang Memcached Client"}, + {vsn,"1"}, + {modules,[gen_server2,merle,merle_app,merle_sup,priority_queue]}, + {registered,[]}, + {applications,[kernel,stdlib]}, + {mod,{merle_app,[]}}, + {env,[]}]}. diff --git a/ebin/merle.beam b/ebin/merle.beam new file mode 100644 index 0000000000000000000000000000000000000000..cab98f3bb71af14d02e24e9d89cb9e9e2ff78b2b GIT binary patch literal 8564 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 I)N8S%5T3PtJ}xP6oENaDkPzoYN`XXkmncXY+! z(NQ6}r{MwUXm|t)M8iw)2Fy4Coy16=JU`zzGv3_?kI(l3yvxJm`J=hh4FEhtOeT#m zwOH`B9a+bXyN+$wZVmCGmZ}P@)2*kg?M4?JvZY$;5OrK#+`JHOv5`K~g}}Zq5fOr{ zM@$d_i~7imZzU}0lQRIe*YA1pdBkS3%->~CdhlWc?DZ`6dpL_C)()A7UC#X7t7MY+ z*yyPnY3kCF(JOsx1?>dp+b0=DtQYngK= zwJJ-1qZ75n2A_tW0ZGQXrK0^eB-EWNq^TefnN&n!vVyTxdEFIa7&BfAV^LJW=wNzp zx=*CQ$wsWg41f3+AMu#)@RV;+CA7FtE-JX2KDQ`LOJS`@Tcb;nJDUzqBef>K^5~IF i@z%?EW*zPdr&7^f-y#3n=p5-DSp4n#Z`k=y3x5F99+0E} literal 0 HcmV?d00001 diff --git a/ebin/merle_sup.beam b/ebin/merle_sup.beam new file mode 100644 index 0000000000000000000000000000000000000000..3ca352bb04d86c9f813e0ff1728b4473755318a5 GIT binary patch literal 788 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 z?dnForECI set(Key, Value) -> set(Key, "0", Value). -set(Key, ExpTime Value) -> +set(Key, ExpTime, Value) -> Flag = random:uniform(?RANDOM_MAX), set(Key, integer_to_list(Flag), ExpTime, Value). From a18939f7e17f53cf32cda9100f50880aea9fd629 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 18 May 2012 14:29:36 -0700 Subject: [PATCH 18/65] Adding ebin to .gitignore --- .gitignore | 1 + ebin/gen_server2.beam | Bin 16384 -> 0 bytes ebin/merle.app | 8 -------- ebin/merle.beam | Bin 8564 -> 0 bytes ebin/merle_app.beam | Bin 648 -> 0 bytes ebin/merle_sup.beam | Bin 788 -> 0 bytes ebin/priority_queue.beam | Bin 4128 -> 0 bytes 7 files changed, 1 insertion(+), 8 deletions(-) create mode 100644 .gitignore delete mode 100644 ebin/gen_server2.beam delete mode 100644 ebin/merle.app delete mode 100644 ebin/merle.beam delete mode 100644 ebin/merle_app.beam delete mode 100644 ebin/merle_sup.beam delete mode 100644 ebin/priority_queue.beam diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..775d6a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +ebin \ No newline at end of file diff --git a/ebin/gen_server2.beam b/ebin/gen_server2.beam deleted file mode 100644 index e6c3225448eb57718c40cbabf79eccde25277790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 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 diff --git a/ebin/merle.app b/ebin/merle.app deleted file mode 100644 index 0a5c0d3..0000000 --- a/ebin/merle.app +++ /dev/null @@ -1,8 +0,0 @@ -{application,merle, - [{description,"Erlang Memcached Client"}, - {vsn,"1"}, - {modules,[gen_server2,merle,merle_app,merle_sup,priority_queue]}, - {registered,[]}, - {applications,[kernel,stdlib]}, - {mod,{merle_app,[]}}, - {env,[]}]}. diff --git a/ebin/merle.beam b/ebin/merle.beam deleted file mode 100644 index cab98f3bb71af14d02e24e9d89cb9e9e2ff78b2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8564 zcmb7J3v?7!nyyN9(zlvisH8eiUJV_DA_NM$i98}y(j-kojDdi>#H7<*37toG=MJIxY55R~Dz8BqfM%P)Fb&szZ$7NmDv&WfP(RF5LT<5s|f4jPA2=RMVXy?v_sE~#r^TvuvR_X z7}KL0^ymyrz~9l41#SLtYqlQ(8sm}1j!-Oah1OUo5*Fdw9EpYEJtIQlxZbKqlO7q7 zcGA+(9b1PPTJ?Cl-XnI0gL+FStOu=Nh=C<5sCVdbT?|L!jV%#q7GiolE9wsiBc0an zaHu5`?X+QIEFOvKLBSskTBCYbhd-bT0e{Q}_W=lt>rqQ69EgrUXb8Z>VhQSjs5KA? zhxI_*77WEq)dK0dpl`9b4Z0kSLzlwskRcQYVByAipljGVz|f&LV*jO3B$>B46!u4Z zG7&zBe~@vJa(!rx(q3x*||LgALkus|dTHK10jE@B!; zAJ;Pj9g&zW#q@Bn5#Z?2P@pl;8ML4wVMR9ltq|E6%r@QPctevnF}%x(>VXZ7u~26h zbcEFwpl8DqFq6ZS6^lnh;nu8pq+JikGWBRQ63t=&f;lEzFwBh!i!~nV)Fa*Te3m&7 z!OWWgV?2>QD}#a^O7ND&|qRD8=$c=J*m*$mNo-VZZ z8oe30eG=?_l*D5(Or2`z_Pr0;2eThW^;od9@|Rme+JpmtUwFIj@v-4t%j8hOS=tdVaJ;P(wEQlQd8 zR)pj*GHC%+DZH%88;L3;mz60t%M{68%}tA)+Iy`3q=?;^Wr_`wi*-gCM9#?l4C;(h z>F7iqd!o)Ls5APqI-i+!@>rd*W}Pvt&e&9)F>VSsW}UGH$-_DhgXCxAegk#JY2JD{ z%R!wy8E=pRFgkeRRB4HjEjcvVVvs_puUX_w@B#mlGCD!VCcy$p4N?SLYl5s<1k+&x z<(eEd4wzeB8E4lwCdy)lQeh}((MfWp?DCY+Nv^prI;lVt>*zJ&0y>c=*Obyp9&eMI zDo)KNCAgZ%Y+cNjm`P+3yD__^7^E1xHQ68|GjeS?(1lV>cCxOiw2UWH3^EEERGyS& ztEwiHXtTK=eh@S#^ulhC(N;+{$QbO4-O0M+(n^F(jZTAzsRkK~!5UAdsZq_`Z?BgNMmqy)&w1Bz#5J7DPTCX+Z-CNLVWLFnH4B#(CFaSr&; zHpoN_1s><9bnX>APP&4}Il%nfGFma1M`bXN=v+}ouN%zcE+2AKk6d<3x>QDI!HsJ?8DfyxSPPs}rQEC(ge)zi4NfxzR(c`? zpCNOSb@%??kj`4$KDm(rUB8*DK`4m?8Oh~kjCq#D)fwL=fQ z^$dKW*O_By@GBYoMic&86aHF+zm~yY1MqL+$(mBSmcegi@EtNwZek{Y4<>|?B7P*EwKWg$+^*O%8C5NSdQs&hS(V9yoK#T82yW zo&zA)*J({W=`u(iIF5s9vwjxsGRL^fS?P3n+_cN1dCwzm%_SS;hB~#2CsBjcLxfZ1 zNo+Qa3Kas47HATUJLo2!#H$3(rP3%)j%Yzsly;A&8&1=WJn7DcY3RKm!2WNLC0407 zafAUKCgeUClNuHF)d?^G@sz^`WdjSjmTh?Dnat3 zaFbEC0@`ECr<-kg^cGtV-C`@GTWv-3R@+Fr?Hk(LM{gTX`#z)F-=I5A)19_!=q{f0 z03Dt=AobZz^*NR*54WJoBe|mYOnHWfNGgRmRY5}23fu(Q|gCP9IQ;77t3*W z5xZ1v&ycIVBuAF31zA8%5UNW+5}G@uEfCNz7skf2V7CUR%NvuOb?(N~Bjj9RQR)7x zGP^kmRm-kVKn0i*z;pmi6<{tf$Vvut)#Wf(F_@B6Eo3Ai*;A0xVInvg!MS&<0B-dl zaBGGHcgx=Z?j{Cq?d9Os{uFTbOW#Na9jQca7_%H|8j8tp8_uL61e6;-~vN} z+nNNYQt4bhOuz;h)c}lY3|3ur%uDiXuu>p3ITG0mF?&n0BcQhqxfr+oq{Ky-cwJiJ(2$9HQ;7j(ikqvUEykp6 zX-V6MOnO@~sYE4>CB=jdYzdFVG#zPaI)+Ts_akXWVVcggG@V1H+5RJGMq`?ATADDV zISAV+tYo=H74Xq3doAp_yW>aljKMsSv^){WbKt5EnVmn9W-O-ZN=wrlOk}_Knc4Li;{w z-<2s!oTFFfoHsc*%?Z2cCadFICPxj9C-)ho2YhEhk^RFRD)Uvr*M+_==&=QkaR%88 z5v+~twQvHq!M0%+b~`wO+;S<~{RY_r*<4Whz;J0$Wk%jsaL4xs6783G@-u_n3K4FI zI5UJ=@#KL5`mivYK7=QMhYB>6KIou_dGa9as&bX! zaF)`CWLtS2jGsaJtkQYSOB}#MA8}lOG=~#zQo?NxI6G}~^E6vU;s^0Vh4cZQJX}m4 zD3W*QYqskW=ARp6JK&fHkot$K68z{l$PS)7Qa~RAD8~@Wu>#FOkErx0PmWxwaZI*B zjbo`ADol!_$!0(czth2`JWd~VX`E*VebhrHIeOs2VN^*TfFCF1V$`Fi!3EvgiQR%h z@sjcE&T(-zJub`vQ7_MZTs|_39*5JFrva`Fl?|Q%TmzK>PYkZH%9v-nn;ze;xp1y) z9vJ*xRtes^k=tQ2Avy3`BJr-IMA)qY`?p&(dK%6RxEtfkK60#(KEjj7is>UMx)AGe zgX~@i$IHhJvIl7M0BuhU*MK&+LGIwmlLho?pzRdWb_!{GN~I@w@>CLgDLut#JC&kM zgQO>t%?aAT1!y~&pzUOWwv$NPDW}_{4Wmw)2BhuI6m2*+?zAFp13=rrm9z~YZ38B4 z114<)CT#dqEFio^Wv@qgLhxfpj*&N;AjHW-pxqV*n_azVjewD z71Aep@^mqMGDRe!A27&0NaQmHxfh6Bpz-ANR{DZQa-Hiv6X^>s5B?yAbt~K1oZQQk zXA9_AAomQCdj`pUPNmQD;?Nc5TDups;R?;dl_UZ2x7SxqznZ6dN;@v z5VYS3qL}(_xH>`5CkBFf5$}4C(IEH--|ZM@7~5{@aN$BABSFxvgA|#%AFeotm}fNz zjwNzYZ|Yd94kRB0#{{`R9rJiV@<7l&$JEh|9OrgZdzE=BS z_QC3twC}+)h5oZUF1~D?z<=G^{+q8ehkc;G`TpB=w&4~3c3V!zi9ess^e-Q__xqBM z{^9YgDRF|H%LT$C+uT|7*;gm2D?ZL=Sws zw0HmXwCN zka?!w57(I>_=n%=+#oo92SHq>egLjBKyY5|2T?)LXCH{e)FYMgcoc2hJK(=%T}F@- zHpIes!-plV`>U=)9~}PpuXzV3X}=8@&^j<1VE z+qp4|BD!NmsG}p~?~FMr+9J9J5)ZZXOoi{C^DtZTTX;K{ePiOQ_#B5i)zt39zvqrk&@If_(g)=ZZ3%;iYLr}@0!CV`K>Hr4Ehz&+c{<0a`=oNo7SesV*0eMXrv9kg2$$@PyN$k z(Lh3*3SalzDw|_*0L0b8$9dolpL5B-v!EdNe6Cm}dc+#hEBeG*@p`ddTp~7ztHjk} zljs)%Vn}QgJH)6M6T8J*#9PH};%(w~v0pqYJ}Mp)kBg6sPl_kRQ{waD8S$+6qWF^d ziujs%PW+|#miV^#j`%C_UGcp5p7^o&YwDvv71l*g6FltYT499AAs zZdJA^eM+ygSGh-dP5C?J4dw5Z9%Z8vP}V3hC8D$|zfk^BIj{Vy@}csP@>}Io<#Xjf zl`oXvD}PY_NBLIyPWeCOuf9xQmM_~k+&98UeTBXv-&o%`-*}(HSL&PMo8~L`&E)81 I)N8S%5T3PtJ}xP6oENaDkPzoYN`XXkmncXY+! z(NQ6}r{MwUXm|t)M8iw)2Fy4Coy16=JU`zzGv3_?kI(l3yvxJm`J=hh4FEhtOeT#m zwOH`B9a+bXyN+$wZVmCGmZ}P@)2*kg?M4?JvZY$;5OrK#+`JHOv5`K~g}}Zq5fOr{ zM@$d_i~7imZzU}0lQRIe*YA1pdBkS3%->~CdhlWc?DZ`6dpL_C)()A7UC#X7t7MY+ z*yyPnY3kCF(JOsx1?>dp+b0=DtQYngK= zwJJ-1qZ75n2A_tW0ZGQXrK0^eB-EWNq^TefnN&n!vVyTxdEFIa7&BfAV^LJW=wNzp zx=*CQ$wsWg41f3+AMu#)@RV;+CA7FtE-JX2KDQ`LOJS`@Tcb;nJDUzqBef>K^5~IF i@z%?EW*zPdr&7^f-y#3n=p5-DSp4n#Z`k=y3x5F99+0E} diff --git a/ebin/merle_sup.beam b/ebin/merle_sup.beam deleted file mode 100644 index 3ca352bb04d86c9f813e0ff1728b4473755318a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 788 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 z?dnForECI Date: Fri, 18 May 2012 15:48:28 -0700 Subject: [PATCH 19/65] Made addcounter functionality work properly with target version of memcache --- src/merle.erl | 44 +++++++++++++++++++++++++------------------- src/merle_app.erl | 16 ---------------- src/merle_sup.erl | 29 ----------------------------- 3 files changed, 25 insertions(+), 64 deletions(-) delete mode 100644 src/merle_app.erl delete mode 100644 src/merle_sup.erl diff --git a/src/merle.erl b/src/merle.erl index e9b69ec..20f7413 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -51,7 +51,7 @@ -export([stats/0, stats/1, version/0, getkey/1, delete/2, set/4, add/4, replace/2, replace/4, cas/5, set/2, set/3, flushall/0, flushall/1, verbosity/1, add/2, cas/3, getskey/1, connect/0, connect/2, delete/1, - disconnect/0, incr/2, decr/2, addcounter/1 ]). + disconnect/0, incr/2, decr/2, incr_safe/2, incr_safe/3 ]). -ifdef(DEV). -export([literal/1]). @@ -253,40 +253,46 @@ literal(Str) when is_binary(Str) -> -endif. %% @doc Add a key to memcached which can be used as a counter via incr/decr. -%% Currently, addcounter/1 checks via incr to see if a counter exists before +%% Currently, incr_safe/2 checks via incr to see if a counter exists before %% creating it. This is a subsitute for using a cas operation to initialize the %% counter. %% %% Unlike the rest of merle, which traffics in serialized erlang data types, -%% addcounter/1 should create a value which non-erlang memcached clients can +%% incr_safe/2 should create a value which non-erlang memcached clients can %% work with. %% -%% To this effect, addcounter/1 uses a new clause merle:handle_call/2 which +%% To this effect, incr_safe/2 uses a new clause merle:handle_call/2 which %% sends %% %% ``` -%% set 0 8\r\nFFFFFFFF\r\n''' +%% set 0 4\r\n0000\r\n''' %% %% to memcached via send_storage_cmd/2 (which adds the terminal CRLF and %% generates the Flag parameter), thus creating a key with name Key with 64-bits %% of space allocated for an integer value. FFFFFFFF is a negative value, not %% acceptable by memcached, and so is coerced to zero. Hey presto! A counter. %% -%% @TODO need an addcounter/2 to to accept an expiration time for the key and -%% maintain addcounter/1 as a convenience function (@equiv addcounter(Key,0)). -addcounter(Key) -> +incr_safe(Key, Value) -> + incr_safe(Key, Value, "0"). + +incr_safe(Key, Value, ExpTime) when is_integer(ExpTime) -> + incr_safe(Key, Value, integer_to_list(ExpTime)); + +incr_safe(Key, Value, ExpTime) -> Flag = random:uniform(?RANDOM_MAX), - case incr(Key,0) of - not_found -> - case gen_server2:call(?SERVER, - {addcounter, {Key,integer_to_list(Flag),"0"}}) of - ["STORED"] -> ok; - ["NOT_STORED"] -> not_stored; - [X] -> X - end; - _ -> ok + case incr(Key, Value) of + not_found -> + case gen_server2:call(?SERVER, {addcounter, {Key, integer_to_list(Flag), ExpTime}}) of + ["STORED"] -> + incr(Key, Value); + ["NOT_STORED"] -> + not_stored; + [X] -> X + end; + Result -> Result end. + %% @doc Interface to the incr method in memcached's protocol. %% %% incr/2 and decr/2 both use erlang:interger_to_list/1 to convert their integer @@ -393,8 +399,8 @@ handle_call({set, {Key, Flag, ExpTime, Value}}, _From, Socket) -> %% erlang data (literal 0xFFFFFFFF instead of the erlang bitstring %% <<131,98,255,255,255,255,255,255,255,255,0>>) handle_call({addcounter, {Key, Flag, ExpTime}}, _From, Socket) -> - Bin = <<"FFFFFFFF">>, %% coerced to 0, happily - Bytes = <<"8">>, + Bin = <<"0000">>, + Bytes = <<"4">>, Reply = send_storage_cmd( Socket, iolist_to_binary([ diff --git a/src/merle_app.erl b/src/merle_app.erl deleted file mode 100644 index 9b9be4c..0000000 --- a/src/merle_app.erl +++ /dev/null @@ -1,16 +0,0 @@ --module(merle_app). - --behaviour(application). - -%% Application callbacks --export([start/2, stop/1]). - -%% =================================================================== -%% Application callbacks -%% =================================================================== - -start(_StartType, _StartArgs) -> - merle_sup:start_link(). - -stop(_State) -> - ok. diff --git a/src/merle_sup.erl b/src/merle_sup.erl deleted file mode 100644 index 62a5f5a..0000000 --- a/src/merle_sup.erl +++ /dev/null @@ -1,29 +0,0 @@ - --module(merle_sup). - --behaviour(supervisor). - -%% API --export([start_link/0]). - -%% Supervisor callbacks --export([init/1]). - -%% Helper macro for declaring children of supervisor --define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). - -%% =================================================================== -%% API functions -%% =================================================================== - -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -%% =================================================================== -%% Supervisor callbacks -%% =================================================================== - -init([]) -> - Merle = ?CHILD(merle, worker), - {ok, { {one_for_one, 5, 10}, [Merle]} }. - From c2b7b23292252d916addc49aa201857be354ffde Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Mon, 21 May 2012 10:44:00 -0700 Subject: [PATCH 20/65] Added support for retrieving counter values, or indeed any value store in purely list form --- src/merle.erl | 61 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index 20f7413..1b85b94 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -51,7 +51,7 @@ -export([stats/0, stats/1, version/0, getkey/1, delete/2, set/4, add/4, replace/2, replace/4, cas/5, set/2, set/3, flushall/0, flushall/1, verbosity/1, add/2, cas/3, getskey/1, connect/0, connect/2, delete/1, - disconnect/0, incr/2, decr/2, incr_safe/2, incr_safe/3 ]). + disconnect/0, incr/2, decr/2, incr_counter/2, incr_counter/3, getcounter/1 ]). -ifdef(DEV). -export([literal/1]). @@ -102,13 +102,23 @@ flushall(Delay) -> end. %% @doc retrieve value based off of key -getkey(Key) when is_atom(Key) -> - getkey(atom_to_list(Key)); getkey(Key) -> - case gen_server2:call(?SERVER, {getkey,{Key}}) of + getkey(Key, term). + +getkey(Key, DataType) when is_atom(Key) -> + getkey(atom_to_list(Key), DataType); +getkey(Key, DataType) -> + case gen_server2:call(?SERVER, {getkey,{Key, DataType}}) of ["END"] -> undefined; [X] -> X end. + +%% @doc used in conjunction with incr_counter to retrieve an integer value from cache +getcounter(Key) -> + case getkey(Key, list) of + undefined -> undefined; + Number -> list_to_integer(Number) + end. %% @doc retrieve value based off of key for use with cas getskey(Key) when is_atom(Key) -> @@ -253,15 +263,15 @@ literal(Str) when is_binary(Str) -> -endif. %% @doc Add a key to memcached which can be used as a counter via incr/decr. -%% Currently, incr_safe/2 checks via incr to see if a counter exists before +%% Currently, incr_counter/2 checks via incr to see if a counter exists before %% creating it. This is a subsitute for using a cas operation to initialize the %% counter. %% %% Unlike the rest of merle, which traffics in serialized erlang data types, -%% incr_safe/2 should create a value which non-erlang memcached clients can +%% incr_counter/2 should create a value which non-erlang memcached clients can %% work with. %% -%% To this effect, incr_safe/2 uses a new clause merle:handle_call/2 which +%% To this effect, incr_counter/2 uses a new clause merle:handle_call/2 which %% sends %% %% ``` @@ -272,13 +282,13 @@ literal(Str) when is_binary(Str) -> %% of space allocated for an integer value. FFFFFFFF is a negative value, not %% acceptable by memcached, and so is coerced to zero. Hey presto! A counter. %% -incr_safe(Key, Value) -> - incr_safe(Key, Value, "0"). +incr_counter(Key, Value) -> + incr_counter(Key, Value, "0"). -incr_safe(Key, Value, ExpTime) when is_integer(ExpTime) -> - incr_safe(Key, Value, integer_to_list(ExpTime)); +incr_counter(Key, Value, ExpTime) when is_integer(ExpTime) -> + incr_counter(Key, Value, integer_to_list(ExpTime)); -incr_safe(Key, Value, ExpTime) -> +incr_counter(Key, Value, ExpTime) -> Flag = random:uniform(?RANDOM_MAX), case incr(Key, Value) of not_found -> @@ -369,8 +379,8 @@ handle_call({flushall, {Delay}}, _From, Socket) -> Reply = send_generic_cmd(Socket, iolist_to_binary([<<"flush_all ">>, Delay])), {reply, Reply, Socket}; -handle_call({getkey, {Key}}, _From, Socket) -> - Reply = send_get_cmd(Socket, iolist_to_binary([<<"get ">>, Key])), +handle_call({getkey, {Key, DataType}}, _From, Socket) -> + Reply = send_get_cmd(Socket, iolist_to_binary([<<"get ">>, Key]), DataType), {reply, Reply, Socket}; handle_call({getskey, {Key}}, _From, Socket) -> @@ -495,9 +505,9 @@ send_storage_cmd(Socket, Cmd, Value) -> %% @private %% @doc send_get_cmd/2 function for retreival commands -send_get_cmd(Socket, Cmd) -> +send_get_cmd(Socket, Cmd, DataType) -> gen_tcp:send(Socket, <>), - Reply = recv_complex_get_reply(Socket), + Reply = recv_complex_get_reply(Socket, DataType), Reply. %% @private @@ -520,7 +530,7 @@ recv_simple_reply() -> %% @private %% @doc receive function for respones containing VALUEs -recv_complex_get_reply(Socket) -> +recv_complex_get_reply(Socket, DataType) -> receive %% For receiving get responses where the key does not exist {tcp, Socket, <<"END\r\n">>} -> ["END"]; @@ -530,7 +540,7 @@ recv_complex_get_reply(Socket) -> Parse = io_lib:fread("~s ~s ~u ~u\r\n", binary_to_list(Data)), {ok,[_,_,_,Bytes], ListBin} = Parse, Bin = list_to_binary(ListBin), - Reply = get_data(Socket, Bin, Bytes, length(ListBin)), + Reply = get_data(Socket, Bin, Bytes, length(ListBin), DataType), [Reply]; {error, closed} -> connection_closed @@ -549,7 +559,7 @@ recv_complex_gets_reply(Socket) -> Parse = io_lib:fread("~s ~s ~u ~u ~u\r\n", binary_to_list(Data)), {ok,[_,_,_,Bytes,CasUniq], ListBin} = Parse, Bin = list_to_binary(ListBin), - Reply = get_data(Socket, Bin, Bytes, length(ListBin)), + Reply = get_data(Socket, Bin, Bytes, length(ListBin), term), [CasUniq, Reply]; {error, closed} -> connection_closed @@ -558,15 +568,20 @@ recv_complex_gets_reply(Socket) -> %% @private %% @doc recieve loop to get all data -get_data(Socket, Bin, Bytes, Len) when Len < Bytes + 7-> +get_data(Socket, Bin, Bytes, Len, DataType) when Len < Bytes + 7-> receive {tcp, Socket, Data} -> Combined = <>, - get_data(Socket, Combined, Bytes, size(Combined)); + get_data(Socket, Combined, Bytes, size(Combined), DataType); {error, closed} -> connection_closed after ?TIMEOUT -> timeout end; -get_data(_, Data, Bytes, _) -> +get_data(_, Data, _Bytes, _, list) -> + {ok,[List], _} = io_lib:fread("~s\r\nEND\r\n", binary_to_list(Data)), + List; +get_data(_, Data, Bytes, _, term) -> <> = Data, - binary_to_term(Bin). + binary_to_term(Bin); +get_data(_, _, _, _, _) -> + error. From d216f70379ffa95b407e81177ff17f8e9617ebad Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Tue, 10 Jul 2012 16:04:08 -0700 Subject: [PATCH 21/65] Debugged issues with merle incr/decr operation --- src/merle.erl | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index 83d6e75..6689eb3 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -118,7 +118,8 @@ getkeys(Ref, Keys) when is_list(Keys) -> %% @doc used in conjunction with incr_counter to retrieve an integer value from cache getcounter(Ref, Key) -> case getkey(Ref, Key) of - undefined -> undefined; + {error, not_found} -> undefined; + {error, _} -> undefined; Number -> list_to_integer(Number) end. @@ -268,11 +269,11 @@ incr_counter(Ref, Key, Value, ExpTime) -> case incr(Ref, Key, Value) of not_found -> case gen_server2:call(Ref, {addcounter, {Key, integer_to_list(Flag), ExpTime}}) of - ["STORED"] -> + {ok, stored} -> incr(Ref, Key, Value); - ["NOT_STORED"] -> + {error, not_stored} -> not_stored; - [X] -> X + X -> X end; Result -> Result end. @@ -288,8 +289,10 @@ incr_counter(Ref, Key, Value, ExpTime) -> %% @see merle:decr/2 incr(Ref, Key,Value) when is_integer(Value) -> case gen_server2:call(Ref, {incr, {Key, integer_to_list(Value)}}) of - ["NOT_FOUND"] -> not_found; - [Str] -> {ok,list_to_integer(Str)} + {error, not_found} -> not_found; + Line -> + {ok, [IntegerString], []} = io_lib:fread("~s\r\n", binary_to_list(Line)), + {ok, list_to_integer(IntegerString)} end. %% @doc Interface to the decr method in memcached's protocol. @@ -306,8 +309,8 @@ incr(Ref, Key,Value) when is_integer(Value) -> %% @see merle:incr/2 decr(Ref, Key, Value) when is_integer(Value) -> case gen_server2:call(Ref, {decr, {Key, integer_to_list(Value)}}) of - ["NOT_FOUND"] -> not_found; - [Str] -> {ok,list_to_integer(Str)} + {error, not_found} -> not_found; + [Str] -> {ok, list_to_integer(Str)} end. %% @doc connect to memcached with defaults @@ -384,6 +387,7 @@ handle_call({set, {Key, Flag, ExpTime, Value}}, _From, Socket) -> Bin ), {reply, Reply, Socket}; + %% special clause to add a counter to memcached instead of serialized %% erlang data (literal 0xFFFFFFFF instead of the erlang bitstring %% <<131,98,255,255,255,255,255,255,255,255,0>>) @@ -436,18 +440,17 @@ handle_call({cas, {Key, Flag, ExpTime, CasUniq, Value}}, _From, Socket) -> Bin ), {reply, Reply, Socket}; + %% Added by Jeremy D. Acord, March 2012 -handle_call({incr, {Key,Value}}, _From, Socket) when is_list(Value) -> +handle_call({incr, {Key, Value}}, _From, Socket) when is_list(Value) -> CMD = iolist_to_binary([<<"incr ">>,Key,<<" ">>,Value]), - Reply = send_generic_cmd(Socket,CMD), - {reply,Reply,Socket}; -handle_call({decr, {Key,Value}}, _From, Socket) when is_list(Value) -> + Reply = send_generic_cmd(Socket, CMD), + {reply, Reply, Socket}; + +handle_call({decr, {Key, Value}}, _From, Socket) when is_list(Value) -> CMD = iolist_to_binary([<<"decr ">>,Key,<<" ">>,Value]), - Reply = send_generic_cmd(Socket,CMD), - {reply,Reply,Socket}; -handle_call({literal,Str}, _From, Socket) -> %% for testing - Reply = send_generic_cmd(Socket,Str), - {reply,Reply,Socket}. + Reply = send_generic_cmd(Socket, CMD), + {reply, Reply, Socket}. %% @private handle_cast(stop, State) -> @@ -558,13 +561,13 @@ parse_simple_response_line(<<"OK", _B/binary>>) -> ok; parse_simple_response_line(<<"ERROR", _B/binary>> =L ) -> {error, L}; parse_simple_response_line(<<"CLIENT_ERROR", _B/binary>> =L ) -> {error, L}; parse_simple_response_line(<<"SERVER_ERROR", _B/binary>> =L) -> {error, L}; -parse_simple_response_line(<<"STORED", _B/binary>>) -> ok; -parse_simple_response_line(<<"NOT_STORED", _B/binary>> ) -> ok; +parse_simple_response_line(<<"STORED", _B/binary>>) -> {ok, stored}; +parse_simple_response_line(<<"NOT_STORED", _B/binary>> ) -> {error, not_stored}; parse_simple_response_line(<<"EXISTS", _B/binary>> ) -> {error, exists}; parse_simple_response_line(<<"NOT_FOUND", _B/binary>> ) -> {error, not_found}; parse_simple_response_line(<<"DELETED", _B/binary>> ) -> ok; parse_simple_response_line(<<"VERSION", _B/binary>> =L) -> {ok, L}; -parse_simple_response_line(Line) -> {error, {unknown_response, Line}}. +parse_simple_response_line(Line) -> Line. %% @private @@ -590,7 +593,7 @@ recv_complex_get_reply(Socket, Accum) -> {error, Error} -> {error, Error} end. - + %% @private %% @doc receive function for cas responses containing VALUEs From e037527e28e6050d411e8f74bb1272b0538d6c25 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Tue, 10 Jul 2012 17:47:14 -0700 Subject: [PATCH 22/65] Added support for timeouts to be passed as an arg to the various merle functions --- src/merle.erl | 325 +++++++++++++++++++++++++------------------------- 1 file changed, 163 insertions(+), 162 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index 6689eb3..8836259 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -38,7 +38,7 @@ -author("Joe Williams "). -version("Version: 0.3"). --define(TIMEOUT, 5000). +-define(DEFAULT_TIMEOUT, 5000). -define(RANDOM_MAX, 65535). -define(DEFAULT_HOST, "localhost"). -define(DEFAULT_PORT, 11211). @@ -54,12 +54,15 @@ %% gen_server API -export([ stats/1, stats/2, version/1, - getkey/2, getkeys/2, delete/3, set/5, add/5, - replace/3, replace/5, cas/6, - set/3, set/4, + getkey/3, getkeys/3, getskey/3, + delete/3, delete/4, + replace/4, replace/6, + set/6, set/4, set/5, + cas/5, cas/7, + add/4, add/6, + incr/4, decr/4, incr_counter/4, incr_counter/5, getcounter/3, flushall/1, flushall/2, - verbosity/2, add/3, cas/4, getskey/2, connect/0, connect/2, delete/2, disconnect/1, - incr/3, decr/3, incr_counter/3, incr_counter/4, getcounter/2 + verbosity/2, connect/0, connect/2, disconnect/1 ]). %% gen_server callbacks @@ -99,13 +102,13 @@ flushall(Ref, Delay) -> gen_server2:call(Ref, {flushall, {Delay}}). %% @doc retrieve value based off of key -getkey(Ref, Key) when is_atom(Key) -> - getkey(Ref, atom_to_list(Key)); -getkey(Ref, Key) -> - gen_server2:call(Ref, {getkey,{Key}}). +getkey(Ref, Key, Timeout) when is_atom(Key) -> + getkey(Ref, atom_to_list(Key), Timeout); +getkey(Ref, Key, Timeout) -> + gen_server2:call(Ref, {getkey, {Key, Timeout}}). %% @doc retrieve multiple values based on keys -getkeys(Ref, Keys) when is_list(Keys) -> +getkeys(Ref, Keys, Timeout) when is_list(Keys) -> StringKeys = lists:map(fun (A) when is_atom(A) -> atom_to_list(A); @@ -113,32 +116,32 @@ getkeys(Ref, Keys) when is_list(Keys) -> S end, Keys), - gen_server2:call(Ref, {getkeys,{join_by(StringKeys, " ")}}). + gen_server2:call(Ref, {getkeys,{join_by(StringKeys, " "), Timeout}}). %% @doc used in conjunction with incr_counter to retrieve an integer value from cache -getcounter(Ref, Key) -> - case getkey(Ref, Key) of +getcounter(Ref, Key, Timeout) -> + case getkey(Ref, Key, Timeout) of {error, not_found} -> undefined; {error, _} -> undefined; Number -> list_to_integer(Number) end. %% @doc retrieve value based off of key for use with cas -getskey(Ref, Key) when is_atom(Key) -> - getskey(Ref, atom_to_list(Key)); -getskey(Ref, Key) -> - gen_server2:call(Ref, {getskey,{Key}}). +getskey(Ref, Key, Timeout) when is_atom(Key) -> + getskey(Ref, atom_to_list(Key), Timeout); +getskey(Ref, Key, Timeout) -> + gen_server2:call(Ref, {getskey,{Key, Timeout}}). %% @doc delete a key -delete(Ref, Key) -> - delete(Ref, Key, "0"). +delete(Ref, Key, Timeout) -> + delete(Ref, Key, "0", Timeout). -delete(Ref, Key, Time) when is_atom(Key) -> - delete(Ref, atom_to_list(Key), Time); -delete(Ref, Key, Time) when is_integer(Time) -> - delete(Ref, Key, integer_to_list(Time)); -delete(Ref, Key, Time) -> - gen_server2:call(Ref, {delete, {Key, Time}}). +delete(Ref, Key, Time, Timeout) when is_atom(Key) -> + delete(Ref, atom_to_list(Key), Time, Timeout); +delete(Ref, Key, Time, Timeout) when is_integer(Time) -> + delete(Ref, Key, integer_to_list(Time), Timeout); +delete(Ref, Key, Time, Timeout) -> + gen_server2:call(Ref, {delete, {Key, Time, Timeout}}). %% Time is the amount of time in seconds %% the client wishes the server to refuse @@ -163,84 +166,65 @@ delete(Ref, Key, Time) -> %% *Value* is the value you want to store. %% @doc Store a key/value pair. -set(Ref, Key, Value) -> - set(Ref, Key, "0", Value). +set(Ref, Key, Value, Timeout) -> + set(Ref, Key, "0", Value, Timeout). -set(Ref, Key, ExpTime, Value) -> +set(Ref, Key, ExpTime, Value, Timeout) -> Flag = random:uniform(?RANDOM_MAX), - set(Ref, Key, integer_to_list(Flag), ExpTime, Value). + set(Ref, Key, integer_to_list(Flag), ExpTime, Value, Timeout). -set(Ref, Key, Flag, ExpTime, Value) when is_atom(Key) -> - set(Ref, atom_to_list(Key), Flag, ExpTime, Value); -set(Ref, Key, Flag, ExpTime, Value) when is_integer(Flag) -> - set(Ref, Key, integer_to_list(Flag), ExpTime, Value); -set(Ref, Key, Flag, ExpTime, Value) when is_integer(ExpTime) -> - set(Ref, Key, Flag, integer_to_list(ExpTime), Value); -set(Ref, Key, Flag, ExpTime, Value) -> - gen_server2:call(Ref, {set, {Key, Flag, ExpTime, Value}}). +set(Ref, Key, Flag, ExpTime, Value, Timeout) when is_atom(Key) -> + set(Ref, atom_to_list(Key), Flag, ExpTime, Value, Timeout); +set(Ref, Key, Flag, ExpTime, Value, Timeout) when is_integer(Flag) -> + set(Ref, Key, integer_to_list(Flag), ExpTime, Value, Timeout); +set(Ref, Key, Flag, ExpTime, Value, Timeout) when is_integer(ExpTime) -> + set(Ref, Key, Flag, integer_to_list(ExpTime), Value, Timeout); +set(Ref, Key, Flag, ExpTime, Value, Timeout) -> + gen_server2:call(Ref, {set, {Key, Flag, ExpTime, Value, Timeout}}). %% @doc Store a key/value pair if it doesn't already exist. -add(Ref, Key, Value) -> +add(Ref, Key, Value, Timeout) -> Flag = random:uniform(?RANDOM_MAX), - add(Ref, Key, integer_to_list(Flag), "0", Value). + add(Ref, Key, integer_to_list(Flag), "0", Value, Timeout). -add(Ref, Key, Flag, ExpTime, Value) when is_atom(Key) -> - add(Ref, atom_to_list(Key), Flag, ExpTime, Value); -add(Ref, Key, Flag, ExpTime, Value) when is_integer(Flag) -> - add(Ref, Key, integer_to_list(Flag), ExpTime, Value); -add(Ref, Key, Flag, ExpTime, Value) when is_integer(ExpTime) -> - add(Ref, Key, Flag, integer_to_list(ExpTime), Value); -add(Ref, Key, Flag, ExpTime, Value) -> - gen_server2:call(Ref, {add, {Key, Flag, ExpTime, Value}}). +add(Ref, Key, Flag, ExpTime, Value, Timeout) when is_atom(Key) -> + add(Ref, atom_to_list(Key), Flag, ExpTime, Value, Timeout); +add(Ref, Key, Flag, ExpTime, Value, Timeout) when is_integer(Flag) -> + add(Ref, Key, integer_to_list(Flag), ExpTime, Value, Timeout); +add(Ref, Key, Flag, ExpTime, Value, Timeout) when is_integer(ExpTime) -> + add(Ref, Key, Flag, integer_to_list(ExpTime), Value, Timeout); +add(Ref, Key, Flag, ExpTime, Value, Timeout) -> + gen_server2:call(Ref, {add, {Key, Flag, ExpTime, Value, Timeout}}). %% @doc Replace an existing key/value pair. -replace(Ref, Key, Value) -> +replace(Ref, Key, Value, Timeout) -> Flag = random:uniform(?RANDOM_MAX), - replace(Ref, Key, integer_to_list(Flag), "0", Value). + replace(Ref, Key, integer_to_list(Flag), "0", Value, Timeout). -replace(Ref, Key, Flag, ExpTime, Value) when is_atom(Key) -> - replace(Ref, atom_to_list(Key), Flag, ExpTime, Value); -replace(Ref, Key, Flag, ExpTime, Value) when is_integer(Flag) -> - replace(Ref, Key, integer_to_list(Flag), ExpTime, Value); -replace(Ref, Key, Flag, ExpTime, Value) when is_integer(ExpTime) -> - replace(Ref, Key, Flag, integer_to_list(ExpTime), Value); -replace(Ref, Key, Flag, ExpTime, Value) -> - gen_server2:call(Ref, {replace, {Key, Flag, ExpTime, Value}}). +replace(Ref, Key, Flag, ExpTime, Value, Timeout) when is_atom(Key) -> + replace(Ref, atom_to_list(Key), Flag, ExpTime, Value, Timeout); +replace(Ref, Key, Flag, ExpTime, Value, Timeout) when is_integer(Flag) -> + replace(Ref, Key, integer_to_list(Flag), ExpTime, Value, Timeout); +replace(Ref, Key, Flag, ExpTime, Value, Timeout) when is_integer(ExpTime) -> + replace(Ref, Key, Flag, integer_to_list(ExpTime), Value, Timeout); +replace(Ref, Key, Flag, ExpTime, Value, Timeout) -> + gen_server2:call(Ref, {replace, {Key, Flag, ExpTime, Value, Timeout}}). %% @doc Store a key/value pair if possible. -cas(Ref, Key, CasUniq, Value) -> +cas(Ref, Key, CasUniq, Value, Timeout) -> Flag = random:uniform(?RANDOM_MAX), - cas(Ref, Key, integer_to_list(Flag), "0", CasUniq, Value). - -cas(Ref, Key, Flag, ExpTime, CasUniq, Value) when is_atom(Key) -> - cas(Ref, atom_to_list(Key), Flag, ExpTime, CasUniq, Value); -cas(Ref, Key, Flag, ExpTime, CasUniq, Value) when is_integer(Flag) -> - cas(Ref, Key, integer_to_list(Flag), ExpTime, CasUniq, Value); -cas(Ref, Key, Flag, ExpTime, CasUniq, Value) when is_integer(ExpTime) -> - cas(Ref, Key, Flag, integer_to_list(ExpTime), CasUniq, Value); -cas(Ref, Key, Flag, ExpTime, CasUniq, Value) when is_integer(CasUniq) -> - cas(Ref, Key, Flag, ExpTime, integer_to_list(CasUniq), Value); -cas(Ref, Key, Flag, ExpTime, CasUniq, Value) -> - gen_server2:call(Ref, {cas, {Key, Flag, ExpTime, CasUniq, Value}}). - --ifdef(DEV). -%% @doc Submit an arbitrary string to memcached using send_generic_cmd/2. This -%% is a utility function that allows a developer to experiment with the strings -%% they will submit to memcached. Memcached is intolerant of malformed input, so -%% this is not meant to be used in a serious application. Hence, it is only -%% included in merle.erl if compiled with the DEV variable defined. -%% -%% The string submitted to literal can be a multiline command; simply include -%% CRLF (<<"\r\n">> or <<10,13>>) to delimit the end of the first line. The -%% argument to literal/1 SHOULD NOT be terminated with CRLF, as this will be -%% appended by merle:send_generic_cmd/2 -%% -%% @see merle:send_generic_cmd/2 -%% @spec (string()) -> [string()] -%% @private -literal(Str) when is_binary(Str) -> - gen_server2:call(?SERVER, {literal, Str}). --endif. + cas(Ref, Key, integer_to_list(Flag), "0", CasUniq, Value, Timeout). + +cas(Ref, Key, Flag, ExpTime, CasUniq, Value, Timeout) when is_atom(Key) -> + cas(Ref, atom_to_list(Key), Flag, ExpTime, CasUniq, Value, Timeout); +cas(Ref, Key, Flag, ExpTime, CasUniq, Value, Timeout) when is_integer(Flag) -> + cas(Ref, Key, integer_to_list(Flag), ExpTime, CasUniq, Value, Timeout); +cas(Ref, Key, Flag, ExpTime, CasUniq, Value, Timeout) when is_integer(ExpTime) -> + cas(Ref, Key, Flag, integer_to_list(ExpTime), CasUniq, Value, Timeout); +cas(Ref, Key, Flag, ExpTime, CasUniq, Value, Timeout) when is_integer(CasUniq) -> + cas(Ref, Key, Flag, ExpTime, integer_to_list(CasUniq), Value, Timeout); +cas(Ref, Key, Flag, ExpTime, CasUniq, Value, Timeout) -> + gen_server2:call(Ref, {cas, {Key, Flag, ExpTime, CasUniq, Value, Timeout}}). %% @doc Add a key to memcached which can be used as a counter via incr/decr. %% Currently, incr_counter/2 checks via incr to see if a counter exists before @@ -258,26 +242,36 @@ literal(Str) when is_binary(Str) -> %% of space allocated for an integer value. FFFFFFFF is a negative value, not %% acceptable by memcached, and so is coerced to zero. Hey presto! A counter. %% -incr_counter(Ref, Key, Value) -> - incr_counter(Ref, Key, Value, "0"). -incr_counter(Ref, Key, Value, ExpTime) when is_integer(ExpTime) -> - incr_counter(Ref, Key, Value, integer_to_list(ExpTime)); +-define(MAX_INCR_TRIES, 2). + +incr_counter(Ref, Key, Value, Timeout) -> + incr_counter(Ref, Key, Value, "0", Timeout). -incr_counter(Ref, Key, Value, ExpTime) -> +incr_counter(Ref, Key, Value, ExpTime, Timeout) when is_integer(ExpTime) -> + incr_counter(Ref, Key, Value, integer_to_list(ExpTime), Timeout); + +incr_counter(Ref, Key, Value, ExpTime, Timeout) -> + incr_counter(Ref, Key, Value, ExpTime, Timeout, 0). + +incr_counter(_Ref, _Key, _Value, _ExpTime, _Timeout, ?MAX_INCR_TRIES) -> + not_stored; +incr_counter(Ref, Key, Value, ExpTime, Timeout, NumTry) -> Flag = random:uniform(?RANDOM_MAX), - case incr(Ref, Key, Value) of + case incr(Ref, Key, Value, Timeout) of not_found -> - case gen_server2:call(Ref, {addcounter, {Key, integer_to_list(Flag), ExpTime}}) of + case gen_server2:call(Ref, {addcounter, {Key, integer_to_list(Flag), ExpTime, Timeout}}) of {ok, stored} -> - incr(Ref, Key, Value); - {error, not_stored} -> - not_stored; + incr(Ref, Key, Value, Timeout); + {error, _} -> + incr_counter(Ref, Key, Value, ExpTime, Timeout, NumTry+1); X -> X end; + {error, _} -> + incr_counter(Ref, Key, Value, ExpTime, Timeout, NumTry+1); Result -> Result end. - + %% @doc Interface to the incr method in memcached's protocol. %% @@ -287,9 +281,10 @@ incr_counter(Ref, Key, Value, ExpTime) -> %% %% @spec incr(Key::list(),Value::integer()) -> (not_found | {ok,NewValue::integer()}) %% @see merle:decr/2 -incr(Ref, Key,Value) when is_integer(Value) -> - case gen_server2:call(Ref, {incr, {Key, integer_to_list(Value)}}) of +incr(Ref, Key, Value, Timeout) when is_integer(Value) -> + case gen_server2:call(Ref, {incr, {Key, integer_to_list(Value), Timeout}}) of {error, not_found} -> not_found; + {error, Error} -> {error, Error}; Line -> {ok, [IntegerString], []} = io_lib:fread("~s\r\n", binary_to_list(Line)), {ok, list_to_integer(IntegerString)} @@ -307,8 +302,8 @@ incr(Ref, Key,Value) when is_integer(Value) -> %% %% @spec decr(Key::list(),Value::integer()) -> (not_found | {ok,NewValue::integer()}) %% @see merle:incr/2 -decr(Ref, Key, Value) when is_integer(Value) -> - case gen_server2:call(Ref, {decr, {Key, integer_to_list(Value)}}) of +decr(Ref, Key, Value, Timeout) when is_integer(Value) -> + case gen_server2:call(Ref, {decr, {Key, integer_to_list(Value), Timeout}}) of {error, not_found} -> not_found; [Str] -> {ok, list_to_integer(Str)} end. @@ -335,48 +330,49 @@ init([Host, Port]) -> gen_tcp:connect(Host, Port, ?TCP_OPTS_ACTIVE). handle_call({stats}, _From, Socket) -> - Reply = send_stats_cmd(Socket, iolist_to_binary([<<"stats">>])), + Reply = send_stats_cmd(Socket, iolist_to_binary([<<"stats">>]), ?DEFAULT_TIMEOUT), {reply, Reply, Socket}; handle_call({stats, {Args}}, _From, Socket) -> - Reply = send_stats_cmd(Socket, iolist_to_binary([<<"stats ">>, Args])), + Reply = send_stats_cmd(Socket, iolist_to_binary([<<"stats ">>, Args]), ?DEFAULT_TIMEOUT), {reply, Reply, Socket}; handle_call({version}, _From, Socket) -> - Reply = send_generic_cmd(Socket, iolist_to_binary([<<"version">>])), + Reply = send_generic_cmd(Socket, iolist_to_binary([<<"version">>]), ?DEFAULT_TIMEOUT), {reply, Reply, Socket}; handle_call({verbosity, {Args}}, _From, Socket) -> - Reply = send_generic_cmd(Socket, iolist_to_binary([<<"verbosity ">>, Args])), + Reply = send_generic_cmd(Socket, iolist_to_binary([<<"verbosity ">>, Args]), ?DEFAULT_TIMEOUT), {reply, Reply, Socket}; handle_call({flushall}, _From, Socket) -> - Reply = send_generic_cmd(Socket, iolist_to_binary([<<"flush_all">>])), + Reply = send_generic_cmd(Socket, iolist_to_binary([<<"flush_all">>]), ?DEFAULT_TIMEOUT), {reply, Reply, Socket}; handle_call({flushall, {Delay}}, _From, Socket) -> - Reply = send_generic_cmd(Socket, iolist_to_binary([<<"flush_all ">>, Delay])), + Reply = send_generic_cmd(Socket, iolist_to_binary([<<"flush_all ">>, Delay]), ?DEFAULT_TIMEOUT), {reply, Reply, Socket}; -handle_call({getkey, {Key}}, _From, Socket) -> - Reply = send_get_cmd(Socket, iolist_to_binary([<<"get ">>, Key])), +handle_call({getkey, {Key, Timeout}}, _From, Socket) -> + Reply = send_get_cmd(Socket, iolist_to_binary([<<"get ">>, Key]), Timeout), {reply, Reply, Socket}; -handle_call({getkeys, {Keys}}, _From, Socket) -> - Reply = send_multi_get_cmd(Socket, iolist_to_binary([<<"get ">>, Keys])), +handle_call({getkeys, {Keys, Timeout}}, _From, Socket) -> + Reply = send_multi_get_cmd(Socket, iolist_to_binary([<<"get ">>, Keys]), Timeout), {reply, Reply, Socket}; -handle_call({getskey, {Key}}, _From, Socket) -> - Reply = send_gets_cmd(Socket, iolist_to_binary([<<"gets ">>, Key])), +handle_call({getskey, {Key, Timeout}}, _From, Socket) -> + Reply = send_gets_cmd(Socket, iolist_to_binary([<<"gets ">>, Key]), Timeout), {reply, [Reply], Socket}; -handle_call({delete, {Key, Time}}, _From, Socket) -> +handle_call({delete, {Key, Time, Timeout}}, _From, Socket) -> Reply = send_generic_cmd( Socket, - iolist_to_binary([<<"delete ">>, Key, <<" ">>, Time]) + iolist_to_binary([<<"delete ">>, Key, <<" ">>, Time]), + Timeout ), {reply, Reply, Socket}; -handle_call({set, {Key, Flag, ExpTime, Value}}, _From, Socket) -> +handle_call({set, {Key, Flag, ExpTime, Value, Timeout}}, _From, Socket) -> Bin = term_to_binary(Value), Bytes = integer_to_list(size(Bin)), Reply = send_storage_cmd( @@ -384,14 +380,15 @@ handle_call({set, {Key, Flag, ExpTime, Value}}, _From, Socket) -> iolist_to_binary([ <<"set ">>, Key, <<" ">>, Flag, <<" ">>, ExpTime, <<" ">>, Bytes ]), - Bin + Bin, + Timeout ), {reply, Reply, Socket}; %% special clause to add a counter to memcached instead of serialized %% erlang data (literal 0xFFFFFFFF instead of the erlang bitstring %% <<131,98,255,255,255,255,255,255,255,255,0>>) -handle_call({addcounter, {Key, Flag, ExpTime}}, _From, Socket) -> +handle_call({addcounter, {Key, Flag, ExpTime, Timeout}}, _From, Socket) -> Bin = <<"0000">>, Bytes = <<"4">>, Reply = send_storage_cmd( @@ -399,11 +396,12 @@ handle_call({addcounter, {Key, Flag, ExpTime}}, _From, Socket) -> iolist_to_binary([ <<"set ">>, Key, <<" ">>, Flag, <<" ">>, ExpTime, <<" ">>, Bytes ]), - Bin + Bin, + Timeout ), {reply, Reply, Socket}; -handle_call({add, {Key, Flag, ExpTime, Value}}, _From, Socket) -> +handle_call({add, {Key, Flag, ExpTime, Value, Timeout}}, _From, Socket) -> Bin = term_to_binary(Value), Bytes = integer_to_list(size(Bin)), Reply = send_storage_cmd( @@ -411,11 +409,12 @@ handle_call({add, {Key, Flag, ExpTime, Value}}, _From, Socket) -> iolist_to_binary([ <<"add ">>, Key, <<" ">>, Flag, <<" ">>, ExpTime, <<" ">>, Bytes ]), - Bin + Bin, + Timeout ), {reply, Reply, Socket}; -handle_call({replace, {Key, Flag, ExpTime, Value}}, _From, Socket) -> +handle_call({replace, {Key, Flag, ExpTime, Value, Timeout}}, _From, Socket) -> Bin = term_to_binary(Value), Bytes = integer_to_list(size(Bin)), Reply = send_storage_cmd( @@ -424,11 +423,12 @@ handle_call({replace, {Key, Flag, ExpTime, Value}}, _From, Socket) -> <<"replace ">>, Key, <<" ">>, Flag, <<" ">>, ExpTime, <<" ">>, Bytes ]), - Bin + Bin, + Timeout ), {reply, Reply, Socket}; -handle_call({cas, {Key, Flag, ExpTime, CasUniq, Value}}, _From, Socket) -> +handle_call({cas, {Key, Flag, ExpTime, CasUniq, Value, Timeout}}, _From, Socket) -> Bin = term_to_binary(Value), Bytes = integer_to_list(size(Bin)), Reply = send_storage_cmd( @@ -437,19 +437,20 @@ handle_call({cas, {Key, Flag, ExpTime, CasUniq, Value}}, _From, Socket) -> <<"cas ">>, Key, <<" ">>, Flag, <<" ">>, ExpTime, <<" ">>, Bytes, <<" ">>, CasUniq ]), - Bin + Bin, + Timeout ), {reply, Reply, Socket}; %% Added by Jeremy D. Acord, March 2012 -handle_call({incr, {Key, Value}}, _From, Socket) when is_list(Value) -> +handle_call({incr, {Key, Value, Timeout}}, _From, Socket) when is_list(Value) -> CMD = iolist_to_binary([<<"incr ">>,Key,<<" ">>,Value]), - Reply = send_generic_cmd(Socket, CMD), + Reply = send_generic_cmd(Socket, CMD, Timeout), {reply, Reply, Socket}; -handle_call({decr, {Key, Value}}, _From, Socket) when is_list(Value) -> +handle_call({decr, {Key, Value, Timeout}}, _From, Socket) when is_list(Value) -> CMD = iolist_to_binary([<<"decr ">>,Key,<<" ">>,Value]), - Reply = send_generic_cmd(Socket, CMD), + Reply = send_generic_cmd(Socket, CMD, Timeout), {reply, Reply, Socket}. %% @private @@ -476,32 +477,32 @@ terminate(_Reason, Socket) -> %% @private %% @doc send_stats_cmd/2 function for stats get -send_stats_cmd(Socket, Cmd) -> +send_stats_cmd(Socket, Cmd, Timeout) -> gen_tcp:send(Socket, <>), - Reply = recv_stats(), + Reply = recv_stats(Timeout), Reply. %% @private %% @doc send_generic_cmd/2 function for simple informational and deletion commands -send_generic_cmd(Socket, Cmd) -> +send_generic_cmd(Socket, Cmd, Timeout) -> gen_tcp:send(Socket, <>), - Reply = recv_simple_reply(), + Reply = recv_simple_reply(Timeout), Reply. %% @private %% @doc send_storage_cmd/3 funtion for storage commands -send_storage_cmd(Socket, Cmd, Value) -> +send_storage_cmd(Socket, Cmd, Value, Timeout) -> gen_tcp:send(Socket, <>), gen_tcp:send(Socket, <>), - Reply = recv_simple_reply(), + Reply = recv_simple_reply(Timeout), Reply. %% @private %% @doc send_get_cmd/2 function for retreival commands -send_get_cmd(Socket, Cmd) -> +send_get_cmd(Socket, Cmd, Timeout) -> inet:setopts(Socket, ?TCP_OPTS_LINE), gen_tcp:send(Socket, <>), - Reply = case recv_complex_get_reply(Socket) of + Reply = case recv_complex_get_reply(Socket, Timeout) of [{_, Value}] -> {ok, Value}; [] -> {error, not_found}; {error, Error} -> {error, Error} @@ -509,10 +510,10 @@ send_get_cmd(Socket, Cmd) -> inet:setopts(Socket, ?TCP_OPTS_ACTIVE), Reply. -send_multi_get_cmd(Socket, Cmd) -> +send_multi_get_cmd(Socket, Cmd, Timeout) -> inet:setopts(Socket, ?TCP_OPTS_LINE), gen_tcp:send(Socket, <>), - Reply = case recv_complex_get_reply(Socket) of + Reply = case recv_complex_get_reply(Socket, Timeout) of {error, Error} -> {error, Error}; R -> {ok, R} end, @@ -521,21 +522,21 @@ send_multi_get_cmd(Socket, Cmd) -> %% @private %% @doc send_gets_cmd/2 function for cas retreival commands -send_gets_cmd(Socket, Cmd) -> +send_gets_cmd(Socket, Cmd, Timeout) -> gen_tcp:send(Socket, <>), - Reply = recv_complex_gets_reply(Socket), + Reply = recv_complex_gets_reply(Socket, Timeout), Reply. %% @private %% {active, once} is overkill here, but don't worry to much on optimize this method -recv_stats() -> - case do_recv_stats() of +recv_stats(Timeout) -> + case do_recv_stats(Timeout) of timeout -> {error, timeout}; Stats -> {ok, Stats} end. -do_recv_stats() -> +do_recv_stats(Timeout) -> receive {tcp, Socket, <<"END\r\n">>} -> inet:setopts(Socket, ?TCP_OPTS_ACTIVE), @@ -543,19 +544,19 @@ do_recv_stats() -> {tcp, Socket, Data} -> {ok, [Field, Value], []} = io_lib:fread("STAT ~s ~s \r\n", binary_to_list(Data)), inet:setopts(Socket, ?TCP_OPTS_ACTIVE), - [{Field, Value} | do_recv_stats()] - after ?TIMEOUT -> + [{Field, Value} | do_recv_stats(Timeout)] + after Timeout -> timeout end. %% @doc receive function for simple responses (not containing VALUEs) -recv_simple_reply() -> +recv_simple_reply(Timeout) -> receive {tcp, Socket, Data} -> inet:setopts(Socket, ?TCP_OPTS_ACTIVE), parse_simple_response_line(Data); {error, closed} -> connection_closed - after ?TIMEOUT -> {error, timeout} + after Timeout -> {error, timeout} end. parse_simple_response_line(<<"OK", _B/binary>>) -> ok; parse_simple_response_line(<<"ERROR", _B/binary>> =L ) -> {error, L}; @@ -572,17 +573,17 @@ parse_simple_response_line(Line) -> Line. %% @private %% @doc receive function for respones containing VALUEs -recv_complex_get_reply(Socket) -> - recv_complex_get_reply(Socket, []). -recv_complex_get_reply(Socket, Accum) -> - case gen_tcp:recv(Socket, 0, ?TIMEOUT) of +recv_complex_get_reply(Socket, Timeout) -> + recv_complex_get_reply(Socket, Timeout, []). +recv_complex_get_reply(Socket, Timeout, Accum) -> + case gen_tcp:recv(Socket, 0, Timeout) of {ok, <<"END\r\n">>} -> Accum; {ok, Data} -> {ok,[_,Key,_,Bytes], []} = io_lib:fread("~s ~s ~u ~u\r\n", binary_to_list(Data)), inet:setopts(Socket, ?TCP_OPTS_RAW), - case gen_tcp:recv(Socket, Bytes+2, ?TIMEOUT) of + case gen_tcp:recv(Socket, Bytes+2, Timeout) of {ok, <>} -> inet:setopts(Socket, ?TCP_OPTS_LINE), recv_complex_get_reply(Socket, @@ -597,7 +598,7 @@ recv_complex_get_reply(Socket, Accum) -> %% @private %% @doc receive function for cas responses containing VALUEs -recv_complex_gets_reply(Socket) -> +recv_complex_gets_reply(Socket, Timeout) -> receive %% For receiving get responses where the key does not exist {tcp, Socket, <<"END\r\n">>} -> @@ -608,18 +609,18 @@ recv_complex_gets_reply(Socket) -> %% Reply format <<"VALUE SOMEKEY FLAG BYTES\r\nSOMEVALUE\r\nEND\r\n">> Parse = io_lib:fread("~s ~s ~u ~u ~u\r\n", binary_to_list(Data)), {ok,[_,_,_,Bytes,CasUniq], []} = Parse, - Reply = get_data(Socket, Bytes), + Reply = get_data(Socket, Timeout, Bytes), {ok, [CasUniq, Reply]}; {error, closed} -> {error, connection_closed} - after ?TIMEOUT -> {error, timeout} + after Timeout -> {error, timeout} end. %% @private %% @doc recieve loop to get all data -get_data(Socket, Bytes) -> +get_data(Socket, Timeout, Bytes) -> inet:setopts(Socket, ?TCP_OPTS_RAW), - {ok, Data} = gen_tcp:recv(Socket, Bytes+7, ?TIMEOUT), + {ok, Data} = gen_tcp:recv(Socket, Bytes+7, Timeout), <> = Data, inet:setopts(Socket, ?TCP_OPTS_ACTIVE), binary_to_term(Value). From a5e2d5fe37c70cb14b66b1ebfd3c48e209050b77 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 11 Jul 2012 12:14:04 -0700 Subject: [PATCH 23/65] A few changes to the merle parsing of a get response... have to support more than just erlang binary term storage in order to support inc/dec operations --- src/merle.erl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index 8836259..b679823 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -123,7 +123,7 @@ getcounter(Ref, Key, Timeout) -> case getkey(Ref, Key, Timeout) of {error, not_found} -> undefined; {error, _} -> undefined; - Number -> list_to_integer(Number) + {ok, NumberBin} -> list_to_integer(binary_to_list(NumberBin)) end. %% @doc retrieve value based off of key for use with cas @@ -586,8 +586,15 @@ recv_complex_get_reply(Socket, Timeout, Accum) -> case gen_tcp:recv(Socket, Bytes+2, Timeout) of {ok, <>} -> inet:setopts(Socket, ?TCP_OPTS_LINE), - recv_complex_get_reply(Socket, - [{Key, binary_to_term(Value)}|Accum]); + + FinalValue = + try binary_to_term(Value) + catch + _:_ -> Value + end, + + recv_complex_get_reply(Socket, Timeout, [{Key, FinalValue}|Accum]); + {error, Error} -> {error, Error} end; From a4fda86ee889c2df3ac10423722dba3bfc9cc3e4 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Tue, 17 Jul 2012 15:07:51 -0700 Subject: [PATCH 24/65] Fixed issue with using merle with elasticache... have to trim values returned --- src/merle.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/merle.erl b/src/merle.erl index b679823..191f79d 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -123,7 +123,7 @@ getcounter(Ref, Key, Timeout) -> case getkey(Ref, Key, Timeout) of {error, not_found} -> undefined; {error, _} -> undefined; - {ok, NumberBin} -> list_to_integer(binary_to_list(NumberBin)) + {ok, NumberBin} -> list_to_integer(string:strip(binary_to_list(NumberBin))) end. %% @doc retrieve value based off of key for use with cas @@ -579,6 +579,8 @@ recv_complex_get_reply(Socket, Timeout, Accum) -> case gen_tcp:recv(Socket, 0, Timeout) of {ok, <<"END\r\n">>} -> Accum; + {ok, <<"NOT_FOUND", _B/binary>>} -> + {error, not_found}; {ok, Data} -> {ok,[_,Key,_,Bytes], []} = io_lib:fread("~s ~s ~u ~u\r\n", binary_to_list(Data)), From e619039261ef5d9fd0a1734771b98a2b777fcb6f Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Tue, 17 Jul 2012 16:03:25 -0700 Subject: [PATCH 25/65] Fixed a bug with socket timeouts --- src/merle.erl | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index 191f79d..c63045a 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -582,24 +582,28 @@ recv_complex_get_reply(Socket, Timeout, Accum) -> {ok, <<"NOT_FOUND", _B/binary>>} -> {error, not_found}; {ok, Data} -> - {ok,[_,Key,_,Bytes], []} = - io_lib:fread("~s ~s ~u ~u\r\n", binary_to_list(Data)), + case io_lib:fread("~s ~s ~u ~u\r\n", binary_to_list(Data)) of + {ok,[_,Key,_,Bytes], []} -> inet:setopts(Socket, ?TCP_OPTS_RAW), - case gen_tcp:recv(Socket, Bytes+2, Timeout) of - {ok, <>} -> - inet:setopts(Socket, ?TCP_OPTS_LINE), + case gen_tcp:recv(Socket, Bytes+2, Timeout) of + {ok, <>} -> + inet:setopts(Socket, ?TCP_OPTS_LINE), - FinalValue = - try binary_to_term(Value) - catch - _:_ -> Value - end, + FinalValue = + try binary_to_term(Value) + catch + _:_ -> Value + end, - recv_complex_get_reply(Socket, Timeout, [{Key, FinalValue}|Accum]); + recv_complex_get_reply(Socket, Timeout, [{Key, FinalValue}|Accum]); - {error, Error} -> - {error, Error} - end; + {error, Error} -> + {error, Error} + end; + ParsedData -> + log4erl:error("Memcache socket unexpected read ~p", [ParsedData]), + {error, timed_out} + end; {error, Error} -> {error, Error} end. From 48fefffc015928884c822162f56d8930b402a492 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 20 Jul 2012 11:38:56 -0700 Subject: [PATCH 26/65] Added logging for better debugging of memcache errors --- src/merle.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/merle.erl b/src/merle.erl index c63045a..188bed2 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -122,7 +122,9 @@ getkeys(Ref, Keys, Timeout) when is_list(Keys) -> getcounter(Ref, Key, Timeout) -> case getkey(Ref, Key, Timeout) of {error, not_found} -> undefined; - {error, _} -> undefined; + {error, Error} -> + log4erl:error("Encountered error while getting counter from memcache: ~p", [Error]), + undefined; {ok, NumberBin} -> list_to_integer(string:strip(binary_to_list(NumberBin))) end. From a9e8ecd439ce0f04da2b9cecffa731908a3e7979 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Mon, 23 Jul 2012 18:15:44 -0700 Subject: [PATCH 27/65] Fixed bug with merle timeouts. When these occur it's best to kill the merle process and let the merle_watcher restart it via the supervisor. --- src/merle.erl | 67 ++++++++++++++++++++++--------------------- src/merle_watcher.erl | 4 ++- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index 188bed2..dd39d47 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -105,8 +105,8 @@ flushall(Ref, Delay) -> getkey(Ref, Key, Timeout) when is_atom(Key) -> getkey(Ref, atom_to_list(Key), Timeout); getkey(Ref, Key, Timeout) -> - gen_server2:call(Ref, {getkey, {Key, Timeout}}). - + gen_server2:call(Ref, {getkey, {Key, Timeout}}). + %% @doc retrieve multiple values based on keys getkeys(Ref, Keys, Timeout) when is_list(Keys) -> StringKeys = lists:map(fun @@ -121,10 +121,7 @@ getkeys(Ref, Keys, Timeout) when is_list(Keys) -> %% @doc used in conjunction with incr_counter to retrieve an integer value from cache getcounter(Ref, Key, Timeout) -> case getkey(Ref, Key, Timeout) of - {error, not_found} -> undefined; - {error, Error} -> - log4erl:error("Encountered error while getting counter from memcache: ~p", [Error]), - undefined; + {error, _} -> undefined; {ok, NumberBin} -> list_to_integer(string:strip(binary_to_list(NumberBin))) end. @@ -329,6 +326,7 @@ start_link(Host, Port) -> %% @private init([Host, Port]) -> + log4erl:info("Socket initialized!"), gen_tcp:connect(Host, Port, ?TCP_OPTS_ACTIVE). handle_call({stats}, _From, Socket) -> @@ -474,6 +472,7 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %% @private %% @doc Closes the socket terminate(_Reason, Socket) -> + log4erl:info("Socket terminated!"), gen_tcp:close(Socket), ok. @@ -507,8 +506,11 @@ send_get_cmd(Socket, Cmd, Timeout) -> Reply = case recv_complex_get_reply(Socket, Timeout) of [{_, Value}] -> {ok, Value}; [] -> {error, not_found}; - {error, Error} -> {error, Error} - end, + {error, Error} -> + log4erl:error("Encountered error from memcache; killing connection now: ~p", [Error]), + erlang:exit(Error), + {error, Error} + end, inet:setopts(Socket, ?TCP_OPTS_ACTIVE), Reply. @@ -557,8 +559,13 @@ recv_simple_reply(Timeout) -> inet:setopts(Socket, ?TCP_OPTS_ACTIVE), parse_simple_response_line(Data); {error, closed} -> + log4erl:error("Encountered error while receiving simple reply from memcache; killing connection now."), + erlang:exit(connection_closed), connection_closed - after Timeout -> {error, timeout} + after Timeout -> + log4erl:error("Encountered timeout while receiving simple reply from memcache; killing connection now."), + erlang:exit(timeout), + {error, timeout} end. parse_simple_response_line(<<"OK", _B/binary>>) -> ok; parse_simple_response_line(<<"ERROR", _B/binary>> =L ) -> {error, L}; @@ -581,31 +588,25 @@ recv_complex_get_reply(Socket, Timeout, Accum) -> case gen_tcp:recv(Socket, 0, Timeout) of {ok, <<"END\r\n">>} -> Accum; - {ok, <<"NOT_FOUND", _B/binary>>} -> - {error, not_found}; {ok, Data} -> - case io_lib:fread("~s ~s ~u ~u\r\n", binary_to_list(Data)) of - {ok,[_,Key,_,Bytes], []} -> - inet:setopts(Socket, ?TCP_OPTS_RAW), - case gen_tcp:recv(Socket, Bytes+2, Timeout) of - {ok, <>} -> - inet:setopts(Socket, ?TCP_OPTS_LINE), - - FinalValue = - try binary_to_term(Value) - catch - _:_ -> Value - end, - - recv_complex_get_reply(Socket, Timeout, [{Key, FinalValue}|Accum]); - - {error, Error} -> - {error, Error} - end; - ParsedData -> - log4erl:error("Memcache socket unexpected read ~p", [ParsedData]), - {error, timed_out} - end; + log4erl:info("Memcache data: ~p", [Data]), + {ok,[_,Key,_,Bytes], []} = io_lib:fread("~s ~s ~u ~u\r\n", binary_to_list(Data)), + inet:setopts(Socket, ?TCP_OPTS_RAW), + case gen_tcp:recv(Socket, Bytes+2, Timeout) of + {ok, <>} -> + inet:setopts(Socket, ?TCP_OPTS_LINE), + + FinalValue = + try binary_to_term(Value) + catch + _:_ -> Value + end, + + recv_complex_get_reply(Socket, Timeout, [{Key, FinalValue}|Accum]); + + {error, Error} -> + {error, Error} + end; {error, Error} -> {error, Error} end. diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index de8a553..ebedc44 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -2,7 +2,7 @@ -export([start_link/1, init/1, handle_call/3, handle_info/2, handle_cast/2, terminate/2]). --define(RESTART_INTERVAL, 15 * 1000). %% retry each 5 seconds. +-define(RESTART_INTERVAL, 5000). %% retry each 5 seconds. -record(state, {mcd_pid, host, @@ -12,6 +12,7 @@ start_link([Host, Port]) -> gen_server:start_link(?MODULE, [Host, Port], []). init([Host, Port]) -> + log4erl:info("Merle watcher initialized!"), erlang:process_flag(trap_exit, true), self() ! timeout, {ok, #state{mcd_pid = undefined, host = Host, port = Port}}. @@ -59,6 +60,7 @@ handle_info(_Info, S) -> handle_cast(_Cast, S) -> {noreply, S}. terminate(_Reason, _S) -> + log4erl:info("Merle watcher terminated!"), ok. From 6f58759a55c1b5c50dc8c53d889474f09f4284e8 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 27 Jul 2012 15:55:35 -0700 Subject: [PATCH 28/65] Ripped out extraneous loggging statement left over from debugging --- src/merle.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/merle.erl b/src/merle.erl index dd39d47..49abe33 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -589,7 +589,6 @@ recv_complex_get_reply(Socket, Timeout, Accum) -> {ok, <<"END\r\n">>} -> Accum; {ok, Data} -> - log4erl:info("Memcache data: ~p", [Data]), {ok,[_,Key,_,Bytes], []} = io_lib:fread("~s ~s ~u ~u\r\n", binary_to_list(Data)), inet:setopts(Socket, ?TCP_OPTS_RAW), case gen_tcp:recv(Socket, Bytes+2, Timeout) of From 0fc016fce64a170bb87f166f9bce7340c4785b46 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 15 Aug 2012 15:50:03 -0700 Subject: [PATCH 29/65] Added a round_robin option for connection pooling --- src/local_pg2.erl | 31 +++++++++++++++++-- src/merle_cluster.erl | 44 ++++++++++++++------------- src/merle_watcher.erl | 69 ++++++++++++++++++++++++------------------- 3 files changed, 92 insertions(+), 52 deletions(-) diff --git a/src/local_pg2.erl b/src/local_pg2.erl index 48b8b72..40c2921 100644 --- a/src/local_pg2.erl +++ b/src/local_pg2.erl @@ -1,7 +1,7 @@ -module(local_pg2). %% Basically the same functionality than pg2, but process groups are local rather than global. --export([create/1, delete/1, join/2, leave/2, get_members/1, get_closest_pid/1, which_groups/0]). +-export([create/1, delete/1, join/2, leave/2, get_members/1, get_closest_pid/2, which_groups/0]). -export([start/0, start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). @@ -21,6 +21,7 @@ create(Name) -> _ -> ok end. + delete(Name) -> ensure_started(), gen_server:call(?MODULE, {delete, Name}). @@ -53,8 +54,9 @@ which_groups() -> [K || {K, _Members} <- ets:tab2list(?TABLE)]. -get_closest_pid(Name) -> +get_closest_pid(random, Name) -> ensure_started(), + case ets:lookup(?TABLE, Name) of [] -> {error, {no_process, Name}}; @@ -63,6 +65,19 @@ get_closest_pid(Name) -> %% http://lethain.com/entry/2009/sep/12/load-balancing-across-erlang-process-groups/ {_, _, X} = erlang:now(), lists:nth((X rem length(Members)) +1, Members) + end; + +get_closest_pid(round_robin, Name) -> + ensure_started(), + + % Get the round robin index + RoundRobinIndex = ets:update_counter(?TABLE, {Name, rr_index}, 1), + + case ets:lookup(?TABLE, Name) of + [] -> + {error, {no_process, Name}}; + [{Name, Members}] -> + lists:nth(RoundRobinIndex rem length(Members), Members) end. @@ -70,9 +85,11 @@ init([]) -> process_flag(trap_exit, true), ets:new(?TABLE, [set, protected, named_table]), {ok, []}. + handle_call({create, Name}, _From, S) -> case ets:lookup(?TABLE, Name) of [] -> + ets:insert(?TABLE, {{Name, rr_index}, 0}), ets:insert(?TABLE, {Name, []}); _ -> ok @@ -84,8 +101,15 @@ handle_call({join, Name, Pid}, _From, S) -> [] -> {reply, no_such_group, S}; [{Name, Members}] -> + + % NOTE: skip one index since we are about to grow the list, this prevents collisions + ets:update_counter(?TABLE, {Name, rr_index}, 1), + + % insert new pid into the table ets:insert(?TABLE, {Name, [Pid | Members]}), + link(Pid), + %%TODO: add pid to linked ones on state.. {reply, ok, S} end; @@ -113,8 +137,10 @@ handle_cast(_Cast, S) -> {noreply, S}. handle_info({'EXIT', Pid, _} , S) -> + log4erl:error("Caught local_pg2 EXIT... leaving pg"), del_member(Pid), {noreply, S}; + handle_info(_Info, S) -> {noreply, S}. @@ -122,6 +148,7 @@ terminate(_Reason, _S) -> ets:delete(?TABLE), %%do not unlink, if this fails, dangling processes should be killed ok. + %%%----------------------------------------------------------------- %%% Internal functions %%%----------------------------------------------------------------- diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 3242291..ea1dfbd 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -9,23 +9,27 @@ index_map(F, List) -> configure(MemcachedHosts, ConnectionsPerHost) -> SortedMemcachedHosts = lists:sort(MemcachedHosts), DynModuleBegin = "-module(merle_cluster_dynamic). - -export([get_server/1]). - get_server(ClusterKey) -> N = erlang:phash2(ClusterKey, ~p), - do_get_server(N). - ", - DynModuleMap = "do_get_server(~p) -> {\"~s\", ~p}; ", - DynModuleEnd = "do_get_server(_N) -> throw({invalid_server_slot, _N}).\n", - ModuleString = lists:flatten([ - io_lib:format(DynModuleBegin, [length(SortedMemcachedHosts)]), - index_map(fun([Host, Port], I) -> - io_lib:format(DynModuleMap, [I-1, Host, Port]) - end, SortedMemcachedHosts), - DynModuleEnd - ]), - {M, B} = dynamic_compile:from_string(ModuleString), - code:load_binary(M, "", B), - lists:foreach(fun([Host, Port]) -> - lists:foreach(fun(_) -> - supervisor:start_child(merle_sup, [[Host, Port]]) - end, lists:seq(1, ConnectionsPerHost)) - end, SortedMemcachedHosts). + -export([get_server/1]). + get_server(ClusterKey) -> N = erlang:phash2(ClusterKey, ~p), + do_get_server(N).", + DynModuleMap = "do_get_server(~p) -> {\"~s\", ~p}; ", + DynModuleEnd = "do_get_server(_N) -> throw({invalid_server_slot, _N}).\n", + ModuleString = lists:flatten([ + io_lib:format(DynModuleBegin, [length(SortedMemcachedHosts)]), + index_map(fun([Host, Port], I) -> io_lib:format(DynModuleMap, [I-1, Host, Port]) end, SortedMemcachedHosts), + DynModuleEnd + ]), + {M, B} = dynamic_compile:from_string(ModuleString), + code:load_binary(M, "", B), + + lists:foreach( + fun([Host, Port]) -> + lists:foreach( + fun(_) -> + supervisor:start_child(merle_sup, [[Host, Port]]) + end, + lists:seq(1, ConnectionsPerHost) + ) + end, + SortedMemcachedHosts + ). diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index ebedc44..3a24e72 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -2,7 +2,7 @@ -export([start_link/1, init/1, handle_call/3, handle_info/2, handle_cast/2, terminate/2]). --define(RESTART_INTERVAL, 5000). %% retry each 5 seconds. +-define(RESTART_INTERVAL, 1000). %% retry each 5 seconds. -record(state, {mcd_pid, host, @@ -19,49 +19,58 @@ init([Host, Port]) -> handle_call(_Call, _From, S) -> {reply, ok, S}. + handle_info('timeout', #state{mcd_pid = undefined, host = Host, port = Port} = State) -> - error_logger:info_report([{memcached, connecting}, {host, Host}, {port, Port}]), - case merle:connect(Host, Port) of + error_logger:info_report([{memcached, connecting}, {host, Host}, {port, Port}]), + + case merle:connect(Host, Port) of {ok, Pid} -> - local_pg2:create({Host, Port}), - local_pg2:join({Host, Port}, Pid), - {noreply, State#state{mcd_pid = Pid}}; + local_pg2:create({Host, Port}), + local_pg2:join({Host, Port}, Pid), + {noreply, State#state{mcd_pid = Pid}}; + {error, Reason} -> - receive - {'EXIT', _ , _} -> - ok - after - 2000 -> - ok - end, - error_logger:error_report([memcached_not_started, - {reason, Reason}, - {host, Host}, - {port, Port}, - {restarting_in, ?RESTART_INTERVAL}]), + receive + {'EXIT', _ , _} -> ok + after 2000 -> + ok + end, + + error_logger:error_report([memcached_not_started, + {reason, Reason}, + {host, Host}, + {port, Port}, + {restarting_in, ?RESTART_INTERVAL}] + ), + {noreply, State, ?RESTART_INTERVAL} end; handle_info({'EXIT', Pid, Reason}, #state{mcd_pid = Pid} = S) -> error_logger:error_report([{memcached_crashed, Pid}, - {reason, Reason}, - {host, S#state.host}, - {port, S#state.port}, - {restarting_in, ?RESTART_INTERVAL}]), + {reason, Reason}, + {host, S#state.host}, + {port, S#state.port}, + {restarting_in, ?RESTART_INTERVAL}]), + + % TODO: do we need this? + %local_pg2:leave({Host, Port}, Pid), + {noreply, S#state{mcd_pid = undefined}, ?RESTART_INTERVAL}; + handle_info(_Info, S) -> error_logger:warning_report([{merle_watcher, self()}, {unknown_info, _Info}]), + case S#state.mcd_pid of - undefined -> - {noreply, S, ?RESTART_INTERVAL}; - _ -> - {noreply, S} + undefined -> + {noreply, S, ?RESTART_INTERVAL}; + _ -> + {noreply, S} end. + handle_cast(_Cast, S) -> {noreply, S}. + terminate(_Reason, _S) -> log4erl:info("Merle watcher terminated!"), - ok. - - - + ok. \ No newline at end of file From 73950f4fb30c07cb9474967ce98888950493bb49 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 15 Aug 2012 16:19:55 -0700 Subject: [PATCH 30/65] Fixed dynamic merle compile --- src/merle_cluster.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index ea1dfbd..6fc3588 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -11,14 +11,18 @@ configure(MemcachedHosts, ConnectionsPerHost) -> DynModuleBegin = "-module(merle_cluster_dynamic). -export([get_server/1]). get_server(ClusterKey) -> N = erlang:phash2(ClusterKey, ~p), - do_get_server(N).", + do_get_server(N).\n", DynModuleMap = "do_get_server(~p) -> {\"~s\", ~p}; ", DynModuleEnd = "do_get_server(_N) -> throw({invalid_server_slot, _N}).\n", + ModuleString = lists:flatten([ io_lib:format(DynModuleBegin, [length(SortedMemcachedHosts)]), index_map(fun([Host, Port], I) -> io_lib:format(DynModuleMap, [I-1, Host, Port]) end, SortedMemcachedHosts), DynModuleEnd ]), + + log4erl:error("dyn module str ~p", [ModuleString]), + {M, B} = dynamic_compile:from_string(ModuleString), code:load_binary(M, "", B), From b01187796dd54ed25dc08cad63a4f256654dde61 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 15 Aug 2012 17:35:31 -0700 Subject: [PATCH 31/65] Ended race condition with local_pg2 init... debugged round_robin merle connection pooling --- src/local_pg2.erl | 33 ++++++++++++++++++--------------- src/merle_sup.erl | 1 + 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/local_pg2.erl b/src/local_pg2.erl index 40c2921..86fd2aa 100644 --- a/src/local_pg2.erl +++ b/src/local_pg2.erl @@ -77,13 +77,13 @@ get_closest_pid(round_robin, Name) -> [] -> {error, {no_process, Name}}; [{Name, Members}] -> - lists:nth(RoundRobinIndex rem length(Members), Members) + lists:nth((RoundRobinIndex rem length(Members)) + 1, Members) end. init([]) -> process_flag(trap_exit, true), - ets:new(?TABLE, [set, protected, named_table]), + ets:new(?TABLE, [set, public, named_table]), {ok, []}. handle_call({create, Name}, _From, S) -> @@ -154,19 +154,22 @@ terminate(_Reason, _S) -> %%%----------------------------------------------------------------- del_member(Pid) -> L = ets:tab2list(?TABLE), - lists:foreach(fun({Name, Members}) -> - case lists:member(Pid, Members) of - true -> - case lists:delete(Pid, Members) of - [] -> - ets:delete(?TABLE, Name); - NewMembers -> - ets:insert(?TABLE, {Name, NewMembers}) - end; - false -> - ok - end - end, L). + lists:foreach(fun(Elem) -> del_member_func(Elem, Pid) end, L). + +del_member_func({{_, rr_index}, _}, _Pid) -> + ok; +del_member_func({Name, Members}, Pid) -> + case lists:member(Pid, Members) of + true -> + case lists:delete(Pid, Members) of + [] -> + ets:delete(?TABLE, Name); + NewMembers -> + ets:insert(?TABLE, {Name, NewMembers}) + end; + false -> + ok + end. ensure_started() -> case whereis(?MODULE) of diff --git a/src/merle_sup.erl b/src/merle_sup.erl index d8ea373..5483724 100644 --- a/src/merle_sup.erl +++ b/src/merle_sup.erl @@ -8,6 +8,7 @@ start_link(Instances, ConnectionsPerInstance) -> {ok, Pid} = supervisor:start_link({local, ?MODULE}, ?MODULE, []), + local_pg2:start(), merle_cluster:configure(Instances, ConnectionsPerInstance), {ok, Pid}. From 26e8ad8d37cb8fc62d14de86af2bf6c1625fa342 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Tue, 21 Aug 2012 13:23:44 -0700 Subject: [PATCH 32/65] Splitting writes from reads on ets tables within process group --- src/local_pg2.erl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/local_pg2.erl b/src/local_pg2.erl index 86fd2aa..e40a59d 100644 --- a/src/local_pg2.erl +++ b/src/local_pg2.erl @@ -6,6 +6,7 @@ -export([start/0, start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). -define(TABLE, local_pg2_table). +-define(INDEXES_TABLE, local_pg2_indexes_table). start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). @@ -71,7 +72,7 @@ get_closest_pid(round_robin, Name) -> ensure_started(), % Get the round robin index - RoundRobinIndex = ets:update_counter(?TABLE, {Name, rr_index}, 1), + RoundRobinIndex = ets:update_counter(?INDEXES_TABLE, {Name, rr_index}, 1), case ets:lookup(?TABLE, Name) of [] -> @@ -84,12 +85,13 @@ get_closest_pid(round_robin, Name) -> init([]) -> process_flag(trap_exit, true), ets:new(?TABLE, [set, public, named_table]), + ets:new(?INDEXES_TABLE, [set, public, named_table]), {ok, []}. handle_call({create, Name}, _From, S) -> case ets:lookup(?TABLE, Name) of [] -> - ets:insert(?TABLE, {{Name, rr_index}, 0}), + ets:insert(?INDEXES_TABLE, {{Name, rr_index}, 0}), ets:insert(?TABLE, {Name, []}); _ -> ok @@ -103,7 +105,7 @@ handle_call({join, Name, Pid}, _From, S) -> [{Name, Members}] -> % NOTE: skip one index since we are about to grow the list, this prevents collisions - ets:update_counter(?TABLE, {Name, rr_index}, 1), + ets:update_counter(?INDEXES_TABLE, {Name, rr_index}, 1), % insert new pid into the table ets:insert(?TABLE, {Name, [Pid | Members]}), @@ -146,6 +148,7 @@ handle_info(_Info, S) -> terminate(_Reason, _S) -> ets:delete(?TABLE), + ets:delete(?INDEXES_TABLE), %%do not unlink, if this fails, dangling processes should be killed ok. @@ -156,8 +159,6 @@ del_member(Pid) -> L = ets:tab2list(?TABLE), lists:foreach(fun(Elem) -> del_member_func(Elem, Pid) end, L). -del_member_func({{_, rr_index}, _}, _Pid) -> - ok; del_member_func({Name, Members}, Pid) -> case lists:member(Pid, Members) of true -> From 9e96b68339bcd852b1b85bc450d7d9b22c3e469c Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Tue, 21 Aug 2012 16:24:43 -0700 Subject: [PATCH 33/65] Trying to lock individual merle conns when in use --- src/local_pg2.erl | 30 +++++++++++++++---- src/merle_cluster.erl | 69 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 6 deletions(-) diff --git a/src/local_pg2.erl b/src/local_pg2.erl index e40a59d..82b80de 100644 --- a/src/local_pg2.erl +++ b/src/local_pg2.erl @@ -1,7 +1,7 @@ -module(local_pg2). %% Basically the same functionality than pg2, but process groups are local rather than global. --export([create/1, delete/1, join/2, leave/2, get_members/1, get_closest_pid/2, which_groups/0]). +-export([create/1, delete/1, join/2, leave/2, get_members/1, get_closest_pid/2, release_pid/1, which_groups/0]). -export([start/0, start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). @@ -69,18 +69,33 @@ get_closest_pid(random, Name) -> end; get_closest_pid(round_robin, Name) -> - ensure_started(), - % Get the round robin index RoundRobinIndex = ets:update_counter(?INDEXES_TABLE, {Name, rr_index}, 1), case ets:lookup(?TABLE, Name) of [] -> {error, {no_process, Name}}; - [{Name, Members}] -> - lists:nth((RoundRobinIndex rem length(Members)) + 1, Members) + [{Name, Members}] -> + Pid = lists:nth((RoundRobinIndex rem length(Members)) + 1, Members), + + UseCount = ets:update_counter(?INDEXES_TABLE, {Pid, use_count}, 1), + + case Pid =< 1 of + true -> + UseCount; + false -> + {full, Pid} + end + + end. + +release_pid(Pid) -> + case is_process_alive(Pid) of + true -> + ets:update_counter(?INDEXES_TABLE, {Pid, use_count}, -1); + false -> + no_proc end. - init([]) -> process_flag(trap_exit, true), @@ -107,6 +122,9 @@ handle_call({join, Name, Pid}, _From, S) -> % NOTE: skip one index since we are about to grow the list, this prevents collisions ets:update_counter(?INDEXES_TABLE, {Name, rr_index}, 1), + % create an entry that will represent a lock for this pid + ets:insert(?INDEXES_TABLE, {Pid, use_count}, 0), + % insert new pid into the table ets:insert(?TABLE, {Name, [Pid | Members]}), diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 6fc3588..fcc1487 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -37,3 +37,72 @@ configure(MemcachedHosts, ConnectionsPerHost) -> end, SortedMemcachedHosts ). + + +exec(Key, Fun, FullDefault, ConnectionTimeout) -> + S = merle_cluster_dynamic:get_server(Key), + + FromPid = self(), + + ConnFetchPid = spawn( + fun() -> + + MerleConn = local_pg2:get_closest_pid(round_robin, S), + MonitorRef = erlang:monitor(process, FromPid), + + FromPid ! {merle_conn, MerleConn}, + + receive + {'DOWN', MonitorRef, _, _, _} -> + + log4erl:error("Merle connection fetch process received 'DOWN' message"), + + local_pg2:release_pid(MerleConn), + + ok; + + done -> + + ok; + + Other -> + + log4erl:error("Merle connection unexpected message ~p", [Other]) + + after 1000 -> + + log4erl:error("Merle connection fetch process timed out") + + end, + + true = erlang:demonitor(MonitorRef) + end + ), + + ReturnValue = receive + {merle_conn, {full, P}} -> + log4erl:error("Merle pool is full!"), + + local_pg2:release_pid(P), + + ConnFetchPid ! done, + + FullDefault; + + {merle_conn, P} -> + Value = Fun(P, Key), + + local_pg2:release_pid(P), + + ConnFetchPid ! done, + + Value + + after ConnectionTimeout -> + + exit(ConnFetchPid, kill), + + FullDefault + end, + + ReturnValue. \ No newline at end of file From 495d2316ed29127eae66f4276581de69fbd267f4 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Tue, 21 Aug 2012 16:27:44 -0700 Subject: [PATCH 34/65] Small bug fix with how we retrieve pids --- src/local_pg2.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/local_pg2.erl b/src/local_pg2.erl index 82b80de..85c3129 100644 --- a/src/local_pg2.erl +++ b/src/local_pg2.erl @@ -80,9 +80,9 @@ get_closest_pid(round_robin, Name) -> UseCount = ets:update_counter(?INDEXES_TABLE, {Pid, use_count}, 1), - case Pid =< 1 of + case UseCount =< 1 of true -> - UseCount; + Pid; false -> {full, Pid} end From 2ed5898993b24d8bf0879f09e7b20600953072f6 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Tue, 21 Aug 2012 16:30:20 -0700 Subject: [PATCH 35/65] Small bug fix with how we create use_count records --- src/local_pg2.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/local_pg2.erl b/src/local_pg2.erl index 85c3129..3906076 100644 --- a/src/local_pg2.erl +++ b/src/local_pg2.erl @@ -123,7 +123,7 @@ handle_call({join, Name, Pid}, _From, S) -> ets:update_counter(?INDEXES_TABLE, {Name, rr_index}, 1), % create an entry that will represent a lock for this pid - ets:insert(?INDEXES_TABLE, {Pid, use_count}, 0), + ets:insert(?INDEXES_TABLE, {{Pid, use_count}, 0}), % insert new pid into the table ets:insert(?TABLE, {Name, [Pid | Members]}), From 114e463fb3fdbe38dfa723201964939479bd94b3 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Tue, 21 Aug 2012 16:32:27 -0700 Subject: [PATCH 36/65] It helps to actually export the function --- src/merle_cluster.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index fcc1487..d3ad6da 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -1,6 +1,6 @@ -module(merle_cluster). --export([configure/2]). +-export([configure/2, exec/4]). index_map(F, List) -> {Map, _} = lists:mapfoldl(fun(X, Iter) -> {F(X, Iter), Iter +1} end, 1, List), From 94fa953efb8680bd65a616f76e3236a6ee6d2168 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 22 Aug 2012 11:59:38 -0700 Subject: [PATCH 37/65] Modified strategy so that merle_watchers are managed by process group.. They manage rebooting failed processes internally so that we don't back the local_pg2's message queue --- src/local_pg2.erl | 40 ++++++++++++++++++++++++++------------- src/merle_cluster.erl | 19 +++++++++++-------- src/merle_watcher.erl | 44 ++++++++++++++++++++++++++++++++----------- 3 files changed, 71 insertions(+), 32 deletions(-) diff --git a/src/local_pg2.erl b/src/local_pg2.erl index 3906076..e5802c7 100644 --- a/src/local_pg2.erl +++ b/src/local_pg2.erl @@ -1,7 +1,7 @@ -module(local_pg2). %% Basically the same functionality than pg2, but process groups are local rather than global. --export([create/1, delete/1, join/2, leave/2, get_members/1, get_closest_pid/2, release_pid/1, which_groups/0]). +-export([create/1, delete/1, join/2, leave/2, get_members/1, get_closest_pid/2, checkout_pid/1, checkin_pid/1, which_groups/0]). -export([start/0, start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). @@ -35,6 +35,7 @@ join(Name, Pid) when is_pid(Pid) -> _ -> gen_server:call(?MODULE, {join, Name, Pid}) end. + leave(Name, Pid) when is_pid(Pid) -> ensure_started(), case ets:lookup(?TABLE, Name) of @@ -53,7 +54,6 @@ get_members(Name) -> which_groups() -> ensure_started(), [K || {K, _Members} <- ets:tab2list(?TABLE)]. - get_closest_pid(random, Name) -> ensure_started(), @@ -65,7 +65,10 @@ get_closest_pid(random, Name) -> %% TODO: we can get more inteligent, check queue size, reductions, etc. %% http://lethain.com/entry/2009/sep/12/load-balancing-across-erlang-process-groups/ {_, _, X} = erlang:now(), - lists:nth((X rem length(Members)) +1, Members) + + Pid = lists:nth((X rem length(Members)) +1, Members), + + checkout_pid(Pid, true) end; get_closest_pid(round_robin, Name) -> @@ -78,18 +81,29 @@ get_closest_pid(round_robin, Name) -> [{Name, Members}] -> Pid = lists:nth((RoundRobinIndex rem length(Members)) + 1, Members), - UseCount = ets:update_counter(?INDEXES_TABLE, {Pid, use_count}, 1), - - case UseCount =< 1 of - true -> - Pid; - false -> - {full, Pid} - end - + checkout_pid(Pid, true) end. -release_pid(Pid) -> +checkout_pid(Pid) -> + checkout_pid(Pid, false). + +checkout_pid(Pid, CheckBackIn) -> + UseCount = ets:update_counter(?INDEXES_TABLE, {Pid, use_count}, 1), + + case UseCount =< 1 of + true -> Pid; + false -> + + if + CheckBackIn -> checkin_pid(Pid); + true -> ok + end, + + in_use + end. + +checkin_pid(in_use) -> ok; +checkin_pid(Pid) -> case is_process_alive(Pid) of true -> ets:update_counter(?INDEXES_TABLE, {Pid, use_count}, -1); diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index d3ad6da..5446181 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -50,14 +50,14 @@ exec(Key, Fun, FullDefault, ConnectionTimeout) -> MerleConn = local_pg2:get_closest_pid(round_robin, S), MonitorRef = erlang:monitor(process, FromPid), - FromPid ! {merle_conn, MerleConn}, + FromPid ! {merle_watcher, MerleConn}, receive {'DOWN', MonitorRef, _, _, _} -> log4erl:error("Merle connection fetch process received 'DOWN' message"), - local_pg2:release_pid(MerleConn), + local_pg2:checkin_pid(MerleConn), ok; @@ -80,25 +80,28 @@ exec(Key, Fun, FullDefault, ConnectionTimeout) -> ), ReturnValue = receive - {merle_conn, {full, P}} -> + {merle_watcher, in_use} -> log4erl:error("Merle pool is full!"), - local_pg2:release_pid(P), - ConnFetchPid ! done, FullDefault; - {merle_conn, P} -> - Value = Fun(P, Key), + {merle_watcher, P} -> + log4erl:error("Merle pool is full!"), + + MC = merle_watcher:merle_connection(P), + + Value = Fun(MC, Key), - local_pg2:release_pid(P), + local_pg2:checkin_pid(P), ConnFetchPid ! done, Value after ConnectionTimeout -> + log4erl:error("Merle timed out while trying to retrieve connection!"), exit(ConnFetchPid, kill), diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index 3a24e72..f8d7aee 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -1,8 +1,9 @@ -module(merle_watcher). -export([start_link/1, init/1, handle_call/3, handle_info/2, handle_cast/2, terminate/2]). +-export([merle_connection/1]). --define(RESTART_INTERVAL, 1000). %% retry each 5 seconds. +-define(RESTART_INTERVAL, 5000). %% retry each 5 seconds. -record(state, {mcd_pid, host, @@ -14,19 +15,35 @@ start_link([Host, Port]) -> init([Host, Port]) -> log4erl:info("Merle watcher initialized!"), erlang:process_flag(trap_exit, true), - self() ! timeout, + + SelfPid = self(), + + local_pg2:create({Host, Port}), + local_pg2:join({Host, Port}, SelfPid), + + local_pg2:checkout_pid(SelfPid), + + SelfPid ! connect, + {ok, #state{mcd_pid = undefined, host = Host, port = Port}}. +merle_connection(Pid) -> + gen_server:call(Pid, mcd_pid). + +handle_call(mcd_pid, _From, State = #state{mcd_pid = McdPid}) -> + {reply, McdPid, State}; + handle_call(_Call, _From, S) -> {reply, ok, S}. -handle_info('timeout', #state{mcd_pid = undefined, host = Host, port = Port} = State) -> +handle_info('connect', #state{mcd_pid = undefined, host = Host, port = Port} = State) -> error_logger:info_report([{memcached, connecting}, {host, Host}, {port, Port}]), case merle:connect(Host, Port) of {ok, Pid} -> - local_pg2:create({Host, Port}), - local_pg2:join({Host, Port}, Pid), + + local_pg2:checkin_pid(self()), + {noreply, State#state{mcd_pid = Pid}}; {error, Reason} -> @@ -50,11 +67,11 @@ handle_info({'EXIT', Pid, Reason}, #state{mcd_pid = Pid} = S) -> error_logger:error_report([{memcached_crashed, Pid}, {reason, Reason}, {host, S#state.host}, - {port, S#state.port}, - {restarting_in, ?RESTART_INTERVAL}]), + {port, S#state.port}]), - % TODO: do we need this? - %local_pg2:leave({Host, Port}, Pid), + local_pg2:checkout_pid(self()), + + self() ! connect, {noreply, S#state{mcd_pid = undefined}, ?RESTART_INTERVAL}; @@ -71,6 +88,11 @@ handle_info(_Info, S) -> handle_cast(_Cast, S) -> {noreply, S}. -terminate(_Reason, _S) -> - log4erl:info("Merle watcher terminated!"), +terminate(_Reason, #state{mcd_pid = undefined}) -> + log4erl:error("Merle watcher terminated, mcd pid is empty!"), + ok; + +terminate(_Reason, #state{mcd_pid = McdPid}) -> + log4erl:error("Merle watcher terminated, killing mcd pid!"), + erlang:exit(McdPid, watcher_died), ok. \ No newline at end of file From dd9e5bd129647bb4b95e215a905fa4a5a00acbf4 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 22 Aug 2012 12:22:26 -0700 Subject: [PATCH 38/65] Removed extraneous log message --- src/merle_cluster.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 5446181..92f281e 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -88,8 +88,6 @@ exec(Key, Fun, FullDefault, ConnectionTimeout) -> FullDefault; {merle_watcher, P} -> - log4erl:error("Merle pool is full!"), - MC = merle_watcher:merle_connection(P), Value = Fun(MC, Key), From b6c2e95fb6f99642de46dcace461cec9e3f33402 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 22 Aug 2012 13:23:20 -0700 Subject: [PATCH 39/65] Changed the way we release the merle connection slightly --- src/merle_cluster.erl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 92f281e..b1e911f 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -57,8 +57,6 @@ exec(Key, Fun, FullDefault, ConnectionTimeout) -> log4erl:error("Merle connection fetch process received 'DOWN' message"), - local_pg2:checkin_pid(MerleConn), - ok; done -> @@ -72,16 +70,19 @@ exec(Key, Fun, FullDefault, ConnectionTimeout) -> after 1000 -> log4erl:error("Merle connection fetch process timed out") - + end, + local_pg2:checkin_pid(MerleConn), + true = erlang:demonitor(MonitorRef) + end ), ReturnValue = receive {merle_watcher, in_use} -> - log4erl:error("Merle pool is full!"), + log4erl:info("Merle pool is full!"), ConnFetchPid ! done, @@ -92,8 +93,6 @@ exec(Key, Fun, FullDefault, ConnectionTimeout) -> Value = Fun(MC, Key), - local_pg2:checkin_pid(P), - ConnFetchPid ! done, Value From 0be553fbd37f6ce1d8d75f680d83d5ef7545130a Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 22 Aug 2012 13:44:32 -0700 Subject: [PATCH 40/65] Reduced error logging to make less noisy --- src/merle_cluster.erl | 4 +++- src/merle_watcher.erl | 7 ------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index b1e911f..5f4549a 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -89,6 +89,8 @@ exec(Key, Fun, FullDefault, ConnectionTimeout) -> FullDefault; {merle_watcher, P} -> + log4erl:info("Merle found connection!"), + MC = merle_watcher:merle_connection(P), Value = Fun(MC, Key), @@ -100,7 +102,7 @@ exec(Key, Fun, FullDefault, ConnectionTimeout) -> after ConnectionTimeout -> log4erl:error("Merle timed out while trying to retrieve connection!"), - exit(ConnFetchPid, kill), + ConnFetchPid ! done, FullDefault end, diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index f8d7aee..10fb96f 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -37,8 +37,6 @@ handle_call(_Call, _From, S) -> {reply, ok, S}. handle_info('connect', #state{mcd_pid = undefined, host = Host, port = Port} = State) -> - error_logger:info_report([{memcached, connecting}, {host, Host}, {port, Port}]), - case merle:connect(Host, Port) of {ok, Pid} -> @@ -64,11 +62,6 @@ handle_info('connect', #state{mcd_pid = undefined, host = Host, port = Port} = S end; handle_info({'EXIT', Pid, Reason}, #state{mcd_pid = Pid} = S) -> - error_logger:error_report([{memcached_crashed, Pid}, - {reason, Reason}, - {host, S#state.host}, - {port, S#state.port}]), - local_pg2:checkout_pid(self()), self() ! connect, From 394d186797b990ccf8e304463a1489f7401181c2 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 22 Aug 2012 14:12:40 -0700 Subject: [PATCH 41/65] Changed exit scheme for merle processes.. should now issue exit message in queue, and move on --- src/merle.erl | 13 ++++++++++--- src/merle_watcher.erl | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index 49abe33..1d4aa1c 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -327,6 +327,9 @@ start_link(Host, Port) -> %% @private init([Host, Port]) -> log4erl:info("Socket initialized!"), + + erlang:process_flag(trap_exit, true), + gen_tcp:connect(Host, Port, ?TCP_OPTS_ACTIVE). handle_call({stats}, _From, Socket) -> @@ -464,6 +467,10 @@ handle_info({tcp_closed, Socket}, Socket) -> {stop, {error, tcp_closed}, Socket}; handle_info({tcp_error, Socket, Reason}, Socket) -> {stop, {error, {tcp_error, Reason}}, Socket}; + +handle_info({'EXIT', _, Reason}, Socket) -> + {stop, {error, Reason}, Socket}; + handle_info(_Info, State) -> {noreply, State}. %% @private @@ -508,7 +515,7 @@ send_get_cmd(Socket, Cmd, Timeout) -> [] -> {error, not_found}; {error, Error} -> log4erl:error("Encountered error from memcache; killing connection now: ~p", [Error]), - erlang:exit(Error), + erlang:exit(self(), Error), {error, Error} end, inet:setopts(Socket, ?TCP_OPTS_ACTIVE), @@ -560,11 +567,11 @@ recv_simple_reply(Timeout) -> parse_simple_response_line(Data); {error, closed} -> log4erl:error("Encountered error while receiving simple reply from memcache; killing connection now."), - erlang:exit(connection_closed), + erlang:exit(self(), connection_closed), connection_closed after Timeout -> log4erl:error("Encountered timeout while receiving simple reply from memcache; killing connection now."), - erlang:exit(timeout), + erlang:exit(self(), timeout), {error, timeout} end. parse_simple_response_line(<<"OK", _B/binary>>) -> ok; diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index 10fb96f..2d29968 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -61,7 +61,7 @@ handle_info('connect', #state{mcd_pid = undefined, host = Host, port = Port} = S {noreply, State, ?RESTART_INTERVAL} end; -handle_info({'EXIT', Pid, Reason}, #state{mcd_pid = Pid} = S) -> +handle_info({'EXIT', Pid, _}, #state{mcd_pid = Pid} = S) -> local_pg2:checkout_pid(self()), self() ! connect, From a0435ab52ed320061ebcdfbd8b7b18c1433b9a7e Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 22 Aug 2012 15:04:10 -0700 Subject: [PATCH 42/65] Reduced error_logger logging when killing memcache connections --- src/merle.erl | 3 ++- src/merle_cluster.erl | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index 1d4aa1c..ff3e59b 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -469,7 +469,8 @@ handle_info({tcp_error, Socket, Reason}, Socket) -> {stop, {error, {tcp_error, Reason}}, Socket}; handle_info({'EXIT', _, Reason}, Socket) -> - {stop, {error, Reason}, Socket}; + log4erl:error("Exiting merle connection ~p", [Reason]), + {stop, normal, Socket}; handle_info(_Info, State) -> {noreply, State}. diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 5f4549a..9b24aff 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -55,7 +55,7 @@ exec(Key, Fun, FullDefault, ConnectionTimeout) -> receive {'DOWN', MonitorRef, _, _, _} -> - log4erl:error("Merle connection fetch process received 'DOWN' message"), + log4erl:info("Merle connection fetch process received 'DOWN' message"), ok; From ccc51f89fce64cfd9682b731af987a629a925e48 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 22 Aug 2012 16:17:45 -0700 Subject: [PATCH 43/65] Simplified merle_cluster exec, complete with monitoring --- src/local_pg2.erl | 4 +- src/merle_cluster.erl | 140 ++++++++++++++++++++++++------------------ src/merle_watcher.erl | 51 +++++++++++---- 3 files changed, 123 insertions(+), 72 deletions(-) diff --git a/src/local_pg2.erl b/src/local_pg2.erl index e5802c7..9b5cc7a 100644 --- a/src/local_pg2.erl +++ b/src/local_pg2.erl @@ -113,8 +113,8 @@ checkin_pid(Pid) -> init([]) -> process_flag(trap_exit, true), - ets:new(?TABLE, [set, public, named_table]), - ets:new(?INDEXES_TABLE, [set, public, named_table]), + ets:new(?TABLE, [set, public, named_table, {read_concurrency, true}]), + ets:new(?INDEXES_TABLE, [set, public, named_table, {write_concurrency, true}]), {ok, []}. handle_call({create, Name}, _From, S) -> diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 9b24aff..006791d 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -39,72 +39,94 @@ configure(MemcachedHosts, ConnectionsPerHost) -> ). -exec(Key, Fun, FullDefault, ConnectionTimeout) -> +exec(Key, Fun, FullDefault, _ConnectionTimeout) -> S = merle_cluster_dynamic:get_server(Key), - FromPid = self(), - - ConnFetchPid = spawn( - fun() -> - - MerleConn = local_pg2:get_closest_pid(round_robin, S), - MonitorRef = erlang:monitor(process, FromPid), - - FromPid ! {merle_watcher, MerleConn}, - - receive - {'DOWN', MonitorRef, _, _, _} -> - - log4erl:info("Merle connection fetch process received 'DOWN' message"), - - ok; - - done -> - - ok; - - Other -> - - log4erl:error("Merle connection unexpected message ~p", [Other]) - - after 1000 -> - - log4erl:error("Merle connection fetch process timed out") - - end, - - local_pg2:checkin_pid(MerleConn), - - true = erlang:demonitor(MonitorRef) - - end - ), - - ReturnValue = receive - {merle_watcher, in_use} -> - log4erl:info("Merle pool is full!"), - - ConnFetchPid ! done, - + case local_pg2:get_closest_pid(round_robin, S) of + in_use -> FullDefault; - {merle_watcher, P} -> - log4erl:info("Merle found connection!"), - - MC = merle_watcher:merle_connection(P), + P -> + merle_watcher:monitor(P, self()), - Value = Fun(MC, Key), - - ConnFetchPid ! done, + MC = merle_watcher:merle_connection(P), - Value + Value = Fun(MC, Key), - after ConnectionTimeout -> - log4erl:error("Merle timed out while trying to retrieve connection!"), + merle_watcher:demonitor(P), - ConnFetchPid ! done, + local_pg2:checkin_pid(P), - FullDefault - end, + Value - ReturnValue. \ No newline at end of file + end. + +%exec(Key, Fun, FullDefault, ConnectionTimeout) -> +% S = merle_cluster_dynamic:get_server(Key), +% +% FromPid = self(), +% +% ConnFetchPid = spawn( +% fun() -> +% +% MerleConn = local_pg2:get_closest_pid(round_robin, S), +% MonitorRef = erlang:monitor(process, FromPid), +% +% FromPid ! {merle_watcher, MerleConn}, +% +% receive +% {'DOWN', MonitorRef, _, _, _} -> +% +% log4erl:info("Merle connection fetch process received 'DOWN' message"), +% +% ok; +% +% done -> +% +% ok; +% +% Other -> +% +% log4erl:error("Merle connection unexpected message ~p", [Other]) +% +% after 1000 -> +% +% log4erl:error("Merle connection fetch process timed out") +% +% end, +% +% local_pg2:checkin_pid(MerleConn), +% +% true = erlang:demonitor(MonitorRef) +% +% end +% ), +% +% ReturnValue = receive +% {merle_watcher, in_use} -> +% log4erl:info("Merle pool is full!"), +% +% ConnFetchPid ! done, +% +% FullDefault; +% +% {merle_watcher, P} -> +% log4erl:info("Merle found connection!"), +% +% MC = merle_watcher:merle_connection(P), +% +% Value = Fun(MC, Key), +% +% ConnFetchPid ! done, +% +% Value +% +% after ConnectionTimeout -> +% log4erl:error("Merle timed out while trying to retrieve connection!"), +% +% ConnFetchPid ! done, +% +% FullDefault +% end, +% +% ReturnValue. \ No newline at end of file diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index 2d29968..5beb444 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -1,13 +1,17 @@ -module(merle_watcher). -export([start_link/1, init/1, handle_call/3, handle_info/2, handle_cast/2, terminate/2]). --export([merle_connection/1]). + +-export([merle_connection/1, monitor/2, demonitor/1]). -define(RESTART_INTERVAL, 5000). %% retry each 5 seconds. --record(state, {mcd_pid, - host, - port}). +-record(state, { + mcd_pid, + host, + port, + monitor +}). start_link([Host, Port]) -> gen_server:start_link(?MODULE, [Host, Port], []). @@ -28,11 +32,32 @@ init([Host, Port]) -> {ok, #state{mcd_pid = undefined, host = Host, port = Port}}. merle_connection(Pid) -> - gen_server:call(Pid, mcd_pid). + gen_server:call(Pid, mcd_pid). + +monitor(Pid, OwnerPid) -> + gen_server:call(Pid, {mark_owner, OwnerPid}). + +demonitor(Pid) -> + gen_server:call(Pid, demonitor). handle_call(mcd_pid, _From, State = #state{mcd_pid = McdPid}) -> {reply, McdPid, State}; +handle_call({monitor, MonitorPid}, _From, State = #state{monitor = PrevMonitor}) -> + case PrevMonitor of + undefined -> ok; + _ -> + true = erlang:demonitor(PrevMonitor) + end, + + Monitor = erlang:monitor(process, MonitorPid), + + {reply, ok, State#state{monitor = Monitor}}; + +handle_call(demonitor, _From, State = #state{monitor = PrevMonitor}) -> + true = erlang:demonitor(PrevMonitor), + {reply, ok, State#state{monitor = undefined}}; + handle_call(_Call, _From, S) -> {reply, ok, S}. @@ -60,6 +85,15 @@ handle_info('connect', #state{mcd_pid = undefined, host = Host, port = Port} = S {noreply, State, ?RESTART_INTERVAL} end; + +handle_info({'DOWN', MonitorRef, _, _, _}, #state{monitor=MonitorRef} = S) -> + log4erl:info("merle_watcher caught a DOWN event"), + + local_pg2:checkin_pid(self()), + + true = erlang:demonitor(MonitorRef), + + {noreply, S#state{monitor = undefined}}; handle_info({'EXIT', Pid, _}, #state{mcd_pid = Pid} = S) -> local_pg2:checkout_pid(self()), @@ -71,12 +105,7 @@ handle_info({'EXIT', Pid, _}, #state{mcd_pid = Pid} = S) -> handle_info(_Info, S) -> error_logger:warning_report([{merle_watcher, self()}, {unknown_info, _Info}]), - case S#state.mcd_pid of - undefined -> - {noreply, S, ?RESTART_INTERVAL}; - _ -> - {noreply, S} - end. + {noreply, S}. handle_cast(_Cast, S) -> {noreply, S}. From ba887787c45de9ee8f5392a70d50c6a57170ca3a Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 22 Aug 2012 16:28:12 -0700 Subject: [PATCH 44/65] Fixed a bug with merle_watcher monitoring... fixed other shit --- src/merle.erl | 8 ++++---- src/merle_watcher.erl | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index ff3e59b..816d998 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -469,7 +469,7 @@ handle_info({tcp_error, Socket, Reason}, Socket) -> {stop, {error, {tcp_error, Reason}}, Socket}; handle_info({'EXIT', _, Reason}, Socket) -> - log4erl:error("Exiting merle connection ~p", [Reason]), + log4erl:warn("Exiting merle connection ~p", [Reason]), {stop, normal, Socket}; handle_info(_Info, State) -> {noreply, State}. @@ -515,7 +515,7 @@ send_get_cmd(Socket, Cmd, Timeout) -> [{_, Value}] -> {ok, Value}; [] -> {error, not_found}; {error, Error} -> - log4erl:error("Encountered error from memcache; killing connection now: ~p", [Error]), + log4erl:warn("Encountered error from memcache; killing connection now: ~p", [Error]), erlang:exit(self(), Error), {error, Error} end, @@ -567,11 +567,11 @@ recv_simple_reply(Timeout) -> inet:setopts(Socket, ?TCP_OPTS_ACTIVE), parse_simple_response_line(Data); {error, closed} -> - log4erl:error("Encountered error while receiving simple reply from memcache; killing connection now."), + log4erl:warn("Encountered error while receiving simple reply from memcache; killing connection now."), erlang:exit(self(), connection_closed), connection_closed after Timeout -> - log4erl:error("Encountered timeout while receiving simple reply from memcache; killing connection now."), + log4erl:warn("Encountered timeout while receiving simple reply from memcache; killing connection now."), erlang:exit(self(), timeout), {error, timeout} end. diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index 5beb444..b8356e8 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -55,7 +55,12 @@ handle_call({monitor, MonitorPid}, _From, State = #state{monitor = PrevMonitor}) {reply, ok, State#state{monitor = Monitor}}; handle_call(demonitor, _From, State = #state{monitor = PrevMonitor}) -> - true = erlang:demonitor(PrevMonitor), + case PrevMonitor of + undefined -> ok; + _ -> + true = erlang:demonitor(PrevMonitor) + end, + {reply, ok, State#state{monitor = undefined}}; handle_call(_Call, _From, S) -> From 5a708f43b8fd662926a16ddb9e47a8176d4d56a6 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 22 Aug 2012 17:54:43 -0700 Subject: [PATCH 45/65] Tweaked supervision tree for merle project, so that if process pool mgr dies, all outstanding merle watchers will also die --- src/merle.erl | 2 +- src/merle_cluster.erl | 79 ++--------------------- src/{local_pg2.erl => merle_pool.erl} | 91 ++++++++++----------------- src/merle_sup.erl | 26 ++++---- src/merle_watcher.erl | 13 ++-- src/merle_watcher_sup.erl | 22 +++++++ 6 files changed, 83 insertions(+), 150 deletions(-) rename src/{local_pg2.erl => merle_pool.erl} (65%) create mode 100644 src/merle_watcher_sup.erl diff --git a/src/merle.erl b/src/merle.erl index 816d998..bc7bfc5 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -558,7 +558,7 @@ do_recv_stats(Timeout) -> inet:setopts(Socket, ?TCP_OPTS_ACTIVE), [{Field, Value} | do_recv_stats(Timeout)] after Timeout -> - timeout + timeout end. %% @doc receive function for simple responses (not containing VALUEs) recv_simple_reply(Timeout) -> diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 006791d..3d92b0f 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -26,11 +26,12 @@ configure(MemcachedHosts, ConnectionsPerHost) -> {M, B} = dynamic_compile:from_string(ModuleString), code:load_binary(M, "", B), + % start all merle watchers lists:foreach( fun([Host, Port]) -> lists:foreach( fun(_) -> - supervisor:start_child(merle_sup, [[Host, Port]]) + supervisor:start_child(merle_watcher_sup, [[Host, Port]]) end, lists:seq(1, ConnectionsPerHost) ) @@ -42,7 +43,7 @@ configure(MemcachedHosts, ConnectionsPerHost) -> exec(Key, Fun, FullDefault, _ConnectionTimeout) -> S = merle_cluster_dynamic:get_server(Key), - case local_pg2:get_closest_pid(round_robin, S) of + case merle_pool:get_closest_pid(round_robin, S) of in_use -> FullDefault; @@ -55,78 +56,8 @@ exec(Key, Fun, FullDefault, _ConnectionTimeout) -> merle_watcher:demonitor(P), - local_pg2:checkin_pid(P), + merle_pool:checkin_pid(P), Value - end. - -%exec(Key, Fun, FullDefault, ConnectionTimeout) -> -% S = merle_cluster_dynamic:get_server(Key), -% -% FromPid = self(), -% -% ConnFetchPid = spawn( -% fun() -> -% -% MerleConn = local_pg2:get_closest_pid(round_robin, S), -% MonitorRef = erlang:monitor(process, FromPid), -% -% FromPid ! {merle_watcher, MerleConn}, -% -% receive -% {'DOWN', MonitorRef, _, _, _} -> -% -% log4erl:info("Merle connection fetch process received 'DOWN' message"), -% -% ok; -% -% done -> -% -% ok; -% -% Other -> -% -% log4erl:error("Merle connection unexpected message ~p", [Other]) -% -% after 1000 -> -% -% log4erl:error("Merle connection fetch process timed out") -% -% end, -% -% local_pg2:checkin_pid(MerleConn), -% -% true = erlang:demonitor(MonitorRef) -% -% end -% ), -% -% ReturnValue = receive -% {merle_watcher, in_use} -> -% log4erl:info("Merle pool is full!"), -% -% ConnFetchPid ! done, -% -% FullDefault; -% -% {merle_watcher, P} -> -% log4erl:info("Merle found connection!"), -% -% MC = merle_watcher:merle_connection(P), -% -% Value = Fun(MC, Key), -% -% ConnFetchPid ! done, -% -% Value -% -% after ConnectionTimeout -> -% log4erl:error("Merle timed out while trying to retrieve connection!"), -% -% ConnFetchPid ! done, -% -% FullDefault -% end, -% -% ReturnValue. \ No newline at end of file + end. \ No newline at end of file diff --git a/src/local_pg2.erl b/src/merle_pool.erl similarity index 65% rename from src/local_pg2.erl rename to src/merle_pool.erl index 9b5cc7a..063fa73 100644 --- a/src/local_pg2.erl +++ b/src/merle_pool.erl @@ -1,22 +1,18 @@ --module(local_pg2). +-module(merle_pool). %% Basically the same functionality than pg2, but process groups are local rather than global. -export([create/1, delete/1, join/2, leave/2, get_members/1, get_closest_pid/2, checkout_pid/1, checkin_pid/1, which_groups/0]). --export([start/0, start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). +-export([start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). --define(TABLE, local_pg2_table). --define(INDEXES_TABLE, local_pg2_indexes_table). +-define(PIDS_TABLE, merle_pool_pids). +-define(LOCKS_TABLE, merle_pool_counts). start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). -start() -> - ensure_started(). - create(Name) -> - ensure_started(), - case ets:lookup(?TABLE, Name) of + case ets:lookup(?PIDS_TABLE, Name) of [] -> gen_server:call(?MODULE, {create, Name}); _ -> @@ -24,12 +20,10 @@ create(Name) -> end. delete(Name) -> - ensure_started(), gen_server:call(?MODULE, {delete, Name}). join(Name, Pid) when is_pid(Pid) -> - ensure_started(), - case ets:lookup(?TABLE, Name) of + case ets:lookup(?PIDS_TABLE, Name) of [] -> {error, {no_such_group, Name}}; _ -> @@ -37,8 +31,7 @@ join(Name, Pid) when is_pid(Pid) -> end. leave(Name, Pid) when is_pid(Pid) -> - ensure_started(), - case ets:lookup(?TABLE, Name) of + case ets:lookup(?PIDS_TABLE, Name) of [] -> {error, {no_such_group, Name}}; _ -> @@ -46,19 +39,15 @@ leave(Name, Pid) when is_pid(Pid) -> end. get_members(Name) -> - ensure_started(), - case ets:lookup(?TABLE, Name) of + case ets:lookup(?PIDS_TABLE, Name) of [] -> {error, {no_such_group, Name}}; [{Name, Members}] -> Members end. which_groups() -> - ensure_started(), - [K || {K, _Members} <- ets:tab2list(?TABLE)]. - -get_closest_pid(random, Name) -> - ensure_started(), + [K || {K, _Members} <- ets:tab2list(?PIDS_TABLE)]. - case ets:lookup(?TABLE, Name) of +get_closest_pid(random, Name) -> + case ets:lookup(?PIDS_TABLE, Name) of [] -> {error, {no_process, Name}}; [{Name, Members}] -> @@ -73,9 +62,9 @@ get_closest_pid(random, Name) -> get_closest_pid(round_robin, Name) -> % Get the round robin index - RoundRobinIndex = ets:update_counter(?INDEXES_TABLE, {Name, rr_index}, 1), + RoundRobinIndex = ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, 1), - case ets:lookup(?TABLE, Name) of + case ets:lookup(?PIDS_TABLE, Name) of [] -> {error, {no_process, Name}}; [{Name, Members}] -> @@ -88,12 +77,11 @@ checkout_pid(Pid) -> checkout_pid(Pid, false). checkout_pid(Pid, CheckBackIn) -> - UseCount = ets:update_counter(?INDEXES_TABLE, {Pid, use_count}, 1), + UseCount = ets:update_counter(?LOCKS_TABLE, {Pid, use_count}, 1), case UseCount =< 1 of true -> Pid; false -> - if CheckBackIn -> checkin_pid(Pid); true -> ok @@ -106,41 +94,41 @@ checkin_pid(in_use) -> ok; checkin_pid(Pid) -> case is_process_alive(Pid) of true -> - ets:update_counter(?INDEXES_TABLE, {Pid, use_count}, -1); + ets:update_counter(?LOCKS_TABLE, {Pid, use_count}, -1); false -> no_proc end. init([]) -> process_flag(trap_exit, true), - ets:new(?TABLE, [set, public, named_table, {read_concurrency, true}]), - ets:new(?INDEXES_TABLE, [set, public, named_table, {write_concurrency, true}]), + ets:new(?PIDS_TABLE, [set, public, named_table, {read_concurrency, true}]), + ets:new(?LOCKS_TABLE, [set, public, named_table, {write_concurrency, true}]), {ok, []}. handle_call({create, Name}, _From, S) -> - case ets:lookup(?TABLE, Name) of + case ets:lookup(?PIDS_TABLE, Name) of [] -> - ets:insert(?INDEXES_TABLE, {{Name, rr_index}, 0}), - ets:insert(?TABLE, {Name, []}); + ets:insert(?LOCKS_TABLE, {{Name, rr_index}, 0}), + ets:insert(?PIDS_TABLE, {Name, []}); _ -> ok end, {reply, ok, S}; handle_call({join, Name, Pid}, _From, S) -> - case ets:lookup(?TABLE, Name) of + case ets:lookup(?PIDS_TABLE, Name) of [] -> {reply, no_such_group, S}; [{Name, Members}] -> % NOTE: skip one index since we are about to grow the list, this prevents collisions - ets:update_counter(?INDEXES_TABLE, {Name, rr_index}, 1), + ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, 1), % create an entry that will represent a lock for this pid - ets:insert(?INDEXES_TABLE, {{Pid, use_count}, 0}), + ets:insert(?LOCKS_TABLE, {{Pid, use_count}, 0}), % insert new pid into the table - ets:insert(?TABLE, {Name, [Pid | Members]}), + ets:insert(?PIDS_TABLE, {Name, [Pid | Members]}), link(Pid), @@ -149,22 +137,22 @@ handle_call({join, Name, Pid}, _From, S) -> end; handle_call({leave, Name, Pid}, _From, S) -> - case ets:lookup(?TABLE, Name) of + case ets:lookup(?PIDS_TABLE, Name) of [] -> {reply, no_such_group, S}; [{Name, Members}] -> case lists:delete(Pid, Members) of [] -> - ets:delete(?TABLE, Name); + ets:delete(?PIDS_TABLE, Name); NewMembers -> - ets:insert(?TABLE, {Name, NewMembers}) + ets:insert(?PIDS_TABLE, {Name, NewMembers}) end, unlink(Pid), {reply, ok, S} end; handle_call({delete, Name}, _From, S) -> - ets:delete(?TABLE, Name), + ets:delete(?PIDS_TABLE, Name), {reply, ok, S}. handle_cast(_Cast, S) -> @@ -179,8 +167,8 @@ handle_info(_Info, S) -> {noreply, S}. terminate(_Reason, _S) -> - ets:delete(?TABLE), - ets:delete(?INDEXES_TABLE), + ets:delete(?PIDS_TABLE), + ets:delete(?LOCKS_TABLE), %%do not unlink, if this fails, dangling processes should be killed ok. @@ -188,7 +176,7 @@ terminate(_Reason, _S) -> %%% Internal functions %%%----------------------------------------------------------------- del_member(Pid) -> - L = ets:tab2list(?TABLE), + L = ets:tab2list(?PIDS_TABLE), lists:foreach(fun(Elem) -> del_member_func(Elem, Pid) end, L). del_member_func({Name, Members}, Pid) -> @@ -196,21 +184,10 @@ del_member_func({Name, Members}, Pid) -> true -> case lists:delete(Pid, Members) of [] -> - ets:delete(?TABLE, Name); + ets:delete(?PIDS_TABLE, Name); NewMembers -> - ets:insert(?TABLE, {Name, NewMembers}) + ets:insert(?PIDS_TABLE, {Name, NewMembers}) end; false -> ok - end. - -ensure_started() -> - case whereis(?MODULE) of - undefined -> - C = {local_pg2, {?MODULE, start_link, []}, permanent, - 1000, worker, [?MODULE]}, - supervisor:start_child(kernel_safe_sup, C); - Pg2Pid -> - {ok, Pg2Pid} - end. - + end. \ No newline at end of file diff --git a/src/merle_sup.erl b/src/merle_sup.erl index 5483724..ceb7689 100644 --- a/src/merle_sup.erl +++ b/src/merle_sup.erl @@ -2,20 +2,22 @@ -export([start_link/2, init/1]). --export([start_child/1]). - -behaviour(supervisor). start_link(Instances, ConnectionsPerInstance) -> - {ok, Pid} = supervisor:start_link({local, ?MODULE}, ?MODULE, []), - local_pg2:start(), - merle_cluster:configure(Instances, ConnectionsPerInstance), - {ok, Pid}. + supervisor:start_link({local, ?MODULE}, ?MODULE, [Instances, ConnectionsPerInstance]). + +init([Instances, ConnectionsPerInstance]) -> + MerlePool = + {merle_pool, + {merle_pool, start_link, []}, + permanent, 5000, worker, dynamic + }, -start_child(N) -> - supervisor:start_child(?MODULE, [N]). + MerleWatcherSup = + {merle_watcher_sup, + {merle_watcher_sup, start_link, [Instances, ConnectionsPerInstance]}, + permanent, 5000, supervisor, dynamic + }, -init([]) -> - MCDSpec = {mcd, {merle_watcher, start_link, []}, - permanent, 5000, worker, dynamic}, - {ok, {{simple_one_for_one, 10, 10}, [MCDSpec]}}. + {ok, {{one_for_all, 10, 10}, [MerlePool, MerleWatcherSup]}}. \ No newline at end of file diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index b8356e8..3bd2656 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -22,10 +22,11 @@ init([Host, Port]) -> SelfPid = self(), - local_pg2:create({Host, Port}), - local_pg2:join({Host, Port}, SelfPid), + merle_pool:create({Host, Port}), - local_pg2:checkout_pid(SelfPid), + merle_pool:join({Host, Port}, SelfPid), + + merle_pool:checkout_pid(SelfPid), SelfPid ! connect, @@ -70,7 +71,7 @@ handle_info('connect', #state{mcd_pid = undefined, host = Host, port = Port} = S case merle:connect(Host, Port) of {ok, Pid} -> - local_pg2:checkin_pid(self()), + merle_pool:checkin_pid(self()), {noreply, State#state{mcd_pid = Pid}}; @@ -94,14 +95,14 @@ handle_info('connect', #state{mcd_pid = undefined, host = Host, port = Port} = S handle_info({'DOWN', MonitorRef, _, _, _}, #state{monitor=MonitorRef} = S) -> log4erl:info("merle_watcher caught a DOWN event"), - local_pg2:checkin_pid(self()), + merle_pool:checkin_pid(self()), true = erlang:demonitor(MonitorRef), {noreply, S#state{monitor = undefined}}; handle_info({'EXIT', Pid, _}, #state{mcd_pid = Pid} = S) -> - local_pg2:checkout_pid(self()), + merle_pool:checkout_pid(self()), self() ! connect, diff --git a/src/merle_watcher_sup.erl b/src/merle_watcher_sup.erl new file mode 100644 index 0000000..193e690 --- /dev/null +++ b/src/merle_watcher_sup.erl @@ -0,0 +1,22 @@ +-module(merle_watcher_sup). + +-export([start_link/2, init/1]). + +-export([start_child/1]). + +-behaviour(supervisor). + +start_link(Instances, ConnectionsPerInstance) -> + {ok, Pid} = supervisor:start_link({local, ?MODULE}, ?MODULE, []), + + merle_cluster:configure(Instances, ConnectionsPerInstance), + + {ok, Pid}. + +start_child(N) -> + supervisor:start_child(?MODULE, [N]). + +init([]) -> + MCDSpec = {mcd, {merle_watcher, start_link, []}, + permanent, 5000, worker, dynamic}, + {ok, {{simple_one_for_one, 10, 10}, [MCDSpec]}}. From 1f7e194405a68b876a4ce92a65429e4ebfce2f16 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Thu, 23 Aug 2012 13:38:01 -0700 Subject: [PATCH 46/65] Adding timestamp that tracks time when connections unlock --- src/merle_cluster.erl | 4 ++-- src/merle_pool.erl | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 3d92b0f..7b0449b 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -40,7 +40,7 @@ configure(MemcachedHosts, ConnectionsPerHost) -> ). -exec(Key, Fun, FullDefault, _ConnectionTimeout) -> +exec(Key, Fun, FullDefault, Now) -> S = merle_cluster_dynamic:get_server(Key), case merle_pool:get_closest_pid(round_robin, S) of @@ -56,7 +56,7 @@ exec(Key, Fun, FullDefault, _ConnectionTimeout) -> merle_watcher:demonitor(P), - merle_pool:checkin_pid(P), + merle_pool:checkin_pid(P, Now), Value diff --git a/src/merle_pool.erl b/src/merle_pool.erl index 063fa73..876ab9a 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -1,12 +1,12 @@ -module(merle_pool). %% Basically the same functionality than pg2, but process groups are local rather than global. --export([create/1, delete/1, join/2, leave/2, get_members/1, get_closest_pid/2, checkout_pid/1, checkin_pid/1, which_groups/0]). +-export([create/1, delete/1, join/2, leave/2, get_members/1, get_closest_pid/2, checkout_pid/1, checkin_pid/1, checkin_pid/2, which_groups/0]). -export([start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). -define(PIDS_TABLE, merle_pool_pids). --define(LOCKS_TABLE, merle_pool_counts). +-define(LOCKS_TABLE, merle_pool_locks). start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). @@ -75,29 +75,47 @@ get_closest_pid(round_robin, Name) -> checkout_pid(Pid) -> checkout_pid(Pid, false). - + checkout_pid(Pid, CheckBackIn) -> - UseCount = ets:update_counter(?LOCKS_TABLE, {Pid, use_count}, 1), + UseCount = mark_used(Pid), case UseCount =< 1 of true -> Pid; false -> if - CheckBackIn -> checkin_pid(Pid); + CheckBackIn -> mark_unused(Pid); true -> ok end, in_use end. + +checkin_pid(Pid) -> + {_, NowSecs, _} = erlang:now(), + checkin_pid(Pid, NowSecs). -checkin_pid(in_use) -> ok; -checkin_pid(Pid) -> +checkin_pid(in_use, _NowSecs) -> ok; +checkin_pid(Pid, NowSecs) -> case is_process_alive(Pid) of true -> - ets:update_counter(?LOCKS_TABLE, {Pid, use_count}, -1); + UseCount = mark_unused(Pid), + + case UseCount =:= 0 of + true -> + ets:insert(?LOCKS_TABLE, {{Pid, last_unlocked}, trunc(NowSecs)}); + false -> + ok + end; + false -> no_proc end. + +mark_used(Pid) -> + ets:update_counter(?LOCKS_TABLE, {Pid, use_count}, 1). + +mark_unused(Pid) -> + ets:update_counter(?LOCKS_TABLE, {Pid, use_count}, -1). init([]) -> process_flag(trap_exit, true), @@ -127,6 +145,10 @@ handle_call({join, Name, Pid}, _From, S) -> % create an entry that will represent a lock for this pid ets:insert(?LOCKS_TABLE, {{Pid, use_count}, 0}), + % create an entry that will represent the last unlock time for this pid + {_, NowSecs, _} = erlang:now(), + ets:insert(?LOCKS_TABLE, {{Pid, last_unlocked}, NowSecs}), + % insert new pid into the table ets:insert(?PIDS_TABLE, {Name, [Pid | Members]}), From 059bab71f833af56a1972cac5d87c184dbf004c0 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Thu, 23 Aug 2012 13:57:21 -0700 Subject: [PATCH 47/65] Added utility method for retrieving number of available merle connections at any given time --- src/merle_pool.erl | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index 876ab9a..ce5a9e7 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -1,7 +1,11 @@ -module(merle_pool). %% Basically the same functionality than pg2, but process groups are local rather than global. --export([create/1, delete/1, join/2, leave/2, get_members/1, get_closest_pid/2, checkout_pid/1, checkin_pid/1, checkin_pid/2, which_groups/0]). +-export([create/1, delete/1, join/2, leave/2, + get_members/1, count_available/1, + get_closest_pid/2, checkout_pid/1, + checkin_pid/1, checkin_pid/2, + which_groups/0]). -export([start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). @@ -42,9 +46,32 @@ get_members(Name) -> case ets:lookup(?PIDS_TABLE, Name) of [] -> {error, {no_such_group, Name}}; [{Name, Members}] -> Members - end. + end. + which_groups() -> [K || {K, _Members} <- ets:tab2list(?PIDS_TABLE)]. + +count_available(Name) -> + case ets:lookup(?PIDS_TABLE, Name) of + + [] -> {error, {no_such_group, Name}}; + + [{Name, Members}] -> + NumAvail = lists:foldl( + fun(Member, Acc) -> + case ets:lookup(?LOCKS_TABLE, Member) of + [{_, 0}] -> + Acc + 1; + _ -> + Acc + end + end, + 0, + Members + ), + + {length(Members), NumAvail} + end. get_closest_pid(random, Name) -> case ets:lookup(?PIDS_TABLE, Name) of From ebe815d5bf8b7f76e1676fed6ca684349d930694 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Thu, 23 Aug 2012 14:04:55 -0700 Subject: [PATCH 48/65] Debugged count_available utility method --- src/merle_pool.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index ce5a9e7..0257140 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -59,8 +59,8 @@ count_available(Name) -> [{Name, Members}] -> NumAvail = lists:foldl( fun(Member, Acc) -> - case ets:lookup(?LOCKS_TABLE, Member) of - [{_, 0}] -> + case ets:lookup(?LOCKS_TABLE, {Member, use_count}) of + [{{Member, use_count}, 0}] -> Acc + 1; _ -> Acc From 1eae31a282259201c717ce4fa09b464c78005529 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Thu, 23 Aug 2012 16:53:20 -0700 Subject: [PATCH 49/65] Bug fix to monitoring call --- src/merle_pool.erl | 4 +++- src/merle_watcher.erl | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index 0257140..bebf7a5 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -132,7 +132,9 @@ checkin_pid(Pid, NowSecs) -> ets:insert(?LOCKS_TABLE, {{Pid, last_unlocked}, trunc(NowSecs)}); false -> ok - end; + end, + + UseCount; false -> no_proc diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index 3bd2656..6f591a7 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -36,7 +36,7 @@ merle_connection(Pid) -> gen_server:call(Pid, mcd_pid). monitor(Pid, OwnerPid) -> - gen_server:call(Pid, {mark_owner, OwnerPid}). + gen_server:call(Pid, {monitor, OwnerPid}). demonitor(Pid) -> gen_server:call(Pid, demonitor). From 83191ce38f6d1debd8570efc953328a14f428335 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Thu, 23 Aug 2012 17:26:10 -0700 Subject: [PATCH 50/65] Adding periodic clean so that we never lose merle connections, even in the event of a bug --- src/merle_pool.erl | 72 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index bebf7a5..dfa4492 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -2,7 +2,7 @@ %% Basically the same functionality than pg2, but process groups are local rather than global. -export([create/1, delete/1, join/2, leave/2, - get_members/1, count_available/1, + get_members/1, count_available/1, clean_locks/0, get_closest_pid/2, checkout_pid/1, checkin_pid/1, checkin_pid/2, which_groups/0]). @@ -12,6 +12,12 @@ -define(PIDS_TABLE, merle_pool_pids). -define(LOCKS_TABLE, merle_pool_locks). +-define(CLEAN_LOCKS_INTERVAL, 10000). % every 10 seconds + +-record(server_state, { + periodic_lock_clean +}). + start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). @@ -73,6 +79,42 @@ count_available(Name) -> {length(Members), NumAvail} end. +clean_locks() -> + L = ets:tab2list(?PIDS_TABLE), + + TotalCleaned = lists:foldl(fun(Pids, Acc) -> Acc + clean_locks(Pids) end, 0, L), + + log4erl:error("Cleaned ~p merle locks", [TotalCleaned]), + + TotalCleaned. + +clean_locks(PoolPids) -> + {_, NowSecs, _} = erlang:now(), + + NumCleaned = lists:foldl( + fun(Pid, Acc) -> + case ets:lookup(?LOCKS_TABLE, {Pid, use_count}) of + [{{Pid, use_count}, 0}] -> + Acc; + _ -> + case ets:lookup(?LOCKS_TABLE, {Pid, last_unlocked}) of + [{{Pid, last_unlocked}, LastUnlocked}] -> + case (LastUnlocked + ?CLEAN_LOCKS_INTERVAL) < NowSecs of + true -> + reset_lock(Pid, NowSecs), + Acc + 1; + false -> Acc + end; + _ -> Acc + end + end + end, + 0, + PoolPids + ), + + NumCleaned. + get_closest_pid(random, Name) -> case ets:lookup(?PIDS_TABLE, Name) of [] -> @@ -145,12 +187,28 @@ mark_used(Pid) -> mark_unused(Pid) -> ets:update_counter(?LOCKS_TABLE, {Pid, use_count}, -1). + + +reset_lock(Pid, NowSecs) -> + % create an entry that will represent a lock for this pid + ets:insert(?LOCKS_TABLE, {{Pid, use_count}, 0}), + + % create an entry that will represent the last unlock time for this pid + ets:insert(?LOCKS_TABLE, {{Pid, last_unlocked}, NowSecs}). + init([]) -> process_flag(trap_exit, true), ets:new(?PIDS_TABLE, [set, public, named_table, {read_concurrency, true}]), ets:new(?LOCKS_TABLE, [set, public, named_table, {write_concurrency, true}]), - {ok, []}. + + PLC = timer:apply_interval(?CLEAN_LOCKS_INTERVAL, merle_pool, clean_locks, []), + + State = #server_state { + periodic_lock_clean = PLC + }, + + {ok, State}. handle_call({create, Name}, _From, S) -> case ets:lookup(?PIDS_TABLE, Name) of @@ -172,11 +230,8 @@ handle_call({join, Name, Pid}, _From, S) -> ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, 1), % create an entry that will represent a lock for this pid - ets:insert(?LOCKS_TABLE, {{Pid, use_count}, 0}), - - % create an entry that will represent the last unlock time for this pid {_, NowSecs, _} = erlang:now(), - ets:insert(?LOCKS_TABLE, {{Pid, last_unlocked}, NowSecs}), + reset_lock(Pid, NowSecs), % insert new pid into the table ets:insert(?PIDS_TABLE, {Name, [Pid | Members]}), @@ -217,9 +272,12 @@ handle_info({'EXIT', Pid, _} , S) -> handle_info(_Info, S) -> {noreply, S}. -terminate(_Reason, _S) -> +terminate(_Reason, #server_state{ periodic_lock_clean=PLC }) -> ets:delete(?PIDS_TABLE), ets:delete(?LOCKS_TABLE), + + timer:cancel(PLC), + %%do not unlink, if this fails, dangling processes should be killed ok. From 43afd58f13124bac0630d29ab618db8495d42fad Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Thu, 23 Aug 2012 17:29:11 -0700 Subject: [PATCH 51/65] Adding periodic clean so that we never lose merle connections, even in the event of a bug --- src/merle_pool.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index dfa4492..73c2c24 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -82,7 +82,7 @@ count_available(Name) -> clean_locks() -> L = ets:tab2list(?PIDS_TABLE), - TotalCleaned = lists:foldl(fun(Pids, Acc) -> Acc + clean_locks(Pids) end, 0, L), + TotalCleaned = lists:foldl(fun({_, Pids}, Acc) -> Acc + clean_locks(Pids) end, 0, L), log4erl:error("Cleaned ~p merle locks", [TotalCleaned]), From f76ea9ee81f6e916ccd2a572eec004cd95ddd582 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 24 Aug 2012 10:25:31 -0700 Subject: [PATCH 52/65] Added proper calculation of now to merle code --- src/merle_pool.erl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index 73c2c24..f1f9219 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -89,7 +89,7 @@ clean_locks() -> TotalCleaned. clean_locks(PoolPids) -> - {_, NowSecs, _} = erlang:now(), + NowSecs = now_secs(), NumCleaned = lists:foldl( fun(Pid, Acc) -> @@ -160,7 +160,7 @@ checkout_pid(Pid, CheckBackIn) -> end. checkin_pid(Pid) -> - {_, NowSecs, _} = erlang:now(), + NowSecs = now_secs(), checkin_pid(Pid, NowSecs). checkin_pid(in_use, _NowSecs) -> ok; @@ -284,6 +284,10 @@ terminate(_Reason, #server_state{ periodic_lock_clean=PLC }) -> %%%----------------------------------------------------------------- %%% Internal functions %%%----------------------------------------------------------------- +now_secs() -> + {NowMegaSecs, NowSecs, _} = erlang:now(), + (1.0e+6 * NowMegaSecs) + NowSecs. + del_member(Pid) -> L = ets:tab2list(?PIDS_TABLE), lists:foreach(fun(Elem) -> del_member_func(Elem, Pid) end, L). From f382bbe5445acccb174a8510441475d78cdf08f5 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 24 Aug 2012 10:28:51 -0700 Subject: [PATCH 53/65] One more time calc --- src/merle_pool.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index f1f9219..41f336c 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -230,7 +230,7 @@ handle_call({join, Name, Pid}, _From, S) -> ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, 1), % create an entry that will represent a lock for this pid - {_, NowSecs, _} = erlang:now(), + NowSecs = now_secs(), reset_lock(Pid, NowSecs), % insert new pid into the table From 7aec9526d98f9ddb5ec1b6a4427dd91dbce65920 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 24 Aug 2012 10:49:38 -0700 Subject: [PATCH 54/65] Now properly cleaning against proper interval --- src/merle_pool.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index 41f336c..032c3e2 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -90,6 +90,7 @@ clean_locks() -> clean_locks(PoolPids) -> NowSecs = now_secs(), + CleanLocksIntervalSecs = ?CLEAN_LOCKS_INTERVAL div 1000, NumCleaned = lists:foldl( fun(Pid, Acc) -> @@ -99,7 +100,7 @@ clean_locks(PoolPids) -> _ -> case ets:lookup(?LOCKS_TABLE, {Pid, last_unlocked}) of [{{Pid, last_unlocked}, LastUnlocked}] -> - case (LastUnlocked + ?CLEAN_LOCKS_INTERVAL) < NowSecs of + case (LastUnlocked + CleanLocksIntervalSecs) < NowSecs of true -> reset_lock(Pid, NowSecs), Acc + 1; From 9381814eb07b055d29461aa3b53897d7d10919fa Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 24 Aug 2012 11:45:41 -0700 Subject: [PATCH 55/65] Added threshold in RR index incrementing --- src/merle_pool.erl | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index 032c3e2..fe02fad 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -130,15 +130,18 @@ get_closest_pid(random, Name) -> checkout_pid(Pid, true) end; -get_closest_pid(round_robin, Name) -> - % Get the round robin index - RoundRobinIndex = ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, 1), - +get_closest_pid(round_robin, Name) -> case ets:lookup(?PIDS_TABLE, Name) of [] -> {error, {no_process, Name}}; - [{Name, Members}] -> - Pid = lists:nth((RoundRobinIndex rem length(Members)) + 1, Members), + [{Name, Members}] -> + + MembersLen = length(Members), + + % Get the round robin index + RRIndex = ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, {2, 1, MembersLen, 1}), + + Pid = lists:nth(RRIndex, Members), checkout_pid(Pid, true) end. From 0f0102403459c291101fbb2eabf05d0d37665c3d Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 24 Aug 2012 12:08:09 -0700 Subject: [PATCH 56/65] Shifting the round robin index to include threshold --- src/merle_pool.erl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index fe02fad..9fe1f3b 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -116,6 +116,9 @@ clean_locks(PoolPids) -> NumCleaned. +shift_rr_index(Name, MembersLen) -> + ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, {2, 1, MembersLen, 1}). + get_closest_pid(random, Name) -> case ets:lookup(?PIDS_TABLE, Name) of [] -> @@ -139,7 +142,7 @@ get_closest_pid(round_robin, Name) -> MembersLen = length(Members), % Get the round robin index - RRIndex = ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, {2, 1, MembersLen, 1}), + RRIndex = shift_rr_index(Name, MembersLen), Pid = lists:nth(RRIndex, Members), @@ -217,7 +220,7 @@ init([]) -> handle_call({create, Name}, _From, S) -> case ets:lookup(?PIDS_TABLE, Name) of [] -> - ets:insert(?LOCKS_TABLE, {{Name, rr_index}, 0}), + ets:insert(?LOCKS_TABLE, {{Name, rr_index}, 1}), ets:insert(?PIDS_TABLE, {Name, []}); _ -> ok @@ -231,7 +234,7 @@ handle_call({join, Name, Pid}, _From, S) -> [{Name, Members}] -> % NOTE: skip one index since we are about to grow the list, this prevents collisions - ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, 1), + shift_rr_index(Name, length(Members)), % create an entry that will represent a lock for this pid NowSecs = now_secs(), From 29ebe6e457d20d53fb9283d140587cad62055b2b Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 24 Aug 2012 14:42:56 -0700 Subject: [PATCH 57/65] Made some changes to the way join initializes state --- src/merle_cluster.erl | 15 ++++++++++++--- src/merle_pool.erl | 7 ++++++- src/merle_watcher.erl | 21 +++++++++------------ 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 7b0449b..a2118e2 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -40,19 +40,28 @@ configure(MemcachedHosts, ConnectionsPerHost) -> ). -exec(Key, Fun, FullDefault, Now) -> +exec(Key, Fun, Default, Now) -> S = merle_cluster_dynamic:get_server(Key), case merle_pool:get_closest_pid(round_robin, S) of in_use -> - FullDefault; + Default; P -> merle_watcher:monitor(P, self()), MC = merle_watcher:merle_connection(P), - Value = Fun(MC, Key), + Value = case MC of + uninitialized -> + log4erl:error("Merle watcher has uninitialized connection, shouldn't happen."), + Default; + undefined -> + log4erl:error("Merle watcher has undefined connection, shouldn't happen."), + Default; + _ -> + Fun(MC, Key) + end, merle_watcher:demonitor(P), diff --git a/src/merle_pool.erl b/src/merle_pool.erl index 9fe1f3b..7736d42 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -238,7 +238,12 @@ handle_call({join, Name, Pid}, _From, S) -> % create an entry that will represent a lock for this pid NowSecs = now_secs(), - reset_lock(Pid, NowSecs), + + % create an entry that will represent a lock for this pid + ets:insert(?LOCKS_TABLE, {{Pid, use_count}, 1}), + + % create an entry that will represent the last unlock time for this pid + ets:insert(?LOCKS_TABLE, {{Pid, last_unlocked}, NowSecs}), % insert new pid into the table ets:insert(?PIDS_TABLE, {Name, [Pid | Members]}), diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index 6f591a7..d6c3d62 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -7,7 +7,7 @@ -define(RESTART_INTERVAL, 5000). %% retry each 5 seconds. -record(state, { - mcd_pid, + mcd_pid = uninitialized, host, port, monitor @@ -26,11 +26,9 @@ init([Host, Port]) -> merle_pool:join({Host, Port}, SelfPid), - merle_pool:checkout_pid(SelfPid), - - SelfPid ! connect, + SelfPid ! timeout, - {ok, #state{mcd_pid = undefined, host = Host, port = Port}}. + {ok, #state{host = Host, port = Port}}. merle_connection(Pid) -> gen_server:call(Pid, mcd_pid). @@ -67,7 +65,11 @@ handle_call(demonitor, _From, State = #state{monitor = PrevMonitor}) -> handle_call(_Call, _From, S) -> {reply, ok, S}. -handle_info('connect', #state{mcd_pid = undefined, host = Host, port = Port} = State) -> +handle_info('timeout', #state{mcd_pid = uninitialized} = State) -> + handle_info('connect', State); +handle_info('timeout', #state{mcd_pid = undefined} = State) -> + handle_info('connect', State); +handle_info('connect', #state{host = Host, port = Port} = State) -> case merle:connect(Host, Port) of {ok, Pid} -> @@ -76,11 +78,6 @@ handle_info('connect', #state{mcd_pid = undefined, host = Host, port = Port} = S {noreply, State#state{mcd_pid = Pid}}; {error, Reason} -> - receive - {'EXIT', _ , _} -> ok - after 2000 -> - ok - end, error_logger:error_report([memcached_not_started, {reason, Reason}, @@ -104,7 +101,7 @@ handle_info({'DOWN', MonitorRef, _, _, _}, #state{monitor=MonitorRef} = S) -> handle_info({'EXIT', Pid, _}, #state{mcd_pid = Pid} = S) -> merle_pool:checkout_pid(self()), - self() ! connect, + self() ! timeout, {noreply, S#state{mcd_pid = undefined}, ?RESTART_INTERVAL}; From 4cf1e3390c32c73390cece2ce1d93037b38bb352 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Fri, 24 Aug 2012 16:26:06 -0700 Subject: [PATCH 58/65] Trying to fix merle initialization bugs --- src/merle_pool.erl | 56 ++++++++++++++++++++++++++++++------------- src/merle_watcher.erl | 6 +++-- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/merle_pool.erl b/src/merle_pool.erl index 7736d42..3d9cae6 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -3,7 +3,8 @@ %% Basically the same functionality than pg2, but process groups are local rather than global. -export([create/1, delete/1, join/2, leave/2, get_members/1, count_available/1, clean_locks/0, - get_closest_pid/2, checkout_pid/1, + get_closest_pid/2, + checkout_indefinitely/1, checkin_pid/1, checkin_pid/2, which_groups/0]). @@ -82,39 +83,58 @@ count_available(Name) -> clean_locks() -> L = ets:tab2list(?PIDS_TABLE), - TotalCleaned = lists:foldl(fun({_, Pids}, Acc) -> Acc + clean_locks(Pids) end, 0, L), + {Cleaned, Connections} = lists:foldl( + fun({_, Pids}, {C, V}) -> + {C2, V2} = clean_locks(Pids), + {C + C2, V + V2} + end, + {0, 0}, + L + ), - log4erl:error("Cleaned ~p merle locks", [TotalCleaned]), + log4erl:error("Cleaned ~p merle locks on ~p valid connections", [Cleaned, Connections]), - TotalCleaned. + {Cleaned, Connections}. clean_locks(PoolPids) -> NowSecs = now_secs(), CleanLocksIntervalSecs = ?CLEAN_LOCKS_INTERVAL div 1000, - NumCleaned = lists:foldl( - fun(Pid, Acc) -> - case ets:lookup(?LOCKS_TABLE, {Pid, use_count}) of + Acc = lists:foldl( + fun(Pid, Acc = {NumCleaned, ValidConnections}) -> + + % clear locks with stale last_unlocked time + NumCleaned2 = case ets:lookup(?LOCKS_TABLE, {Pid, use_count}) of [{{Pid, use_count}, 0}] -> - Acc; + NumCleaned; _ -> case ets:lookup(?LOCKS_TABLE, {Pid, last_unlocked}) of [{{Pid, last_unlocked}, LastUnlocked}] -> case (LastUnlocked + CleanLocksIntervalSecs) < NowSecs of true -> reset_lock(Pid, NowSecs), - Acc + 1; - false -> Acc + NumCleaned + 1; + false -> NumCleaned end; - _ -> Acc + _ -> NumCleaned end - end + end, + + % maintain a count of the number of valid connections + ValidConnections2 = case merle_watcher:merle_connection(Pid) of + uninitialized -> ValidConnections; + undefined -> ValidConnections; + _ -> ValidConnections + 1 + end, + + {NumCleaned2, ValidConnections2} + end, - 0, + {0, 0}, PoolPids ), - NumCleaned. + Acc. shift_rr_index(Name, MembersLen) -> ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, {2, 1, MembersLen, 1}). @@ -149,9 +169,6 @@ get_closest_pid(round_robin, Name) -> checkout_pid(Pid, true) end. -checkout_pid(Pid) -> - checkout_pid(Pid, false). - checkout_pid(Pid, CheckBackIn) -> UseCount = mark_used(Pid), @@ -166,6 +183,11 @@ checkout_pid(Pid, CheckBackIn) -> in_use end. +% Checks out the specified Pid, clears its unlock time so that its lock is never cleaned +checkout_indefinitely(Pid) -> + checkout_pid(Pid, false), + ets:delete(?LOCKS_TABLE, {Pid, last_unlocked}). + checkin_pid(Pid) -> NowSecs = now_secs(), checkin_pid(Pid, NowSecs). diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index d6c3d62..645986e 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -86,7 +86,9 @@ handle_info('connect', #state{host = Host, port = Port} = State) -> {restarting_in, ?RESTART_INTERVAL}] ), - {noreply, State, ?RESTART_INTERVAL} + timer:send_after(?RESTART_INTERVAL, self(), timeout), + + {noreply, State} end; handle_info({'DOWN', MonitorRef, _, _, _}, #state{monitor=MonitorRef} = S) -> @@ -99,7 +101,7 @@ handle_info({'DOWN', MonitorRef, _, _, _}, #state{monitor=MonitorRef} = S) -> {noreply, S#state{monitor = undefined}}; handle_info({'EXIT', Pid, _}, #state{mcd_pid = Pid} = S) -> - merle_pool:checkout_pid(self()), + merle_pool:checkout_indefinitely(self()), self() ! timeout, From 07e19d700329ebba14a8fe482c639691df8d716b Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Tue, 27 Nov 2012 14:04:58 -0800 Subject: [PATCH 59/65] Removing overly verbose error logs from merle --- src/merle_watcher.erl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index 645986e..9e46ced 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -79,12 +79,13 @@ handle_info('connect', #state{host = Host, port = Port} = State) -> {error, Reason} -> - error_logger:error_report([memcached_not_started, - {reason, Reason}, - {host, Host}, - {port, Port}, - {restarting_in, ?RESTART_INTERVAL}] - ), + % This logging is overly noisy on server start. + % error_logger:error_report([memcached_not_started, + % {reason, Reason}, + % {host, Host}, + % {port, Port}, + % {restarting_in, ?RESTART_INTERVAL}] + % ), timer:send_after(?RESTART_INTERVAL, self(), timeout), From 3fdbf4d63ae6813f7bc0880d7f1c143896bf4456 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 2 Jan 2013 13:43:41 -0800 Subject: [PATCH 60/65] No longer greedily initializing all connections upon first start. Instead we initialize on demand. --- .gitignore | 5 ++++- src/merle_cluster.erl | 5 +---- src/merle_watcher.erl | 51 +++++++++++++++++++++++++------------------ 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 775d6a8..4392b40 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -ebin \ No newline at end of file +out +.idea +ebin +*.iml \ No newline at end of file diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index a2118e2..456f9fc 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -53,11 +53,8 @@ exec(Key, Fun, Default, Now) -> MC = merle_watcher:merle_connection(P), Value = case MC of - uninitialized -> - log4erl:error("Merle watcher has uninitialized connection, shouldn't happen."), - Default; undefined -> - log4erl:error("Merle watcher has undefined connection, shouldn't happen."), + log4erl:error("Merle watcher has undefined connection, should initialize connection now."), Default; _ -> Fun(MC, Key) diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl index 9e46ced..8e59543 100644 --- a/src/merle_watcher.erl +++ b/src/merle_watcher.erl @@ -7,10 +7,11 @@ -define(RESTART_INTERVAL, 5000). %% retry each 5 seconds. -record(state, { - mcd_pid = uninitialized, + mcd_pid, host, port, - monitor + monitor, + pid }). start_link([Host, Port]) -> @@ -26,9 +27,7 @@ init([Host, Port]) -> merle_pool:join({Host, Port}, SelfPid), - SelfPid ! timeout, - - {ok, #state{host = Host, port = Port}}. + {ok, #state{host = Host, port = Port, pid = SelfPid}}. merle_connection(Pid) -> gen_server:call(Pid, mcd_pid). @@ -39,6 +38,9 @@ monitor(Pid, OwnerPid) -> demonitor(Pid) -> gen_server:call(Pid, demonitor). +handle_call(mcd_pid, _From, State = #state{mcd_pid = undefined}) -> + self() ! 'connect', + {reply, undefined, State}; handle_call(mcd_pid, _From, State = #state{mcd_pid = McdPid}) -> {reply, McdPid, State}; @@ -65,11 +67,7 @@ handle_call(demonitor, _From, State = #state{monitor = PrevMonitor}) -> handle_call(_Call, _From, S) -> {reply, ok, S}. -handle_info('timeout', #state{mcd_pid = uninitialized} = State) -> - handle_info('connect', State); -handle_info('timeout', #state{mcd_pid = undefined} = State) -> - handle_info('connect', State); -handle_info('connect', #state{host = Host, port = Port} = State) -> +handle_info('connect', #state{host = Host, port = Port, mcd_pid = undefined} = State) -> case merle:connect(Host, Port) of {ok, Pid} -> @@ -80,17 +78,19 @@ handle_info('connect', #state{host = Host, port = Port} = State) -> {error, Reason} -> % This logging is overly noisy on server start. - % error_logger:error_report([memcached_not_started, - % {reason, Reason}, - % {host, Host}, - % {port, Port}, - % {restarting_in, ?RESTART_INTERVAL}] - % ), + error_logger:error_report([memcached_not_started, + {reason, Reason}, + {host, Host}, + {port, Port}, + {restarting_in, ?RESTART_INTERVAL}] + ), - timer:send_after(?RESTART_INTERVAL, self(), timeout), + timer:send_after(?RESTART_INTERVAL, self(), 'connect'), {noreply, State} end; +handle_info('connect', #state{mcd_pid = _McdPid} = State) -> + {noreply, State}; handle_info({'DOWN', MonitorRef, _, _, _}, #state{monitor=MonitorRef} = S) -> log4erl:info("merle_watcher caught a DOWN event"), @@ -102,9 +102,11 @@ handle_info({'DOWN', MonitorRef, _, _, _}, #state{monitor=MonitorRef} = S) -> {noreply, S#state{monitor = undefined}}; handle_info({'EXIT', Pid, _}, #state{mcd_pid = Pid} = S) -> - merle_pool:checkout_indefinitely(self()), + SelfPid = self(), + + merle_pool:checkout_indefinitely(SelfPid), - self() ! timeout, + SelfPid ! 'connect', {noreply, S#state{mcd_pid = undefined}, ?RESTART_INTERVAL}; @@ -116,11 +118,18 @@ handle_info(_Info, S) -> handle_cast(_Cast, S) -> {noreply, S}. -terminate(_Reason, #state{mcd_pid = undefined}) -> +terminate(_Reason, #state{host = Host, port = Port, pid = SelfPid, mcd_pid = undefined}) -> log4erl:error("Merle watcher terminated, mcd pid is empty!"), + + merle_pool:leave({Host, Port}, SelfPid), + ok; -terminate(_Reason, #state{mcd_pid = McdPid}) -> +terminate(_Reason, #state{host = Host, port = Port, pid = SelfPid, mcd_pid = McdPid}) -> log4erl:error("Merle watcher terminated, killing mcd pid!"), + + merle_pool:leave({Host, Port}, SelfPid), + erlang:exit(McdPid, watcher_died), + ok. \ No newline at end of file From 0e7459fc476d1468ac93f0733cd5e67f66f3cb14 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Wed, 2 Jan 2013 17:09:52 -0800 Subject: [PATCH 61/65] Simplified connection pooling logic. Now use status is stored within merle_client itself. merle_pool merely chooses a client, which is then checked out via the exec function --- src/merle_client.erl | 207 ++++++++++++++++++ ...e_watcher_sup.erl => merle_client_sup.erl} | 4 +- src/merle_cluster.erl | 47 ++-- src/merle_pool.erl | 148 ++++--------- src/merle_sup.erl | 4 +- src/merle_watcher.erl | 135 ------------ 6 files changed, 281 insertions(+), 264 deletions(-) create mode 100644 src/merle_client.erl rename src/{merle_watcher_sup.erl => merle_client_sup.erl} (85%) delete mode 100644 src/merle_watcher.erl diff --git a/src/merle_client.erl b/src/merle_client.erl new file mode 100644 index 0000000..b0d4770 --- /dev/null +++ b/src/merle_client.erl @@ -0,0 +1,207 @@ +-module(merle_client). + +-export([start_link/1, init/1, handle_call/3, handle_info/2, handle_cast/2, terminate/2]). + +-export([checkout/3, checkin/1, get_checkout_state/1, get_socket/1]). + +-define(RESTART_INTERVAL, 5000). %% retry each 5 seconds. + +-record(state, { + host, + port, + + socket, % memcached connection socket + + monitor, % represents a monitor bw checking out process and me + + checked_out, % boolean indicating whether this connection is checked out or not + check_out_time % timestamp marking when this connection was checked out +}). + + +start_link([Host, Port]) -> + gen_server:start_link(?MODULE, [Host, Port], []). + + +init([Host, Port]) -> + log4erl:info("Merle watcher initialized!"), + erlang:process_flag(trap_exit, true), + + merle_pool:create({Host, Port}), + merle_pool:join({Host, Port}, self()), + + { + ok, + check_in_state( + #state{ + host = Host, + port = Port + } + ) + }. + + +%% +%% API +%% + + +checkout(Pid, BorrowerPid, CheckoutTime) -> + gen_server:call(Pid, {checkout, BorrowerPid, CheckoutTime}). + + +checkin(Pid) -> + gen_server:call(Pid, checkin). + + +get_checkout_state(Pid) -> + gen_server:call(Pid, get_checkout_state). + + +get_socket(Pid) -> + gen_server:call(Pid, get_socket). + + +%% +%% SERVER CALL HANDLERS +%% + + +%% +%% Handle checkout events. Mark this server as used, and note the time. +%% Bind a monitor with the checking out process. +%% +handle_call({checkout, _, _}, _From, State = #state{checked_out = true}) -> + {reply, busy, State}; +handle_call({checkout, _, _}, _From, State = #state{socket = undefined}) -> + % NOTE: initializes socket when none found + {reply, no_socket, connect_socket(State)}; +handle_call({checkout, BorrowerPid, CheckoutTime}, _From, State = #state{socket = Socket, monitor = PrevMonitor}) -> + % handle any previously existing monitors + case PrevMonitor of + undefined -> + ok; + _ -> + true = erlang:demonitor(PrevMonitor) + end, + + Monitor = erlang:monitor(process, BorrowerPid), + + {reply, Socket, check_out_state(State#state{monitor = Monitor}, CheckoutTime)}; + + +%% +%% Handle checkin events. Demonitor perviously monitored process, and mark as checked in +%% +handle_call(checkin, _From, State = #state{monitor = PrevMonitor}) -> + case PrevMonitor of + undefined -> ok; + _ -> + true = erlang:demonitor(PrevMonitor) + end, + + {reply, ok, check_in_state(State#state{monitor = undefined})}; + + +%% +%% Returns checkout state for the client in question +%% +handle_call(get_checkout_state, _From, State = #state{checked_out = CheckedOut, check_out_time = CheckOutTime}) -> + {reply, {CheckedOut, CheckOutTime}, State}; + + +%% +%% Returns socket for the client in question +%% +handle_call(get_socket, _From, State = #state{socket = Socket}) -> + {reply, Socket, State}; + + +handle_call(_Call, _From, S) -> + {reply, ok, S}. + + +%% +%% Handles 'connect' messages -> initializes socket on host/port, saving a reference +%% +handle_info('connect', #state{host = Host, port = Port, checked_out = true, socket = undefined} = State) -> + case merle:connect(Host, Port) of + {ok, Socket} -> + + {noreply, check_in_state(State#state{socket = Socket})}; + + {error, Reason} -> + error_logger:error_report([memcached_connection_error, + {reason, Reason}, + {host, Host}, + {port, Port}, + {restarting_in, ?RESTART_INTERVAL}] + ), + + timer:send_after(?RESTART_INTERVAL, self(), 'connect'), + + {noreply, State} + end; + + +%% +%% Handles down events from monitored process. Need to check back in if this happens. +%% +handle_info({'DOWN', MonitorRef, _, _, _}, #state{monitor=MonitorRef} = S) -> + log4erl:info("merle_watcher caught a DOWN event"), + + true = erlang:demonitor(MonitorRef), + + {noreply, check_in_state(S#state{monitor = undefined})}; + + +%% +%% Handles exit events on the memcached socket. If this occurs need to reconnect. +%% +handle_info({'EXIT', Socket, _}, S = #state{socket = Socket}) -> + {noreply, connect_socket(S), ?RESTART_INTERVAL}; + + +handle_info(_Info, S) -> + error_logger:warning_report([{merle_watcher, self()}, {unknown_info, _Info}]), + {noreply, S}. + + +handle_cast(_Cast, S) -> + {noreply, S}. + + +terminate(_Reason, #state{socket = undefined}) -> + log4erl:error("Merle watcher terminated, socket is empty!"), + ok; + +terminate(_Reason, #state{socket = Socket}) -> + log4erl:error("Merle watcher terminated, killing socket!"), + erlang:exit(Socket, watcher_died), + ok. + +%% +%% HELPER FUNCTIONS +%% + +connect_socket(State = #state{}) -> + self() ! 'connect', + check_out_state_indefinitely(State#state{socket = undefined}). + + +check_out_state_indefinitely(State = #state{}) -> + check_out_state(State = #state{}, indefinite). + + +check_out_state(State = #state{}, CheckOutTime) -> + State#state{ + checked_out = true, + check_out_time = CheckOutTime + }. + + +check_in_state(State = #state{}) -> + State#state{ + checked_out = false, + check_out_time = undefined + }. \ No newline at end of file diff --git a/src/merle_watcher_sup.erl b/src/merle_client_sup.erl similarity index 85% rename from src/merle_watcher_sup.erl rename to src/merle_client_sup.erl index 193e690..fcb022f 100644 --- a/src/merle_watcher_sup.erl +++ b/src/merle_client_sup.erl @@ -1,4 +1,4 @@ --module(merle_watcher_sup). +-module(merle_client_sup). -export([start_link/2, init/1]). @@ -17,6 +17,6 @@ start_child(N) -> supervisor:start_child(?MODULE, [N]). init([]) -> - MCDSpec = {mcd, {merle_watcher, start_link, []}, + MCDSpec = {mcd, {merle_client, start_link, []}, permanent, 5000, worker, dynamic}, {ok, {{simple_one_for_one, 10, 10}, [MCDSpec]}}. diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 456f9fc..ad30980 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -31,7 +31,7 @@ configure(MemcachedHosts, ConnectionsPerHost) -> fun([Host, Port]) -> lists:foreach( fun(_) -> - supervisor:start_child(merle_watcher_sup, [[Host, Port]]) + merle_client_sup:start_child([Host, Port]) end, lists:seq(1, ConnectionsPerHost) ) @@ -42,28 +42,33 @@ configure(MemcachedHosts, ConnectionsPerHost) -> exec(Key, Fun, Default, Now) -> S = merle_cluster_dynamic:get_server(Key), + exec_on_client( + merle_pool:get_client(round_robin, S, self()), + Key, + Fun, + Default, + Now + ). - case merle_pool:get_closest_pid(round_robin, S) of - in_use -> - Default; - - P -> - merle_watcher:monitor(P, self()), - - MC = merle_watcher:merle_connection(P), - - Value = case MC of - undefined -> - log4erl:error("Merle watcher has undefined connection, should initialize connection now."), - Default; - _ -> - Fun(MC, Key) - end, - merle_watcher:demonitor(P), +exec_on_client({error, Error}, _Key, _Fun, Default, _Now) -> + log4erl:error("Error finding merle client: ~r~n, returning default value", [Error]), + Default; +exec_on_client(undefined, _Key, _Fun, Default, _Now) -> + log4erl:error("Undefined merle client, returning default value"), + Default; +exec_on_client(Client, Key, Fun, Default, Now) -> + exec_on_socket(merle_client:checkout(Client, self(), Now), Client, Key, Fun, Default). - merle_pool:checkin_pid(P, Now), - Value +exec_on_socket(no_socket, _Client, _Key, _Fun, Default) -> + log4erl:error("Designated merle connection has no socket, returning default value"), + Default; +exec_on_socket(busy, _Client, _Key, _Fun, Default) -> + log4erl:error("Designated merle connection is in use, returning default value"), + Default; +exec_on_socket(Socket, Client, Key, Fun, _Default) -> + Value = Fun(Socket, Key), + merle_client:checkin(Client), + Value. - end. \ No newline at end of file diff --git a/src/merle_pool.erl b/src/merle_pool.erl index 3d9cae6..7997758 100644 --- a/src/merle_pool.erl +++ b/src/merle_pool.erl @@ -3,15 +3,13 @@ %% Basically the same functionality than pg2, but process groups are local rather than global. -export([create/1, delete/1, join/2, leave/2, get_members/1, count_available/1, clean_locks/0, - get_closest_pid/2, - checkout_indefinitely/1, - checkin_pid/1, checkin_pid/2, + get_client/2, which_groups/0]). -export([start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). -define(PIDS_TABLE, merle_pool_pids). --define(LOCKS_TABLE, merle_pool_locks). +-define(INDICES_TABLE, merle_pool_indices). -define(CLEAN_LOCKS_INTERVAL, 10000). % every 10 seconds @@ -66,8 +64,8 @@ count_available(Name) -> [{Name, Members}] -> NumAvail = lists:foldl( fun(Member, Acc) -> - case ets:lookup(?LOCKS_TABLE, {Member, use_count}) of - [{{Member, use_count}, 0}] -> + case merle_client:get_checkout_state(Member) of + {false, _} -> Acc + 1; _ -> Acc @@ -96,50 +94,53 @@ clean_locks() -> {Cleaned, Connections}. -clean_locks(PoolPids) -> +clean_locks(Clients) -> NowSecs = now_secs(), CleanLocksIntervalSecs = ?CLEAN_LOCKS_INTERVAL div 1000, Acc = lists:foldl( - fun(Pid, Acc = {NumCleaned, ValidConnections}) -> + fun(Client, {NumCleaned, ValidConnections}) -> % clear locks with stale last_unlocked time - NumCleaned2 = case ets:lookup(?LOCKS_TABLE, {Pid, use_count}) of - [{{Pid, use_count}, 0}] -> + NumCleaned2 = case merle_client:get_checkout_state(Client) of + {false, _} -> NumCleaned; - _ -> - case ets:lookup(?LOCKS_TABLE, {Pid, last_unlocked}) of - [{{Pid, last_unlocked}, LastUnlocked}] -> - case (LastUnlocked + CleanLocksIntervalSecs) < NowSecs of - true -> - reset_lock(Pid, NowSecs), - NumCleaned + 1; - false -> NumCleaned - end; - _ -> NumCleaned + + {true, indefinite} -> + NumCleaned; + + {true, CheckOutTime} -> + case (CheckOutTime + CleanLocksIntervalSecs) < NowSecs of + true -> + merle_client:checkin(Client), + NumCleaned + 1; + false -> + NumCleaned end end, % maintain a count of the number of valid connections - ValidConnections2 = case merle_watcher:merle_connection(Pid) of - uninitialized -> ValidConnections; + ValidConnections2 = case merle_client:get_socket(Client) of undefined -> ValidConnections; _ -> ValidConnections + 1 end, {NumCleaned2, ValidConnections2} + end, - end, - {0, 0}, - PoolPids + {0, 0}, + + Clients ), Acc. + shift_rr_index(Name, MembersLen) -> - ets:update_counter(?LOCKS_TABLE, {Name, rr_index}, {2, 1, MembersLen, 1}). + ets:update_counter(?INDICES_TABLE, {Name, rr_index}, {2, 1, MembersLen, 1}). -get_closest_pid(random, Name) -> + +get_client(random, Name) -> case ets:lookup(?PIDS_TABLE, Name) of [] -> {error, {no_process, Name}}; @@ -150,10 +151,10 @@ get_closest_pid(random, Name) -> Pid = lists:nth((X rem length(Members)) +1, Members), - checkout_pid(Pid, true) + Pid end; -get_closest_pid(round_robin, Name) -> +get_client(round_robin, Name) -> case ets:lookup(?PIDS_TABLE, Name) of [] -> {error, {no_process, Name}}; @@ -164,72 +165,17 @@ get_closest_pid(round_robin, Name) -> % Get the round robin index RRIndex = shift_rr_index(Name, MembersLen), - Pid = lists:nth(RRIndex, Members), - - checkout_pid(Pid, true) - end. - -checkout_pid(Pid, CheckBackIn) -> - UseCount = mark_used(Pid), - - case UseCount =< 1 of - true -> Pid; - false -> - if - CheckBackIn -> mark_unused(Pid); - true -> ok - end, - - in_use - end. - -% Checks out the specified Pid, clears its unlock time so that its lock is never cleaned -checkout_indefinitely(Pid) -> - checkout_pid(Pid, false), - ets:delete(?LOCKS_TABLE, {Pid, last_unlocked}). - -checkin_pid(Pid) -> - NowSecs = now_secs(), - checkin_pid(Pid, NowSecs). - -checkin_pid(in_use, _NowSecs) -> ok; -checkin_pid(Pid, NowSecs) -> - case is_process_alive(Pid) of - true -> - UseCount = mark_unused(Pid), - - case UseCount =:= 0 of - true -> - ets:insert(?LOCKS_TABLE, {{Pid, last_unlocked}, trunc(NowSecs)}); - false -> - ok - end, - - UseCount; - - false -> - no_proc + lists:nth(RRIndex, Members) end. - -mark_used(Pid) -> - ets:update_counter(?LOCKS_TABLE, {Pid, use_count}, 1). - -mark_unused(Pid) -> - ets:update_counter(?LOCKS_TABLE, {Pid, use_count}, -1). - -reset_lock(Pid, NowSecs) -> - % create an entry that will represent a lock for this pid - ets:insert(?LOCKS_TABLE, {{Pid, use_count}, 0}), - - % create an entry that will represent the last unlock time for this pid - ets:insert(?LOCKS_TABLE, {{Pid, last_unlocked}, NowSecs}). - +%% +%% SERVER FUNCTIONS +%% init([]) -> process_flag(trap_exit, true), ets:new(?PIDS_TABLE, [set, public, named_table, {read_concurrency, true}]), - ets:new(?LOCKS_TABLE, [set, public, named_table, {write_concurrency, true}]), + ets:new(?INDICES_TABLE, [set, public, named_table, {write_concurrency, true}]), PLC = timer:apply_interval(?CLEAN_LOCKS_INTERVAL, merle_pool, clean_locks, []), @@ -242,7 +188,7 @@ init([]) -> handle_call({create, Name}, _From, S) -> case ets:lookup(?PIDS_TABLE, Name) of [] -> - ets:insert(?LOCKS_TABLE, {{Name, rr_index}, 1}), + ets:insert(?INDICES_TABLE, {{Name, rr_index}, 1}), ets:insert(?PIDS_TABLE, {Name, []}); _ -> ok @@ -258,21 +204,12 @@ handle_call({join, Name, Pid}, _From, S) -> % NOTE: skip one index since we are about to grow the list, this prevents collisions shift_rr_index(Name, length(Members)), - % create an entry that will represent a lock for this pid - NowSecs = now_secs(), - - % create an entry that will represent a lock for this pid - ets:insert(?LOCKS_TABLE, {{Pid, use_count}, 1}), - - % create an entry that will represent the last unlock time for this pid - ets:insert(?LOCKS_TABLE, {{Pid, last_unlocked}, NowSecs}), - % insert new pid into the table ets:insert(?PIDS_TABLE, {Name, [Pid | Members]}), + % NOTE: link processes on join, so that if client dies, we remove it from the pool link(Pid), - %%TODO: add pid to linked ones on state.. {reply, ok, S} end; @@ -308,16 +245,19 @@ handle_info(_Info, S) -> terminate(_Reason, #server_state{ periodic_lock_clean=PLC }) -> ets:delete(?PIDS_TABLE), - ets:delete(?LOCKS_TABLE), + ets:delete(?INDICES_TABLE), timer:cancel(PLC), %%do not unlink, if this fails, dangling processes should be killed ok. -%%%----------------------------------------------------------------- -%%% Internal functions -%%%----------------------------------------------------------------- + + +%% +%% HELPER FUNCTIONS +%% + now_secs() -> {NowMegaSecs, NowSecs, _} = erlang:now(), (1.0e+6 * NowMegaSecs) + NowSecs. diff --git a/src/merle_sup.erl b/src/merle_sup.erl index ceb7689..c48add1 100644 --- a/src/merle_sup.erl +++ b/src/merle_sup.erl @@ -15,8 +15,8 @@ init([Instances, ConnectionsPerInstance]) -> }, MerleWatcherSup = - {merle_watcher_sup, - {merle_watcher_sup, start_link, [Instances, ConnectionsPerInstance]}, + {merle_client_sup, + {merle_client_sup, start_link, [Instances, ConnectionsPerInstance]}, permanent, 5000, supervisor, dynamic }, diff --git a/src/merle_watcher.erl b/src/merle_watcher.erl deleted file mode 100644 index 8e59543..0000000 --- a/src/merle_watcher.erl +++ /dev/null @@ -1,135 +0,0 @@ --module(merle_watcher). - --export([start_link/1, init/1, handle_call/3, handle_info/2, handle_cast/2, terminate/2]). - --export([merle_connection/1, monitor/2, demonitor/1]). - --define(RESTART_INTERVAL, 5000). %% retry each 5 seconds. - --record(state, { - mcd_pid, - host, - port, - monitor, - pid -}). - -start_link([Host, Port]) -> - gen_server:start_link(?MODULE, [Host, Port], []). - -init([Host, Port]) -> - log4erl:info("Merle watcher initialized!"), - erlang:process_flag(trap_exit, true), - - SelfPid = self(), - - merle_pool:create({Host, Port}), - - merle_pool:join({Host, Port}, SelfPid), - - {ok, #state{host = Host, port = Port, pid = SelfPid}}. - -merle_connection(Pid) -> - gen_server:call(Pid, mcd_pid). - -monitor(Pid, OwnerPid) -> - gen_server:call(Pid, {monitor, OwnerPid}). - -demonitor(Pid) -> - gen_server:call(Pid, demonitor). - -handle_call(mcd_pid, _From, State = #state{mcd_pid = undefined}) -> - self() ! 'connect', - {reply, undefined, State}; -handle_call(mcd_pid, _From, State = #state{mcd_pid = McdPid}) -> - {reply, McdPid, State}; - -handle_call({monitor, MonitorPid}, _From, State = #state{monitor = PrevMonitor}) -> - case PrevMonitor of - undefined -> ok; - _ -> - true = erlang:demonitor(PrevMonitor) - end, - - Monitor = erlang:monitor(process, MonitorPid), - - {reply, ok, State#state{monitor = Monitor}}; - -handle_call(demonitor, _From, State = #state{monitor = PrevMonitor}) -> - case PrevMonitor of - undefined -> ok; - _ -> - true = erlang:demonitor(PrevMonitor) - end, - - {reply, ok, State#state{monitor = undefined}}; - -handle_call(_Call, _From, S) -> - {reply, ok, S}. - -handle_info('connect', #state{host = Host, port = Port, mcd_pid = undefined} = State) -> - case merle:connect(Host, Port) of - {ok, Pid} -> - - merle_pool:checkin_pid(self()), - - {noreply, State#state{mcd_pid = Pid}}; - - {error, Reason} -> - - % This logging is overly noisy on server start. - error_logger:error_report([memcached_not_started, - {reason, Reason}, - {host, Host}, - {port, Port}, - {restarting_in, ?RESTART_INTERVAL}] - ), - - timer:send_after(?RESTART_INTERVAL, self(), 'connect'), - - {noreply, State} - end; -handle_info('connect', #state{mcd_pid = _McdPid} = State) -> - {noreply, State}; - -handle_info({'DOWN', MonitorRef, _, _, _}, #state{monitor=MonitorRef} = S) -> - log4erl:info("merle_watcher caught a DOWN event"), - - merle_pool:checkin_pid(self()), - - true = erlang:demonitor(MonitorRef), - - {noreply, S#state{monitor = undefined}}; - -handle_info({'EXIT', Pid, _}, #state{mcd_pid = Pid} = S) -> - SelfPid = self(), - - merle_pool:checkout_indefinitely(SelfPid), - - SelfPid ! 'connect', - - {noreply, S#state{mcd_pid = undefined}, ?RESTART_INTERVAL}; - -handle_info(_Info, S) -> - error_logger:warning_report([{merle_watcher, self()}, {unknown_info, _Info}]), - - {noreply, S}. - -handle_cast(_Cast, S) -> - {noreply, S}. - -terminate(_Reason, #state{host = Host, port = Port, pid = SelfPid, mcd_pid = undefined}) -> - log4erl:error("Merle watcher terminated, mcd pid is empty!"), - - merle_pool:leave({Host, Port}, SelfPid), - - ok; - -terminate(_Reason, #state{host = Host, port = Port, pid = SelfPid, mcd_pid = McdPid}) -> - log4erl:error("Merle watcher terminated, killing mcd pid!"), - - merle_pool:leave({Host, Port}, SelfPid), - - erlang:exit(McdPid, watcher_died), - - ok. \ No newline at end of file From f5f6e119ccdeed3274540df46393a2542c2fb464 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Thu, 3 Jan 2013 16:01:42 -0800 Subject: [PATCH 62/65] Some bug fixes --- src/merle_client.erl | 2 +- src/merle_cluster.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/merle_client.erl b/src/merle_client.erl index b0d4770..6dbcd6f 100644 --- a/src/merle_client.erl +++ b/src/merle_client.erl @@ -190,7 +190,7 @@ connect_socket(State = #state{}) -> check_out_state_indefinitely(State = #state{}) -> - check_out_state(State = #state{}, indefinite). + check_out_state(State, indefinite). check_out_state(State = #state{}, CheckOutTime) -> diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index ad30980..458a23e 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -43,7 +43,7 @@ configure(MemcachedHosts, ConnectionsPerHost) -> exec(Key, Fun, Default, Now) -> S = merle_cluster_dynamic:get_server(Key), exec_on_client( - merle_pool:get_client(round_robin, S, self()), + merle_pool:get_client(round_robin, S), Key, Fun, Default, From 2e7323ff1303abec38af4f82fe6b69bfa426fe0c Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Thu, 10 Jan 2013 14:00:14 -0800 Subject: [PATCH 63/65] Returning result along with merle state upon call to merle:exec --- src/merle.erl | 6 +++--- src/merle_cluster.erl | 25 ++++++++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/merle.erl b/src/merle.erl index bc7bfc5..0ba12ab 100644 --- a/src/merle.erl +++ b/src/merle.erl @@ -121,8 +121,8 @@ getkeys(Ref, Keys, Timeout) when is_list(Keys) -> %% @doc used in conjunction with incr_counter to retrieve an integer value from cache getcounter(Ref, Key, Timeout) -> case getkey(Ref, Key, Timeout) of - {error, _} -> undefined; - {ok, NumberBin} -> list_to_integer(string:strip(binary_to_list(NumberBin))) + {error, Error} -> {error, Error}; + {ok, NumberBin} -> {ok, list_to_integer(string:strip(binary_to_list(NumberBin)))} end. %% @doc retrieve value based off of key for use with cas @@ -254,7 +254,7 @@ incr_counter(Ref, Key, Value, ExpTime, Timeout) -> incr_counter(Ref, Key, Value, ExpTime, Timeout, 0). incr_counter(_Ref, _Key, _Value, _ExpTime, _Timeout, ?MAX_INCR_TRIES) -> - not_stored; + {error, not_stored}; incr_counter(Ref, Key, Value, ExpTime, Timeout, NumTry) -> Flag = random:uniform(?RANDOM_MAX), case incr(Ref, Key, Value, Timeout) of diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index a2118e2..2f5ff7f 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -44,8 +44,10 @@ exec(Key, Fun, Default, Now) -> S = merle_cluster_dynamic:get_server(Key), case merle_pool:get_closest_pid(round_robin, S) of + in_use -> - Default; + log4erl:error("Merle watcher has uninitialized connection, shouldn't happen."), + {in_use, Default}; P -> merle_watcher:monitor(P, self()), @@ -55,12 +57,25 @@ exec(Key, Fun, Default, Now) -> Value = case MC of uninitialized -> log4erl:error("Merle watcher has uninitialized connection, shouldn't happen."), - Default; + {uninitialized_socket, Default}; + undefined -> - log4erl:error("Merle watcher has undefined connection, shouldn't happen."), - Default; + log4erl:error("Merle watcher has undefined connection."), + {no_socket, Default}; + _ -> - Fun(MC, Key) + + % dispatch to the passed function + case Fun(MC, Key) of + + {error, Error} -> + log4erl:error("Merle encountered error."), + {Error, Default}; + + {ok, Value} -> + {ok, Value} + + end end, merle_watcher:demonitor(P), From 29c93f74fbf427980c8fded1c2fc8165d5ca2e0b Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Thu, 10 Jan 2013 14:30:14 -0800 Subject: [PATCH 64/65] Fixed a merle code bug --- src/merle_cluster.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 2f5ff7f..30b22de 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -54,7 +54,7 @@ exec(Key, Fun, Default, Now) -> MC = merle_watcher:merle_connection(P), - Value = case MC of + FinalValue = case MC of uninitialized -> log4erl:error("Merle watcher has uninitialized connection, shouldn't happen."), {uninitialized_socket, Default}; @@ -82,6 +82,6 @@ exec(Key, Fun, Default, Now) -> merle_pool:checkin_pid(P, Now), - Value + FinalValue end. \ No newline at end of file From 592f5d8ccb29492946bcfb4a63e04386131fb596 Mon Sep 17 00:00:00 2001 From: jesse-ad Date: Thu, 10 Jan 2013 16:19:33 -0800 Subject: [PATCH 65/65] Turned down log4erl logging on merle errors --- src/merle_cluster.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/merle_cluster.erl b/src/merle_cluster.erl index 773a8b1..55ad4de 100644 --- a/src/merle_cluster.erl +++ b/src/merle_cluster.erl @@ -69,7 +69,7 @@ exec_on_socket(busy, _Client, _Key, _Fun, Default) -> exec_on_socket(Socket, Client, Key, Fun, Default) -> FinalValue = case Fun(Socket, Key) of {error, Error} -> - log4erl:error("Merle encountered error, returning default value"), + log4erl:info("Merle encountered error ~p, returning default value", [Error]), {Error, Default}; {ok, Value} -> {ok, Value}