mod_http_upload_quota: Avoid 'max_days' race
Try to spread clean-up runs for multiple hosts, rather than scheduling them in parallel. This should reduce I/O spikes, and avoid race conditions where multiple processes detect and then try to delete the same old files (if multiple hosts have the same 'docroot'). Fixes #3497.
This commit is contained in:
parent
caf07692db
commit
868387a405
|
@ -27,7 +27,6 @@
|
||||||
-author('holger@zedat.fu-berlin.de').
|
-author('holger@zedat.fu-berlin.de').
|
||||||
|
|
||||||
-define(TIMEOUT, timer:hours(24)).
|
-define(TIMEOUT, timer:hours(24)).
|
||||||
-define(INITIAL_TIMEOUT, timer:minutes(10)).
|
|
||||||
-define(FORMAT(Error), file:format_error(Error)).
|
-define(FORMAT(Error), file:format_error(Error)).
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
@ -64,7 +63,7 @@
|
||||||
max_days :: pos_integer() | infinity,
|
max_days :: pos_integer() | infinity,
|
||||||
docroot :: binary(),
|
docroot :: binary(),
|
||||||
disk_usage = #{} :: disk_usage(),
|
disk_usage = #{} :: disk_usage(),
|
||||||
timers :: [timer:tref()]}).
|
timer :: reference() | undefined}).
|
||||||
|
|
||||||
-type disk_usage() :: #{{binary(), binary()} => non_neg_integer()}.
|
-type disk_usage() :: #{{binary(), binary()} => non_neg_integer()}.
|
||||||
-type state() :: #state{}.
|
-type state() :: #state{}.
|
||||||
|
@ -166,12 +165,11 @@ init([ServerHost|_]) ->
|
||||||
DocRoot1 = mod_http_upload_opt:docroot(ServerHost),
|
DocRoot1 = mod_http_upload_opt:docroot(ServerHost),
|
||||||
DocRoot2 = mod_http_upload:expand_home(str:strip(DocRoot1, right, $/)),
|
DocRoot2 = mod_http_upload:expand_home(str:strip(DocRoot1, right, $/)),
|
||||||
DocRoot3 = mod_http_upload:expand_host(DocRoot2, ServerHost),
|
DocRoot3 = mod_http_upload:expand_host(DocRoot2, ServerHost),
|
||||||
Timers = if MaxDays == infinity -> [];
|
Timer = if MaxDays == infinity -> undefined;
|
||||||
true ->
|
true ->
|
||||||
{ok, T1} = timer:send_after(?INITIAL_TIMEOUT, sweep),
|
Timeout = p1_rand:uniform(?TIMEOUT div 2),
|
||||||
{ok, T2} = timer:send_interval(?TIMEOUT, sweep),
|
erlang:send_after(Timeout, self(), sweep)
|
||||||
[T1, T2]
|
end,
|
||||||
end,
|
|
||||||
ejabberd_hooks:add(http_upload_slot_request, ServerHost, ?MODULE,
|
ejabberd_hooks:add(http_upload_slot_request, ServerHost, ?MODULE,
|
||||||
handle_slot_request, 50),
|
handle_slot_request, 50),
|
||||||
{ok, #state{server_host = ServerHost,
|
{ok, #state{server_host = ServerHost,
|
||||||
|
@ -179,7 +177,7 @@ init([ServerHost|_]) ->
|
||||||
access_hard_quota = AccessHardQuota,
|
access_hard_quota = AccessHardQuota,
|
||||||
max_days = MaxDays,
|
max_days = MaxDays,
|
||||||
docroot = DocRoot3,
|
docroot = DocRoot3,
|
||||||
timers = Timers}}.
|
timer = Timer}}.
|
||||||
|
|
||||||
-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
|
-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
|
||||||
handle_call(Request, From, State) ->
|
handle_call(Request, From, State) ->
|
||||||
|
@ -249,6 +247,7 @@ handle_info(sweep, #state{server_host = ServerHost,
|
||||||
max_days = MaxDays} = State)
|
max_days = MaxDays} = State)
|
||||||
when is_integer(MaxDays), MaxDays > 0 ->
|
when is_integer(MaxDays), MaxDays > 0 ->
|
||||||
?DEBUG("Got 'sweep' message for ~ts", [ServerHost]),
|
?DEBUG("Got 'sweep' message for ~ts", [ServerHost]),
|
||||||
|
Timer = erlang:send_after(?TIMEOUT, self(), sweep),
|
||||||
case file:list_dir(DocRoot) of
|
case file:list_dir(DocRoot) of
|
||||||
{ok, Entries} ->
|
{ok, Entries} ->
|
||||||
BackThen = secs_since_epoch() - (MaxDays * 86400),
|
BackThen = secs_since_epoch() - (MaxDays * 86400),
|
||||||
|
@ -264,17 +263,17 @@ handle_info(sweep, #state{server_host = ServerHost,
|
||||||
?ERROR_MSG("Cannot open document root ~ts: ~ts",
|
?ERROR_MSG("Cannot open document root ~ts: ~ts",
|
||||||
[DocRoot, ?FORMAT(Error)])
|
[DocRoot, ?FORMAT(Error)])
|
||||||
end,
|
end,
|
||||||
{noreply, State};
|
{noreply, State#state{timer = Timer}};
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
?ERROR_MSG("Unexpected info: ~p", [Info]),
|
?ERROR_MSG("Unexpected info: ~p", [Info]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
|
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
|
||||||
terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
|
terminate(Reason, #state{server_host = ServerHost, timer = Timer}) ->
|
||||||
?DEBUG("Stopping upload quota process for ~ts: ~p", [ServerHost, Reason]),
|
?DEBUG("Stopping upload quota process for ~ts: ~p", [ServerHost, Reason]),
|
||||||
ejabberd_hooks:delete(http_upload_slot_request, ServerHost, ?MODULE,
|
ejabberd_hooks:delete(http_upload_slot_request, ServerHost, ?MODULE,
|
||||||
handle_slot_request, 50),
|
handle_slot_request, 50),
|
||||||
lists:foreach(fun timer:cancel/1, Timers).
|
misc:cancel_timer(Timer).
|
||||||
|
|
||||||
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
|
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
|
||||||
code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
|
code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
|
||||||
|
|
Loading…
Reference in New Issue