Skip to content

Commit 7e8b89e

Browse files
Taureclaude
andauthored
feat: add graceful shutdown for k8s deployments (#365)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ea35d47 commit 7e8b89e

File tree

2 files changed

+48
-2
lines changed

2 files changed

+48
-2
lines changed

src/nova.erl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
-module(nova).
77

8+
-include_lib("kernel/include/logger.hrl").
9+
810
-export([
911
get_main_app/0,
1012
get_apps/0,

src/nova_app.erl

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
-behaviour(application).
1010

11+
-include_lib("kernel/include/logger.hrl").
12+
1113
%% Application callbacks
12-
-export([start/2, stop/1]).
14+
-export([start/2, prep_stop/1, stop/1]).
1315

1416
%%====================================================================
1517
%% API
@@ -18,10 +20,52 @@
1820
start(_StartType, _StartArgs) ->
1921
nova_sup:start_link().
2022

21-
%%--------------------------------------------------------------------
23+
prep_stop(State) ->
24+
graceful_shutdown(),
25+
State.
26+
2227
stop(_State) ->
2328
ok.
2429

2530
%%====================================================================
2631
%% Internal functions
2732
%%====================================================================
33+
34+
graceful_shutdown() ->
35+
Delay = application:get_env(nova, shutdown_delay, 0),
36+
case Delay > 0 of
37+
true ->
38+
?LOG_NOTICE(#{msg => <<"Graceful shutdown started">>, delay_ms => Delay}),
39+
timer:sleep(Delay);
40+
false ->
41+
ok
42+
end,
43+
?LOG_NOTICE(#{msg => <<"Suspending listener">>}),
44+
ranch:suspend_listener(nova_listener),
45+
DrainTimeout = application:get_env(nova, shutdown_drain_timeout, 15000),
46+
?LOG_NOTICE(#{msg => <<"Draining connections">>, timeout_ms => DrainTimeout}),
47+
drain_connections(DrainTimeout),
48+
?LOG_NOTICE(#{msg => <<"Stopping listener">>}),
49+
cowboy:stop_listener(nova_listener),
50+
ok.
51+
52+
drain_connections(Timeout) ->
53+
Deadline = erlang:monotonic_time(millisecond) + Timeout,
54+
drain_loop(Deadline).
55+
56+
drain_loop(Deadline) ->
57+
case ranch:info(nova_listener) of
58+
#{active_connections := 0} ->
59+
ok;
60+
#{active_connections := N} ->
61+
Now = erlang:monotonic_time(millisecond),
62+
case Now >= Deadline of
63+
true ->
64+
?LOG_WARNING(#{msg => <<"Drain timeout reached">>,
65+
remaining_connections => N}),
66+
ok;
67+
false ->
68+
timer:sleep(500),
69+
drain_loop(Deadline)
70+
end
71+
end.

0 commit comments

Comments
 (0)