From 7e6d9477653a8208bd29b4fdfab30ece382ea036 Mon Sep 17 00:00:00 2001 From: Steve Vinoski Date: Fri, 17 Feb 2012 00:42:22 -0500 Subject: [PATCH] make wterl an app, cache the WT connection Add app, supervisor, and worker to open and cache the database connection. Revert to using binaries for WT config in order to allow wterl callers to use proplists for configuration. Change {error, not_found} to not_found return values from wterl. --- c_src/wterl.c | 33 +++++++------- src/wterl.app.src | 1 + src/wterl.erl | 43 ++++++++++-------- src/wterl_app.erl | 39 ++++++++++++++++ src/wterl_conn.erl | 111 +++++++++++++++++++++++++++++++++++++++++++++ src/wterl_sup.erl | 48 ++++++++++++++++++++ 6 files changed, 239 insertions(+), 36 deletions(-) create mode 100644 src/wterl_app.erl create mode 100644 src/wterl_conn.erl create mode 100644 src/wterl_sup.erl diff --git a/c_src/wterl.c b/c_src/wterl.c index 2e052e9..397b8d9 100644 --- a/c_src/wterl.c +++ b/c_src/wterl.c @@ -46,7 +46,6 @@ typedef struct WT_CURSOR* cursor; } wterl_cursor_handle; -typedef char Config[256]; // configuration strings typedef char Uri[128]; // object names // Atoms (initialized in on_load) @@ -115,7 +114,7 @@ static ErlNifFunc nif_funcs[] = static inline ERL_NIF_TERM wterl_strerror(ErlNifEnv* env, int rc) { if (rc == WT_NOTFOUND) - return enif_make_tuple2(env, ATOM_ERROR, enif_make_atom(env, "not_found")); + return enif_make_atom(env, "not_found"); else return enif_make_tuple2(env, ATOM_ERROR, enif_make_string(env, wiredtiger_strerror(rc), ERL_NIF_LATIN1)); @@ -123,13 +122,13 @@ static inline ERL_NIF_TERM wterl_strerror(ErlNifEnv* env, int rc) static ERL_NIF_TERM wterl_conn_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - Config config; + ErlNifBinary config; char homedir[4096]; if (enif_get_string(env, argv[0], homedir, sizeof homedir, ERL_NIF_LATIN1) && - enif_get_string(env, argv[1], config, sizeof config, ERL_NIF_LATIN1)) + enif_inspect_binary(env, argv[1], &config)) { WT_CONNECTION* conn; - int rc = wiredtiger_open(homedir, NULL, config, &conn); + int rc = wiredtiger_open(homedir, NULL, (const char*)config.data, &conn); if (rc == 0) { wterl_conn_handle* conn_handle = @@ -179,36 +178,36 @@ static inline ERL_NIF_TERM wterl_session_worker(ErlNifEnv* env, int argc, const if (enif_get_resource(env, argv[0], wterl_session_RESOURCE, (void**)&session_handle)) { WT_SESSION* session = session_handle->session; - Config config; + ErlNifBinary config; Uri uri; if (enif_get_string(env, argv[1], uri, sizeof uri, ERL_NIF_LATIN1) && - enif_get_string(env, argv[2], config, sizeof config, ERL_NIF_LATIN1)) + enif_inspect_binary(env, argv[2], &config)) { int rc; switch (op) { case WTERL_OP_CREATE: - rc = session->create(session, uri, config); + rc = session->create(session, uri, (const char*)config.data); break; case WTERL_OP_DROP: - rc = session->drop(session, uri, config); + rc = session->drop(session, uri, (const char*)config.data); break; case WTERL_OP_SALVAGE: - rc = session->salvage(session, uri, config); + rc = session->salvage(session, uri, (const char*)config.data); break; case WTERL_OP_SYNC: - rc = session->sync(session, uri, config); + rc = session->sync(session, uri, (const char*)config.data); break; case WTERL_OP_TRUNCATE: // Ignore the cursor start/stop form of truncation for now, // support only the full file truncation. - rc = session->truncate(session, uri, NULL, NULL, config); + rc = session->truncate(session, uri, NULL, NULL, (const char*)config.data); break; case WTERL_OP_UPGRADE: - rc = session->upgrade(session, uri, config); + rc = session->upgrade(session, uri, (const char*)config.data); break; default: case WTERL_OP_VERIFY: - rc = session->verify(session, uri, config); + rc = session->verify(session, uri, (const char*)config.data); break; } if (rc != 0) @@ -285,13 +284,13 @@ static ERL_NIF_TERM wterl_session_rename(ErlNifEnv* env, int argc, const ERL_NIF if (enif_get_resource(env, argv[0], wterl_session_RESOURCE, (void**)&session_handle)) { WT_SESSION* session = session_handle->session; - Config config; + ErlNifBinary config; Uri oldname, newname; if (enif_get_string(env, argv[1], oldname, sizeof oldname, ERL_NIF_LATIN1) && enif_get_string(env, argv[2], newname, sizeof newname, ERL_NIF_LATIN1) && - enif_get_string(env, argv[3], config, sizeof config, ERL_NIF_LATIN1)) + enif_inspect_binary(env, argv[3], &config)) { - int rc = session->rename(session, oldname, newname, config); + int rc = session->rename(session, oldname, newname, (const char*)config.data); if (rc != 0) { return wterl_strerror(env, rc); diff --git a/src/wterl.app.src b/src/wterl.app.src index 7a4bc5c..534c8f0 100644 --- a/src/wterl.app.src +++ b/src/wterl.app.src @@ -7,6 +7,7 @@ kernel, stdlib ]}, + {mod, {wterl_app, []}}, {env, [ ]} ]}. diff --git a/src/wterl.erl b/src/wterl.erl index 37c6282..7d26d13 100644 --- a/src/wterl.erl +++ b/src/wterl.erl @@ -55,7 +55,7 @@ session_upgrade/3, session_verify/2, session_verify/3, - config_to_bin/2]). + config_to_bin/1]). -on_load(init/0). @@ -63,6 +63,8 @@ nif_stub_error(Line) -> erlang:nif_error({nif_not_loaded,module,?MODULE,line,Line}). +-define(EMPTY_CONFIG, <<"\0">>). + -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. @@ -91,12 +93,12 @@ session_close(_Ref) -> ?nif_stub. session_create(Ref, Name) -> - session_create(Ref, Name, ""). + session_create(Ref, Name, ?EMPTY_CONFIG). session_create(_Ref, _Name, _Config) -> ?nif_stub. session_drop(Ref, Name) -> - session_drop(Ref, Name, ""). + session_drop(Ref, Name, ?EMPTY_CONFIG). session_drop(_Ref, _Name, _Config) -> ?nif_stub. @@ -110,32 +112,32 @@ session_put(_Ref, _Table, _Key, _Value) -> ?nif_stub. session_rename(Ref, OldName, NewName) -> - session_rename(Ref, OldName, NewName, ""). + session_rename(Ref, OldName, NewName, ?EMPTY_CONFIG). session_rename(_Ref, _OldName, _NewName, _Config) -> ?nif_stub. session_salvage(Ref, Name) -> - session_salvage(Ref, Name, ""). + session_salvage(Ref, Name, ?EMPTY_CONFIG). session_salvage(_Ref, _Name, _Config) -> ?nif_stub. session_sync(Ref, Name) -> - session_sync(Ref, Name, ""). + session_sync(Ref, Name, ?EMPTY_CONFIG). session_sync(_Ref, _Name, _Config) -> ?nif_stub. session_truncate(Ref, Name) -> - session_truncate(Ref, Name, ""). + session_truncate(Ref, Name, ?EMPTY_CONFIG). session_truncate(_Ref, _Name, _Config) -> ?nif_stub. session_upgrade(Ref, Name) -> - session_upgrade(Ref, Name, ""). + session_upgrade(Ref, Name, ?EMPTY_CONFIG). session_upgrade(_Ref, _Name, _Config) -> ?nif_stub. session_verify(Ref, Name) -> - session_verify(Ref, Name, ""). + session_verify(Ref, Name, ?EMPTY_CONFIG). session_verify(_Ref, _Name, _Config) -> ?nif_stub. @@ -179,6 +181,7 @@ config_types() -> {eviction_target, integer}, {eviction_trigger, integer}, {extensions, string}, + {force, bool}, {hazard_max, integer}, {home_environment, bool}, {home_environment_priv, bool}, @@ -203,6 +206,8 @@ config_encode(bool, false) -> config_encode(_Type, _Value) -> invalid. +config_to_bin(Opts) -> + config_to_bin(Opts, []). config_to_bin([], Acc) -> iolist_to_binary([Acc, <<"\0">>]); config_to_bin([{Key, Value} | Rest], Acc) -> @@ -215,7 +220,7 @@ config_to_bin([{Key, Value} | Rest], Acc) -> Acc; EncodedValue -> EncodedKey = atom_to_binary(Key, utf8), - [EncodedKey, <<" = ">>, EncodedValue, <<", ">> | Acc] + [EncodedKey, <<"=">>, EncodedValue, <<",">> | Acc] end, config_to_bin(Rest, Acc2); false -> @@ -235,13 +240,13 @@ basic_test() -> ok = filelib:ensure_dir(filename:join("/tmp/wterl.basic", "foo")), io:put_chars(standard_error, "\topen connection\n"), - {ok, ConnRef} = - conn_open("/tmp/wterl.basic", "create=true,cache_size=100MB"), + OpenConfig = config_to_bin([{create,true},{cache_size,"100MB"}]), + {ok, ConnRef} = conn_open("/tmp/wterl.basic", OpenConfig), {ok, SRef} = session_open(ConnRef), %% Remove the table from any earlier run and re-create it io:put_chars(standard_error, "\ttable drop/create\n"), - ok = session_drop(SRef, "table:test", "force"), + ok = session_drop(SRef, "table:test", config_to_bin([{force,true}])), ok = session_create(SRef, "table:test"), %% Insert/delete a key using the session handle @@ -249,7 +254,7 @@ basic_test() -> ok = session_put(SRef, "table:test", <<"a">>, <<"apple">>), {ok, <<"apple">>} = session_get(SRef, "table:test", <<"a">>), ok = session_delete(SRef, "table:test", <<"a">>), - {error, not_found} = session_get(SRef, "table:test", <<"a">>), + not_found = session_get(SRef, "table:test", <<"a">>), %% Insert some items io:put_chars(standard_error, "\tsession insert\n"), @@ -277,7 +282,7 @@ basic_test() -> io:put_chars(standard_error, "\tsession truncate\n"), ok = session_truncate(SRef, "table:test"), - {error, not_found} = session_get(SRef, "table:test", <<"a">>), + not_found = session_get(SRef, "table:test", <<"a">>), ok = session_put(SRef, "table:test", <<"a">>, <<"apple">>), ok = session_put(SRef, "table:test", <<"b">>, <<"banana">>), @@ -314,7 +319,7 @@ basic_test() -> {ok, <<"cherry">>} = cursor_prev(Cursor), {ok, <<"date">>} = cursor_next(Cursor), {ok, <<"gooseberry">>} = cursor_next(Cursor), - {error, not_found} = cursor_next(Cursor), + not_found = cursor_next(Cursor), ok = cursor_close(Cursor), %% Search for an item @@ -352,7 +357,7 @@ basic_test() -> io:put_chars(standard_error, "\tcursor update\n"), {ok, Cursor} = cursor_open(SRef, "table:test"), ok = cursor_update(Cursor, <<"g">>, <<"goji berries">>), - {error, not_found} = cursor_update(Cursor, <<"k">>, <<"kumquat">>), + not_found = cursor_update(Cursor, <<"k">>, <<"kumquat">>), ok = cursor_close(Cursor), {ok, <<"goji berries">>} = session_get(SRef, "table:test", <<"g">>), @@ -360,9 +365,9 @@ basic_test() -> io:put_chars(standard_error, "\tcursor remove\n"), {ok, Cursor} = cursor_open(SRef, "table:test"), ok = cursor_remove(Cursor, <<"g">>, <<"goji berries">>), - {error, not_found} = cursor_remove(Cursor, <<"l">>, <<"lemon">>), + not_found = cursor_remove(Cursor, <<"l">>, <<"lemon">>), ok = cursor_close(Cursor), - {error, not_found} = session_get(SRef, "table:test", <<"g">>), + not_found = session_get(SRef, "table:test", <<"g">>), %% Close the session/connection io:put_chars(standard_error, "\tsession/connection close\n"), diff --git a/src/wterl_app.erl b/src/wterl_app.erl new file mode 100644 index 0000000..2b34fbc --- /dev/null +++ b/src/wterl_app.erl @@ -0,0 +1,39 @@ +%% ------------------------------------------------------------------- +%% +%% wterl: access to WiredTiger database +%% +%% Copyright (c) 2012 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- +-module(wterl_app). +-author('Steve Vinoski '). + +-behaviour(application). + +%% Application callbacks +-export([start/2, stop/1]). + + +%% =================================================================== +%% Application callbacks +%% =================================================================== + +start(_StartType, _StartArgs) -> + wterl_sup:start_link(). + +stop(_State) -> + ok. diff --git a/src/wterl_conn.erl b/src/wterl_conn.erl new file mode 100644 index 0000000..1b03942 --- /dev/null +++ b/src/wterl_conn.erl @@ -0,0 +1,111 @@ +%% ------------------------------------------------------------------- +%% +%% wterl_conn: manage a connection to WiredTiger +%% +%% Copyright (c) 2012 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- +-module(wterl_conn). +-author('Steve Vinoski '). + +-behaviour(gen_server). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +%% API +-export([start_link/0, + open/1, get/0, close/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, { conn }). + +%% ==================================================================== +%% API +%% ==================================================================== + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +open(Dir) -> + gen_server:call(?MODULE, {open, Dir}, infinity). + +get() -> + gen_server:call(?MODULE, get, infinity). + +close() -> + gen_server:call(?MODULE, close, infinity). + +%% ==================================================================== +%% gen_server callbacks +%% ==================================================================== + +init([]) -> + {ok, #state{}}. + +handle_call({open, Dir}, _From, #state{conn = undefined}=State) -> + Opts = [{create, true}, {cache_size, "100MB"}], + {Reply, NState} = case wterl:conn_open(Dir, wterl:config_to_bin(Opts)) of + {ok, ConnRef}=OK -> + {OK, State#state{conn = ConnRef}}; + Error -> + {Error, State} + end, + {reply, Reply, NState}; +handle_call({open, _Dir}, _From,#state{conn = ConnRef}=State) -> + {reply, {ok, ConnRef}, State}; + +handle_call(get, _From, #state{conn = undefined}=State) -> + {reply, {error, "no connection"}, State}; +handle_call(get, _From, #state{conn = ConnRef}=State) -> + {reply, {ok, ConnRef}, State}; + +handle_call(close, _From, #state{conn = ConnRef}=State) -> + close(ConnRef), + {reply, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, #state{conn = ConnRef}) -> + close(ConnRef), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% ==================================================================== +%% Internal functions +%% ==================================================================== + +close(undefined) -> + ok; +close(ConnRef) -> + wterl:conn_close(ConnRef). + + +-ifdef(TEST). + + +-endif. diff --git a/src/wterl_sup.erl b/src/wterl_sup.erl new file mode 100644 index 0000000..0ead62a --- /dev/null +++ b/src/wterl_sup.erl @@ -0,0 +1,48 @@ +%% ------------------------------------------------------------------- +%% +%% wterl_sup: supervisor for WiredTiger database app +%% +%% Copyright (c) 2012 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- +-module(wterl_sup). +-author('Steve Vinoski '). + +-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([]) -> + {ok, {{one_for_one, 5, 10}, [?CHILD(wterl_conn, worker)]}}.