Skip to content

Commit 1649371

Browse files
committed
auto-reload config on file change
1 parent 0d130cf commit 1649371

4 files changed

Lines changed: 101 additions & 48 deletions

File tree

rel/overlay/etc/default.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,3 +1233,8 @@ url = {{nouveau_url}}
12331233
; from the _first_ authentication failure.
12341234
; note: changing this setting requires a couchdb restart.
12351235
;max_lifetime = 300000
1236+
1237+
[config]
1238+
; periodically reload configuration from file.
1239+
; Set to infinity to disable.
1240+
;auto_reload_secs = infinity

src/config/src/config.erl

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
-export([set/3, set/4, set/5]).
2727
-export([delete/2, delete/3, delete/4]).
2828

29-
-export([get_integer/3, set_integer/3, set_integer/4]).
29+
-export([get_integer/3, set_integer/3, set_integer/4, get_integer_or_infinity/3]).
3030
-export([get_float/3, set_float/3, set_float/4]).
3131
-export([get_boolean/3, set_boolean/3, set_boolean/4]).
3232

@@ -38,7 +38,7 @@
3838
-export([subscribe_for_changes/1]).
3939

4040
-export([init/1]).
41-
-export([handle_call/3, handle_cast/2]).
41+
-export([handle_call/3, handle_cast/2, handle_info/2]).
4242
-export([terminate/2]).
4343

4444
-export([is_sensitive/2]).
@@ -64,7 +64,9 @@
6464
% Just *.ini files: original *.ini + expanded *.d dirs
6565
ini_files,
6666
% The file we write config values to
67-
write_filename
67+
write_filename,
68+
% Reload timer ref
69+
reload_tref
6870
}).
6971

7072
start_link(IniFilesDirs) ->
@@ -80,6 +82,17 @@ all() ->
8082
lists:sort(lists:filtermap(fun settings_fmap_fun/1, persistent_term:get())).
8183

8284
get_integer(Section, Key, Default) when is_integer(Default) ->
85+
get_integer_int(Section, Key, Default).
86+
87+
get_integer_or_infinity(Section, Key, Default) when is_integer(Default); Default == infinity ->
88+
case get_value(Section, Key, Default) of
89+
infinity ->
90+
infinity;
91+
_Value ->
92+
get_integer_int(Section, Key, Default)
93+
end.
94+
95+
get_integer_int(Section, Key, Default) ->
8396
try
8497
to_integer(get_value(Section, Key, Default))
8598
catch
@@ -294,7 +307,7 @@ init(IniFilesDirs) ->
294307
% correct node name and distribution mode.
295308
case check_distribution_mode() of
296309
ok ->
297-
{ok, Config};
310+
{ok, schedule_reload(Config)};
298311
{error, Msg} ->
299312
erlang:display(Msg),
300313
timer:sleep(500),
@@ -331,9 +344,9 @@ handle_call({set, Sec, Key, Val, Opts}, _From, Config) ->
331344
ok ->
332345
Event = {config_change, Sec, Key, Val, Persist},
333346
gen_event:sync_notify(config_event, Event),
334-
{reply, ok, Config};
347+
{reply, ok, schedule_reload(Config)};
335348
{error, Else} ->
336-
{reply, {error, Else}, Config}
349+
{reply, {error, Else}, schedule_reload(Config)}
337350
end
338351
end;
339352
handle_call({delete, Sec, Key, Persist, Reason}, _From, Config) ->
@@ -357,55 +370,21 @@ handle_call({delete, Sec, Key, Persist, Reason}, _From, Config) ->
357370
ok ->
358371
Event = {config_change, Sec, Key, deleted, Persist},
359372
gen_event:sync_notify(config_event, Event),
360-
{reply, ok, Config};
373+
{reply, ok, schedule_reload(Config)};
361374
Else ->
362-
{reply, Else, Config}
375+
{reply, Else, schedule_reload(Config)}
363376
end;
364377
handle_call(reload, _From, #config{} = Config) ->
365-
#config{ini_files_dirs = IniFilesDirs} = Config,
366-
IniFiles = expand_dirs(IniFilesDirs),
367-
% Update persistent term with ini values.
368-
IniMap = ini_map(IniFiles),
369-
maps:foreach(
370-
fun({Sec, Key}, V) ->
371-
VExisting = get_value(Sec, Key, undefined),
372-
case V =:= VExisting of
373-
true ->
374-
ok;
375-
false ->
376-
put_value(Sec, Key, V),
377-
Msg = "Reload detected config change ~s.~s = ~p",
378-
Args = [Sec, Key, maybe_conceal(V, is_sensitive(Sec, Key))],
379-
couch_log:notice(Msg, Args),
380-
Event = {config_change, Sec, Key, V, true},
381-
gen_event:sync_notify(config_event, Event)
382-
end
383-
end,
384-
IniMap
385-
),
386-
% And remove anything in persistent terms that wasn't on disk.
387-
lists:foreach(
388-
fun
389-
({{Sec, Key}, _}) when not is_map_key({Sec, Key}, IniMap) ->
390-
NoticeMsg = "Reload deleting in-memory config ~s.~s",
391-
couch_log:notice(NoticeMsg, [Sec, Key]),
392-
erase_value(Sec, Key),
393-
Event = {config_change, Sec, Key, deleted, true},
394-
gen_event:sync_notify(config_event, Event);
395-
(_) ->
396-
ok
397-
end,
398-
all()
399-
),
400-
Config1 = Config#config{
401-
ini_files = IniFiles,
402-
write_filename = get_write_file(IniFiles)
403-
},
404-
{reply, ok, Config1}.
378+
{reply, ok, reload(Config)}.
405379

