summaryrefslogtreecommitdiff
path: root/src/couchdb/couch_server.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couchdb/couch_server.erl')
-rw-r--r--src/couchdb/couch_server.erl252
1 files changed, 148 insertions, 104 deletions
diff --git a/src/couchdb/couch_server.erl b/src/couchdb/couch_server.erl
index bb3617b2..86cdb2f8 100644
--- a/src/couchdb/couch_server.erl
+++ b/src/couchdb/couch_server.erl
@@ -15,7 +15,7 @@
-behaviour(application).
-export([start/0,start/1,start/2,stop/0,stop/1]).
--export([open/1,create/2,delete/1,all_databases/0,get_version/0]).
+-export([open/2,create/2,delete/1,all_databases/0,get_version/0]).
-export([init/1, handle_call/3,sup_start_link/2]).
-export([handle_cast/2,code_change/3,handle_info/2,terminate/2]).
-export([dev_start/0,remote_restart/0]).
@@ -25,7 +25,9 @@
-record(server,{
root_dir = [],
dbname_regexp,
- options=[]
+ remote_restart=[],
+ max_dbs_open=100,
+ current_dbs_open=0
}).
start() ->
@@ -64,33 +66,41 @@ get_version() ->
sup_start_link(RootDir, Options) ->
gen_server:start_link({local, couch_server}, couch_server, {RootDir, Options}, []).
-open(Filename) ->
- gen_server:call(couch_server, {open, Filename}).
+open(DbName, Options) ->
+ gen_server:call(couch_server, {open, DbName, Options}).
-create(Filename, Options) ->
- gen_server:call(couch_server, {create, Filename, Options}).
+create(DbName, Options) ->
+ gen_server:call(couch_server, {create, DbName, Options}).
-delete(Filename) ->
- gen_server:call(couch_server, {delete, Filename}).
+delete(DbName) ->
+ gen_server:call(couch_server, {delete, DbName}).
remote_restart() ->
gen_server:call(couch_server, remote_restart).
-init({RootDir, Options}) ->
- {ok, RegExp} = regexp:parse("^[a-z][a-z0-9\\_\\$()\\+\\-\\/]*$"),
- {ok, #server{root_dir=RootDir, dbname_regexp=RegExp, options=Options}}.
-
-check_filename(#server{dbname_regexp=RegExp}, Filename) ->
- case regexp:match(Filename, RegExp) of
+check_dbname(#server{dbname_regexp=RegExp}, DbName) ->
+ case regexp:match(DbName, RegExp) of
nomatch ->
{error, illegal_database_name};
_Match ->
ok
end.
-get_full_filename(Server, Filename) ->
- filename:join([Server#server.root_dir, "./" ++ Filename ++ ".couch"]).
+get_full_filename(Server, DbName) ->
+ filename:join([Server#server.root_dir, "./" ++ DbName ++ ".couch"]).
+init({RootDir, Options}) ->
+ {ok, RegExp} = regexp:parse("^[a-z][a-z0-9\\_\\$()\\+\\-\\/]*$"),
+ ets:new(couch_dbs_by_name, [set, private, named_table]),
+ ets:new(couch_dbs_by_pid, [set, private, named_table]),
+ ets:new(couch_dbs_by_lru, [ordered_set, private, named_table]),
+ process_flag(trap_exit, true),
+ MaxDbsOpen = proplists:get_value(max_dbs_open, Options),
+ RemoteRestart = proplists:get_value(remote_restart, Options),
+ {ok, #server{root_dir=RootDir,
+ dbname_regexp=RegExp,
+ max_dbs_open=MaxDbsOpen,
+ remote_restart=RemoteRestart}}.
terminate(_Reason, _Server) ->
ok.
@@ -109,107 +119,141 @@ all_databases() ->
{ok, Filenames}.
+maybe_close_lru_db(#server{current_dbs_open=NumOpen, max_dbs_open=MaxOpen}=Server)
+ when NumOpen < MaxOpen ->
+ {ok, Server};
+maybe_close_lru_db(#server{current_dbs_open=NumOpen}=Server) ->
+ % must free up the lru db.
+ case try_close_lru(now()) of
+ ok -> {ok, Server#server{current_dbs_open=NumOpen-1}};
+ Error -> Error
+ end.
+
+try_close_lru(StartTime) ->
+ LruTime = ets:first(couch_dbs_by_lru),
+ if LruTime > StartTime ->
+ % this means we've looped through all our opened dbs and found them
+ % all in use.
+ {error, all_dbs_active};
+ true ->
+ [{_, DbName}] = ets:lookup(couch_dbs_by_lru, LruTime),
+ [{_, {MainPid, LruTime}}] = ets:lookup(couch_dbs_by_name, DbName),
+ case couch_db:num_refs(MainPid) of
+ 0 ->
+ exit(MainPid, kill),
+ receive {'EXIT', MainPid, _Reason} -> ok end,
+ true = ets:delete(couch_dbs_by_lru, LruTime),
+ true = ets:delete(couch_dbs_by_name, DbName),
+ true = ets:delete(couch_dbs_by_pid, MainPid),
+ ok;
+ _NumRefs ->
+ % this still has referrers. Go ahead and give it a current lru time
+ % and try the next one in the table.
+ NewLruTime = now(),
+ true = ets:insert(couch_dbs_by_name, {DbName, {MainPid, NewLruTime}}),
+ true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
+ true = ets:delete(couch_dbs_by_lru, LruTime),
+ true = ets:insert(couch_dbs_by_lru, {NewLruTime, DbName}),
+ try_close_lru(StartTime)
+ end
+ end.
+
+
handle_call(get_root, _From, #server{root_dir=Root}=Server) ->
{reply, {ok, Root}, Server};
-handle_call({open, Filename}, From, Server) ->
- case check_filename(Server, Filename) of
- {error, Error} ->
- {reply, {error, Error}, Server};
- ok ->
- Filepath = get_full_filename(Server, Filename),
- Result = supervisor:start_child(couch_server_sup,
- {Filename,
- {couch_db, open, [Filename, Filepath]},
- transient ,
- infinity,
- supervisor,
- [couch_db]}),
- case Result of
- {ok, Db} ->
- {reply, {ok, Db}, Server};
- {error, already_present} ->
- ok = supervisor:delete_child(couch_server_sup, Filename),
- % call self recursively
- handle_call({open, Filename}, From, Server);
- {error, {already_started, Db}} ->
- {reply, {ok, Db}, Server};
- {error, {not_found, _}} ->
- {reply, not_found, Server};
- {error, {Error, _}} ->
- {reply, {error, Error}, Server}
- end
+handle_call({open, DbName, Options}, {FromPid,_}, Server) ->
+ Filepath = get_full_filename(Server, DbName),
+ LruTime = now(),
+ case ets:lookup(couch_dbs_by_name, DbName) of
+ [] ->
+ case maybe_close_lru_db(Server) of
+ {ok, Server2} ->
+ case couch_db:start_link(DbName, Filepath, Options) of
+ {ok, MainPid} ->
+ true = ets:insert(couch_dbs_by_name, {DbName, {MainPid, LruTime}}),
+ true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
+ true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
+ DbsOpen = Server2#server.current_dbs_open + 1,
+ {reply,
+ couch_db:open_ref_counted(MainPid, FromPid),
+ Server2#server{current_dbs_open=DbsOpen}};
+ CloseError ->
+ {reply, CloseError, Server2}
+ end;
+ Error ->
+ {reply, Error, Server}
+ end;
+ [{_, {MainPid, PrevLruTime}}] ->
+ true = ets:insert(couch_dbs_by_name, {DbName, {MainPid, LruTime}}),
+ true = ets:delete(couch_dbs_by_lru, PrevLruTime),
+ true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
+ {reply, couch_db:open_ref_counted(MainPid, FromPid), Server}
end;
-handle_call({create, Filename, Options}, _From, Server) ->
- case check_filename(Server, Filename) of
- {error, Error} ->
- {reply, {error, Error}, Server};
+handle_call({create, DbName, Options}, {FromPid,_}, Server) ->
+ case check_dbname(Server, DbName) of
ok ->
- Filepath = get_full_filename(Server, Filename),
- ChildSpec = {Filename,
- {couch_db, create, [Filename, Filepath, Options]},
- transient,
- infinity,
- supervisor,
- [couch_db]},
- Result =
- case supervisor:delete_child(couch_server_sup, Filename) of
- ok ->
- sup_start_child(couch_server_sup, ChildSpec);
- {error, not_found} ->
- sup_start_child(couch_server_sup, ChildSpec);
- {error, running} ->
- % a server process for this database already started. Maybe kill it
- case lists:member(overwrite, Options) of
- true ->
- supervisor:terminate_child(couch_server_sup, Filename),
- ok = supervisor:delete_child(couch_server_sup, Filename),
- sup_start_child(couch_server_sup, ChildSpec);
- false ->
- {error, database_already_exists}
- end
- end,
- case Result of
- {ok, _Db} -> couch_db_update_notifier:notify({created, Filename});
- _ -> ok
- end,
- {reply, Result, Server}
+ Filepath = get_full_filename(Server, DbName),
+
+ case ets:lookup(couch_dbs_by_name, DbName) of
+ [] ->
+ case couch_db:start_link(DbName, Filepath, [create|Options]) of
+ {ok, MainPid} ->
+ LruTime = now(),
+ true = ets:insert(couch_dbs_by_name, {DbName, {MainPid, LruTime}}),
+ true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
+ true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
+ DbsOpen = Server#server.current_dbs_open + 1,
+ {reply,
+ couch_db:open_ref_counted(MainPid, FromPid),
+ Server#server{current_dbs_open=DbsOpen}};
+ Error ->
+ {reply, Error, Server}
+ end;
+ [_AlreadyRunningDb] ->
+ {reply, {error, file_exists}, Server}
+ end;
+ Error ->
+ {reply, Error, Server}
end;
-handle_call({delete, Filename}, _From, Server) ->
- FullFilepath = get_full_filename(Server, Filename),
- supervisor:terminate_child(couch_server_sup, Filename),
- supervisor:delete_child(couch_server_sup, Filename),
+handle_call({delete, DbName}, _From, Server) ->
+ FullFilepath = get_full_filename(Server, DbName),
+ Server2 =
+ case ets:lookup(couch_dbs_by_name, DbName) of
+ [] -> Server;
+ [{_, {Pid, LruTime}}] ->
+ exit(Pid, kill),
+ receive {'EXIT', Pid, _Reason} -> ok end,
+ true = ets:delete(couch_dbs_by_name, DbName),
+ true = ets:delete(couch_dbs_by_pid, Pid),
+ true = ets:delete(couch_dbs_by_lru, LruTime),
+ DbsOpen = Server#server.current_dbs_open - 1,
+ Server#server{current_dbs_open=DbsOpen}
+ end,
case file:delete(FullFilepath) of
ok ->
- couch_db_update_notifier:notify({deleted, Filename}),
- {reply, ok, Server};
+ couch_db_update_notifier:notify({deleted, DbName}),
+ {reply, ok, Server2};
{error, enoent} ->
- {reply, not_found, Server};
+ {reply, not_found, Server2};
Else ->
- {reply, Else, Server}
+ {reply, Else, Server2}
end;
-handle_call(remote_restart, _From, #server{options=Options}=Server) ->
- case proplists:get_value(remote_restart, Options) of
- true ->
- exit(self(), restart);
- _ ->
- ok
- end,
+handle_call(remote_restart, _From, #server{remote_restart=false}=Server) ->
+ {reply, ok, Server};
+handle_call(remote_restart, _From, #server{remote_restart=true}=Server) ->
+ exit(couch_server_sup, restart),
{reply, ok, Server}.
-% this function is just to strip out the child spec error stuff if hit
-sup_start_child(couch_server_sup, ChildSpec) ->
- case supervisor:start_child(couch_server_sup, ChildSpec) of
- {error, {Error, _ChildInfo}} ->
- {error, Error};
- Else ->
- Else
- end.
-
-handle_cast(_Msg, State) ->
- {noreply,State}.
+handle_cast(Msg, _Server) ->
+ exit({unknown_cast_message, Msg}).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info({'EXIT', Pid, _Reason}, Server) ->
+ [{Pid, DbName}] = ets:lookup(couch_dbs_by_pid, Pid),
+ true = ets:delete(couch_dbs_by_pid, Pid),
+ true = ets:delete(couch_dbs_by_name, DbName),
+ {noreply, Server};
+handle_info(Info, _Server) ->
+ exit({unknown_message, Info}).