aboutsummaryrefslogtreecommitdiffstats
path: root/doc/src/guide/connection_draining.asciidoc
blob: 2ccdbc84f33040592c9cb01c94fb7a2f308fcb5f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
== Connection draining

Stopping a Ranch listener via `ranch:stop_listener/1` will invariably kill
all connection processes the listener hosts. However, you may want to stop
a listener in a graceful fashion, ie by not accepting any new connections,
but allowing the existing connection processes to exit by themselves instead
of being killed.

For this purpose, you should first suspend the listener you wish to
stop gracefully, and then wait for its connection count to drop to
zero.

.Draining a single listener

[source,erlang]
----
ok = ranch:suspend_listener(Ref),
ok = ranch:wait_for_connections(Ref, '==', 0),
ok = ranch:stop_listener(Ref).
----

If you want to drain more than just one listener, it may be important to first suspend
them all before beginning to wait for their connection counts to reach zero. Otherwise,
the not yet suspended listeners will still be accepting connections while you wait for
the suspended ones to be drained.

.Draining multiple listeners

[source,erlang]
----
lists:foreach(
	fun (Ref) ->
		ok = ranch:suspend_listener(Ref)
	end,
	Refs
),
lists:foreach(
	fun (Ref) ->
		ok = ranch:wait_for_connections(Ref, '==', 0),
		ok = ranch:stop_listener(Ref)
	end,
	Refs
).
----

If you have long-running connection processes hosted by the listener you want to stop
gracefully, draining may take a long time, possibly forever. If you just want to give
the connection processes a chance to finish, but are not willing to wait for infinity,
the waiting part could be handled in a separate process.

.Draining a listener with a timeout

[source,erlang]
----
ok = ranch:suspend_listener(Ref),
{DrainPid, DrainRef} = spawn_monitor(
	fun () ->
		ok = ranch:wait_for_connections(Ref, '==', 0)
	end
),
receive
	{'DOWN', DrainRef, process, DrainPid, _} ->
		ok
after DrainTimeout ->
	exit(DrainPid, kill),
	ok
end,
ok = ranch:stop_listener(Ref).
----

To drain listeners automatically as part of your application shutdown routine,
use the `prep_stop/1` function of your application module.

.Draining listeners automatically on application shutdown

[source,erlang]
----
-module(my_app).

-behavior(application).

-export([start/2]).
-export([prep_stop/1]).
-export([stop/1]).

start(_StartType, _StartArgs) ->
	{ok, _} = ranch:start_listener(my_listener, ranch_tcp, #{}, my_protocol, []),
	my_app_sup:start_link().

prep_stop(State) ->
	ok = ranch:suspend_listener(my_listener),
	ok = ranch:wait_for_connections(my_listener, '==', 0),
	ok = ranch:stop_listener(my_listener),
	State.

stop(_State) ->
	ok.
----