406380
handle_cast(_Msg, State) ->
407381
{noreply, State}.
408382

383+
handle_info(reload, #config{} = Config) ->
384+
{noreply, schedule_reload(reload(Config))};
385+
handle_info(_Msg, State) ->
386+
{noreply, State}.
387+
409388
terminate(_Reason, _State) ->
410389
erase_all().
411390

@@ -615,6 +594,58 @@ settings_fmap_fun({{?MODULE, ?SETTINGS, Sec, Key}, Val}) ->
615594
settings_fmap_fun(_) ->
616595
false.
617596

597+
reload(Config) ->
598+
#config{ini_files_dirs = IniFilesDirs} = Config,
599+
IniFiles = expand_dirs(IniFilesDirs),
600+
% Update ets with ini values.
601+
IniMap = ini_map(IniFiles),
602+
maps:foreach(
603+
fun({Sec, Key}, V) ->
604+
VExisting = get_value(Sec, Key, undefined),
605+
case V =:= VExisting of
606+
true ->
607+
ok;
608+
false ->
609+
put_value(Sec, Key, V),
610+
Msg = "Reload detected config change ~s.~s = ~p",
611+
Args = [Sec, Key, maybe_conceal(V, is_sensitive(Sec, Key))],
612+
couch_log:notice(Msg, Args),
613+
Event = {config_change, Sec, Key, V, true},
614+
gen_event:sync_notify(config_event, Event)
615+
end
616+
end,
617+
IniMap
618+
),
619+
% And remove anything in ets that wasn't on disk.
620+
lists:foreach(
621+
fun
622+
({{Sec, Key}, _}) when not is_map_key({Sec, Key}, IniMap) ->
623+
NoticeMsg = "Reload deleting in-memory config ~s.~s",
624+
couch_log:notice(NoticeMsg, [Sec, Key]),
625+
erase_value(Sec, Key),
626+
Event = {config_change, Sec, Key, deleted, true},
627+
gen_event:sync_notify(config_event, Event);
628+
(_) ->
629+
ok
630+
end,
631+
all()
632+
),
633+
Config#config{
634+
ini_files = IniFiles,
635+
write_filename = get_write_file(IniFiles)
636+
}.
637+
638+
schedule_reload(#config{} = Config) ->
639+
timer:cancel(Config#config.reload_tref),
640+
case get_integer_or_infinity("config", "auto_reload_secs", infinity) of
641+
infinity ->
642+
Config;
643+
AutoReloadSecs when AutoReloadSecs > 0 ->
644+
AutoReloadMS = erlang:convert_time_unit(AutoReloadSecs, second, millisecond),
645+
TRef = erlang:send_after(AutoReloadMS, ?MODULE, reload),
646+
Config#config{reload_tref = TRef}
647+
end.
648+
618649
-ifdef(TEST).
619650
-include_lib("couch/include/couch_eunit.hrl").
620651

test/elixir/test/config/suite.elixir

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
"Non-term whitelist values allow further modification of the whitelist",
150150
"PORT `BUGGED` ?raw tests from config.js",
151151
"Reload config",
152+
"Auto-reload config",
152153
"Server-side password hashing, and raw updates disabling that",
153154
"Settings can be altered with undefined whitelist allowing any change",
154155
"Standard config options are present",

test/elixir/test/config_test.exs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,22 @@ defmodule ConfigTest do
182182
assert resp.status_code == 200
183183
end
184184

185+
test "Auto-reload config", context do
186+
{:ok, fd} = File.open "dev/lib/node1/etc/local.ini", [:append]
187+
IO.puts(fd, "[foo]\nbar = baz")
188+
File.close fd
189+
assert !Map.has_key?(get_config(context, "foo"), "bar")
190+
191+
# enable config auto-reload
192+
set_config(context, "config", "auto_reload_secs", "1")
193+
on_exit(fn ->
194+
delete_config(context, "config", "auto_reload_secs", nil)
195+
end)
196+
197+
:timer.sleep(1200)
198+
assert get_config(context, "foo", "bar") == "baz"
199+
end
200+
185201
# Those are negative test cases. The positive cases are implicitly
186202
# tested by other ones.
187203
test "Only JSON strings are accepted", context do

0 commit comments

Comments
 (0)