Packages

Client interface to MongoDB, also known as the driver. See www.mongodb.org

Current section

Files

Jump to
mongodb_erlang src connection mc_worker.erl
Raw

src/connection/mc_worker.erl

-module(mc_worker).
-behaviour(gen_server).
-include("mongo_protocol.hrl").
-define(WRITE(Req), is_record(Req, insert); is_record(Req, update); is_record(Req, delete)).
-define(READ(Req), is_record(Request, 'query'); is_record(Request, getmore)).
-export([start_link/1, disconnect/1, hibernate/1]).
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-record(state, {
socket :: gen_tcp:socket() | ssl:sslsocket(),
request_storage = #{} :: map(),
buffer = <<>> :: binary(),
conn_state :: conn_state(),
hibernate_timer :: reference() | undefined,
next_req_fun :: fun(),
net_module :: ssl | gen_tcp
}).
-spec start_link(proplists:proplist()) -> {ok, pid()}.
start_link(Options) ->
proc_lib:start_link(?MODULE, init, [Options]).
%% Make worker to go into hibernate. Any next call will wake it.
%% It should be done if you have problems with memory while fetching > 64B binaries from db.
-spec hibernate(pid()) -> ok.
hibernate(#{pool := Worker}) ->
hibernate(Worker);
hibernate(Worker) ->
gen_server:cast(Worker, hibernate).
%% halt worker, close tcp connection
disconnect(Worker) ->
gen_server:cast(Worker, halt).
init(Options) ->
case mc_worker_logic:connect_to_database(Options) of
{ok, Socket} ->
proc_lib:init_ack({ok, self()}),
ConnState = form_state(Options),
try_register(Options),
NetModule = get_set_opts_module(Options),
Login = mc_utils:get_value(login, Options),
Password = mc_utils:get_value(password, Options),
NextReqFun = mc_utils:get_value(next_req_fun, Options, fun() -> ok end),
auth_if_credentials(Socket, ConnState, NetModule, Login, Password),
gen_server:enter_loop(?MODULE, [],
#state{socket = Socket,
conn_state = ConnState,
net_module = NetModule,
next_req_fun = NextReqFun});
Error ->
proc_lib:init_ack(Error)
end.
handle_call(NewState, _, State = #state{conn_state = OldState}) when is_record(NewState, conn_state) -> % update state, return old
{reply, {ok, OldState}, State#state{conn_state = NewState}};
handle_call(#ensure_index{collection = Coll, index_spec = IndexSpec}, _, State) -> % ensure index request with insert request
#state{conn_state = ConnState, socket = Socket, net_module = NetModule} = State,
Index = mc_worker_logic:ensure_index(IndexSpec, ConnState#conn_state.database, Coll),
{ok, _, _} =
mc_worker_logic:make_request(
Socket,
NetModule,
ConnState#conn_state.database,
#insert{collection = mc_worker_logic:update_dbcoll(Coll, <<"system.indexes">>), documents = [Index]}),
{reply, ok, State};
handle_call(Request, From, State) when ?WRITE(Request) -> % write requests (deprecated)
process_write_request(Request, From, State);
handle_call(Request, From, State) when ?READ(Request) -> % read requests (and all through command)
process_read_request(Request, From, State);
handle_call(Request, _, State = #state{socket = Socket, conn_state = ConnState, net_module = NetModule})
when is_record(Request, killcursor) ->
{ok, _, _} = mc_worker_logic:make_request(Socket, NetModule, ConnState#conn_state.database, Request),
{reply, ok, State};
handle_call({stop, _}, _From, State) -> % stop request
{stop, normal, ok, State}.
%% @hidden
handle_cast(halt, State) ->
{stop, normal, State};
handle_cast(hibernate, State) ->
{noreply, State, hibernate};
handle_cast(_, State) ->
{noreply, State}.
%% @hidden
handle_info({Net, _Socket, Data}, State = #state{request_storage = RequestStorage}) when Net =:= tcp; Net =:= ssl ->
Buffer = <<(State#state.buffer)/binary, Data/binary>>,
{Responses, Pending} = mc_worker_logic:decode_responses(Buffer),
UReqStor = mc_worker_logic:process_responses(Responses, RequestStorage),
UState = need_hibernate(byte_size(Buffer), State),
{noreply, UState#state{buffer = Pending, request_storage = UReqStor}};
handle_info({NetR, _Socket}, State) when NetR =:= tcp_closed; NetR =:= ssl_closed ->
{stop, tcp_closed, State};
handle_info(hibernate, State) ->
{noreply, State#state{hibernate_timer = undefined}, hibernate};
handle_info({NetR, _Socket, Reason}, State) when NetR =:= tcp_error; NetR =:= ssl_error ->
{stop, Reason, State}.
%% @hidden
terminate(_, State = #state{net_module = NetModule}) ->
try NetModule:close(State#state.socket)
catch
_:_ -> ok
end.
%% @hidden
code_change(_Old, State, _Extra) ->
{ok, State}.
%% @private
process_read_request(Request, From, State) ->
#state{socket = Socket,
request_storage = RequestStorage,
conn_state = CS,
net_module = NetModule,
next_req_fun = Next} = State,
{UpdReq, Selector} = get_query_selector(Request, CS),
{ok, PacketSize, Id} = mc_worker_logic:make_request(Socket, NetModule, CS#conn_state.database, UpdReq),
UState = need_hibernate(PacketSize, State),
case get_write_concern(Selector) of
{<<"w">>, 0} -> %no concern request
Next(),
{reply, #reply{
cursornotfound = false,
queryerror = false,
cursorid = 0,
documents = [#{<<"ok">> => 1}]}, UState};
_ -> %ordinary request with response
Next(),
RespFun = mc_worker_logic:get_resp_fun(UpdReq, From), % save function, which will be called on response
URStorage = RequestStorage#{Id => RespFun},
{noreply, UState#state{request_storage = URStorage}}
end.
%% @deprecated
%% @private
process_write_request(Request, _, State = #state{conn_state = #conn_state{write_mode = unsafe, database = Db}}) ->
#state{socket = Socket, net_module = NetModule} = State,
{ok, PacketSize, _} = mc_worker_logic:make_request(Socket, NetModule, Db, Request),
UState = need_hibernate(PacketSize, State),
{reply, ok, UState};
process_write_request(Request, From, State = #state{conn_state = #conn_state{write_mode = Safe, database = Db}}) ->
#state{socket = Socket, net_module = NetModule, request_storage = ReqStor} = State,
Params = case Safe of safe -> {}; {safe, Param} -> Param end,
ConfirmWrite =
#'query'
{ % check-write read request
batchsize = -1,
collection = mc_worker_logic:update_dbcoll(mc_worker_logic:collection(Request), <<"$cmd">>),
selector = bson:append({<<"getlasterror">>, 1}, Params)
},
{ok, PacketSize, Id} = mc_worker_logic:make_request(
Socket, NetModule, Db, [Request, ConfirmWrite]), % ordinary write request
RespFun = mc_worker_logic:get_resp_fun(Request, From),
UReqStor = ReqStor#{Id => RespFun}, % save function, which will be called on response
UState = need_hibernate(PacketSize, State#state{request_storage = UReqStor}),
{noreply, UState}.
%% @private
need_hibernate(Pack, State) when Pack < 64 -> State; %no need in hibernate
need_hibernate(_, State = #state{hibernate_timer = undefined}) ->
TRef = erlang:send_after(1000, self(), hibernate),
State#state{hibernate_timer = TRef};
need_hibernate(_, State) -> State. %timer already started
%% @private
get_query_selector(Query = #query{selector = Selector, sok_overriden = true}, CS) ->
{Query#'query'{slaveok = CS#conn_state.read_mode =:= slave_ok}, Selector};
get_query_selector(Query = #query{selector = Selector, sok_overriden = false}, _) ->
{Query, Selector};
get_query_selector(GetMore, _) -> {GetMore, {}}.
%% @private
get_write_concern(#{<<"writeConcern">> := N}) -> N;
get_write_concern(Selector) when is_tuple(Selector) ->
bson:lookup(<<"writeConcern">>, Selector);
get_write_concern(_) -> undefined.
%% @private
%% Parses proplist to record
form_state(Options) ->
Database = mc_utils:get_value(database, Options, <<"admin">>),
AuthSource = mc_utils:get_value(auth_source, Options, <<"admin">>),
RMode = mc_utils:get_value(r_mode, Options, master),
WMode = mc_utils:get_value(w_mode, Options, unsafe),
#conn_state{database = Database, auth_source = AuthSource, read_mode = RMode, write_mode = WMode}.
%% @private
%% Register this process if needed
try_register(Options) ->
case lists:keyfind(register, 1, Options) of
false -> ok;
{_, Name} when is_atom(Name) -> register(Name, self());
{_, RegFun} when is_function(RegFun) -> RegFun(self())
end.
%% @private
get_set_opts_module(Options) ->
case mc_utils:get_value(ssl, Options, false) of
true -> ssl;
false -> gen_tcp
end.
%% @private
auth_if_credentials(_, _, _, Login, Password) when Login =:= undefined; Password =:= undefined ->
ok;
auth_if_credentials(Socket, ConnState, NetModule, Login, Password) ->
Version = mc_worker_logic:get_version(Socket, ConnState#conn_state.auth_source, NetModule),
mc_auth_logic:auth(Version, Socket, ConnState#conn_state.auth_source, Login, Password, NetModule),
ok.