aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README.md4
-rw-r--r--doc/overview.edoc4
-rw-r--r--erlang.mk72
-rw-r--r--examples/tcp_echo/Makefile6
-rw-r--r--examples/tcp_echo/README.md29
-rw-r--r--examples/tcp_echo/rebar.config4
-rw-r--r--examples/tcp_echo/relx.config2
-rw-r--r--examples/tcp_echo/src/echo_protocol.erl5
-rw-r--r--examples/tcp_echo/src/tcp_echo.app.src4
-rw-r--r--examples/tcp_echo/src/tcp_echo.erl12
-rwxr-xr-xexamples/tcp_echo/start.sh3
-rw-r--r--examples/tcp_reverse/Makefile6
-rw-r--r--examples/tcp_reverse/README.md33
-rw-r--r--examples/tcp_reverse/relx.config2
-rw-r--r--examples/tcp_reverse/src/reverse_protocol.erl73
-rw-r--r--examples/tcp_reverse/src/tcp_reverse.app.src15
-rw-r--r--examples/tcp_reverse/src/tcp_reverse_app.erl19
-rw-r--r--examples/tcp_reverse/src/tcp_reverse_sup.erl22
-rw-r--r--guide/introduction.md8
-rw-r--r--guide/listeners.md7
-rw-r--r--guide/parsers.md92
-rw-r--r--guide/toc.md42
-rw-r--r--manual/ranch.md171
-rw-r--r--manual/ranch_app.md28
-rw-r--r--manual/ranch_protocol.md35
-rw-r--r--manual/ranch_ssl.md97
-rw-r--r--manual/ranch_tcp.md47
-rw-r--r--manual/ranch_transport.md197
-rw-r--r--manual/toc.md11
-rw-r--r--src/ranch.app.src2
-rw-r--r--src/ranch.erl10
-rw-r--r--src/ranch_acceptor.erl11
-rw-r--r--src/ranch_conns_sup.erl89
-rw-r--r--src/ranch_listener_sup.erl10
-rw-r--r--src/ranch_ssl.erl33
-rw-r--r--src/ranch_tcp.erl7
-rw-r--r--src/ranch_transport.erl7
-rw-r--r--test/sendfile_SUITE.erl1
-rw-r--r--test/shutdown_SUITE.erl164
-rw-r--r--test/trap_exit_protocol.erl23
41 files changed, 1272 insertions, 137 deletions
diff --git a/Makefile b/Makefile
index bd48b7c..0535bd5 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ dep_ct_helper = https://github.com/extend/ct_helper.git master
# Options.
COMPILE_FIRST = ranch_transport
-CT_SUITES = acceptor sendfile
+CT_SUITES = acceptor sendfile shutdown
PLT_APPS = crypto public_key ssl
# Standard targets.
diff --git a/README.md b/README.md
index a88bfef..570d27d 100644
--- a/README.md
+++ b/README.md
@@ -22,9 +22,9 @@ to close any of the currently opened sockets.
Getting started
---------------
- * [Read the guide](http://ninenines.eu/docs/en/ranch/HEAD/guide/introduction)
+ * [Read the guide](http://ninenines.eu/docs/en/ranch/HEAD/guide)
+ * [Check the manual](http://ninenines.eu/docs/en/ranch/HEAD/manual)
* Look at the examples in the `examples/` directory
- * Build API documentation with `make docs`; open `doc/index.html`
Support
-------
diff --git a/doc/overview.edoc b/doc/overview.edoc
deleted file mode 100644
index baf4939..0000000
--- a/doc/overview.edoc
+++ /dev/null
@@ -1,4 +0,0 @@
-@author Lo�c Hoguin <[email protected]>
-@copyright 2011-2012 Lo�c Hoguin
-@version HEAD
-@title Socket acceptor pool for TCP protocols.
diff --git a/erlang.mk b/erlang.mk
index 617abdc..107cdd5 100644
--- a/erlang.mk
+++ b/erlang.mk
@@ -24,7 +24,7 @@ export PKG_FILE
PKG_FILE_URL ?= https://raw.github.com/extend/erlang.mk/master/packages.v1.tsv
define get_pkg_file
- wget -O $(PKG_FILE) $(PKG_FILE_URL)
+ wget --no-check-certificate -O $(PKG_FILE) $(PKG_FILE_URL) || rm $(PKG_FILE)
endef
# Verbosity and tweaks.
@@ -46,8 +46,36 @@ dtl_verbose = $(dtl_verbose_$(V))
gen_verbose_0 = @echo " GEN " $@;
gen_verbose = $(gen_verbose_$(V))
-.PHONY: all clean-all app clean deps clean-deps docs clean-docs \
- build-tests tests build-plt dialyze
+.PHONY: rel clean-rel all clean-all app clean deps clean-deps \
+ docs clean-docs build-tests tests build-plt dialyze
+
+# Release.
+
+RELX_CONFIG ?= $(CURDIR)/relx.config
+
+ifneq ($(wildcard $(RELX_CONFIG)),)
+
+RELX ?= $(CURDIR)/relx
+export RELX
+
+RELX_URL ?= https://github.com/erlware/relx/releases/download/v0.5.2/relx
+RELX_OPTS ?=
+
+define get_relx
+ wget -O $(RELX) $(RELX_URL) || rm $(RELX)
+ chmod +x $(RELX)
+endef
+
+rel: clean-rel all $(RELX)
+ @$(RELX) -c $(RELX_CONFIG) $(RELX_OPTS)
+
+$(RELX):
+ @$(call get_relx)
+
+clean-rel:
+ @rm -rf _rel
+
+endif
# Deps directory.
@@ -62,7 +90,13 @@ ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
# Application.
-ERL_LIBS ?= $(DEPS_DIR)
+ifeq ($(filter $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),)
+ifeq ($(ERL_LIBS),)
+ ERL_LIBS = $(DEPS_DIR)
+else
+ ERL_LIBS := $(ERL_LIBS):$(DEPS_DIR)
+endif
+endif
export ERL_LIBS
ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \
@@ -79,7 +113,7 @@ app: ebin/$(PROJECT).app
$(eval MODULES := $(shell find ebin -type f -name \*.beam \
| sed 's/ebin\///;s/\.beam/,/' | sed '$$s/.$$//'))
$(appsrc_verbose) cat src/$(PROJECT).app.src \
- | sed 's/{modules, \[\]}/{modules, \[$(MODULES)\]}/' \
+ | sed 's/{modules,[[:space:]]*\[\]}/{modules, \[$(MODULES)\]}/' \
> ebin/$(PROJECT).app
define compile_erl
@@ -152,13 +186,21 @@ deps: $(ALL_DEPS_DIRS)
done
clean-deps:
- @for dep in $(ALL_DEPS_DIRS) ; do $(MAKE) -C $$dep clean; done
+ @for dep in $(ALL_DEPS_DIRS) ; do \
+ if [ -f $$dep/Makefile ] ; then \
+ $(MAKE) -C $$dep clean ; \
+ else \
+ echo "include $(CURDIR)/erlang.mk" | $(MAKE) -f - -C $$dep clean ; \
+ fi ; \
+ done
# Documentation.
+EDOC_OPTS ?=
+
docs: clean-docs
$(gen_verbose) erl -noshell \
- -eval 'edoc:application($(PROJECT), ".", []), init:stop().'
+ -eval 'edoc:application($(PROJECT), ".", [$(EDOC_OPTS)]), init:stop().'
clean-docs:
$(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info
@@ -183,14 +225,26 @@ CT_RUN = ct_run \
# -cover test/cover.spec
CT_SUITES ?=
-CT_SUITES_FULL = $(addsuffix _SUITE,$(CT_SUITES))
+
+define test_target
+test_$(1): ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}'
+test_$(1): clean deps app build-tests
+ @if [ -d "test" ] ; \
+ then \
+ mkdir -p logs/ ; \
+ $(CT_RUN) -suite $(addsuffix _SUITE,$(1)) ; \
+ fi
+ $(gen_verbose) rm -f test/*.beam
+endef
+
+$(foreach test,$(CT_SUITES),$(eval $(call test_target,$(test))))
tests: ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}'
tests: clean deps app build-tests
@if [ -d "test" ] ; \
then \
mkdir -p logs/ ; \
- $(CT_RUN) -suite $(CT_SUITES_FULL) ; \
+ $(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) ; \
fi
$(gen_verbose) rm -f test/*.beam
diff --git a/examples/tcp_echo/Makefile b/examples/tcp_echo/Makefile
new file mode 100644
index 0000000..a7c2330
--- /dev/null
+++ b/examples/tcp_echo/Makefile
@@ -0,0 +1,6 @@
+PROJECT = tcp_echo
+
+DEPS = ranch
+dep_ranch = pkg://ranch master
+
+include ../../erlang.mk
diff --git a/examples/tcp_echo/README.md b/examples/tcp_echo/README.md
index ee8a8c8..d65fae5 100644
--- a/examples/tcp_echo/README.md
+++ b/examples/tcp_echo/README.md
@@ -1,18 +1,27 @@
-Ranch TCP Echo
-==============
+Ranch TCP echo example
+======================
-To compile this example you need rebar in your PATH.
+To try this example, you need GNU `make` and `git` in your PATH.
-Type the following command:
-```
-$ rebar get-deps compile
+To build the example, run the following command:
+
+``` bash
+$ make
```
-You can then start the Erlang node with the following command:
+To start the release in the foreground:
+
+``` bash
+$ ./_rel/bin/tcp_echo_example console
```
-./start.sh
+
+Then start a telnet session to port 5555:
+
+``` bash
+$ telnet localhost 5555
```
-Then start telnet as indicated and type in a few lines. Be
-aware that there is a timeout of 5 seconds without receiving
+Type in a few words and see them echoed back.
+
+Be aware that there is a timeout of 5 seconds without receiving
data before the example server disconnects your session.
diff --git a/examples/tcp_echo/rebar.config b/examples/tcp_echo/rebar.config
deleted file mode 100644
index 78300c9..0000000
--- a/examples/tcp_echo/rebar.config
+++ /dev/null
@@ -1,4 +0,0 @@
-{deps, [
- {ranch, ".*",
- {git, "git://github.com/extend/ranch.git", "master"}}
-]}.
diff --git a/examples/tcp_echo/relx.config b/examples/tcp_echo/relx.config
new file mode 100644
index 0000000..a850b71
--- /dev/null
+++ b/examples/tcp_echo/relx.config
@@ -0,0 +1,2 @@
+{release, {tcp_echo_example, "1"}, [tcp_echo]}.
+{extended_start_script, true}.
diff --git a/examples/tcp_echo/src/echo_protocol.erl b/examples/tcp_echo/src/echo_protocol.erl
index 85ea289..5ed79b3 100644
--- a/examples/tcp_echo/src/echo_protocol.erl
+++ b/examples/tcp_echo/src/echo_protocol.erl
@@ -1,7 +1,10 @@
%% Feel free to use, reuse and abuse the code in this file.
-module(echo_protocol).
--export([start_link/4, init/4]).
+-behaviour(ranch_protocol).
+
+-export([start_link/4]).
+-export([init/4]).
start_link(Ref, Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
diff --git a/examples/tcp_echo/src/tcp_echo.app.src b/examples/tcp_echo/src/tcp_echo.app.src
index 103fd56..af50890 100644
--- a/examples/tcp_echo/src/tcp_echo.app.src
+++ b/examples/tcp_echo/src/tcp_echo.app.src
@@ -1,10 +1,10 @@
%% Feel free to use, reuse and abuse the code in this file.
{application, tcp_echo, [
- {description, "Ranch TCP Echo example."},
+ {description, "Ranch TCP echo example."},
{vsn, "1"},
{modules, []},
- {registered, []},
+ {registered, [tcp_echo_sup]},
{applications, [
kernel,
stdlib,
diff --git a/examples/tcp_echo/src/tcp_echo.erl b/examples/tcp_echo/src/tcp_echo.erl
deleted file mode 100644
index 46d31da..0000000
--- a/examples/tcp_echo/src/tcp_echo.erl
+++ /dev/null
@@ -1,12 +0,0 @@
-%% Feel free to use, reuse and abuse the code in this file.
-
--module(tcp_echo).
-
-%% API.
--export([start/0]).
-
-%% API.
-
-start() ->
- ok = application:start(ranch),
- ok = application:start(tcp_echo).
diff --git a/examples/tcp_echo/start.sh b/examples/tcp_echo/start.sh
deleted file mode 100755
index 925cf36..0000000
--- a/examples/tcp_echo/start.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-erl -pa ebin deps/*/ebin -s tcp_echo \
- -eval "io:format(\"Run: telnet localhost 5555~n\")."
diff --git a/examples/tcp_reverse/Makefile b/examples/tcp_reverse/Makefile
new file mode 100644
index 0000000..20cc1ae
--- /dev/null
+++ b/examples/tcp_reverse/Makefile
@@ -0,0 +1,6 @@
+PROJECT = tcp_reverse
+
+DEPS = ranch
+dep_ranch = pkg://ranch master
+
+include ../../erlang.mk
diff --git a/examples/tcp_reverse/README.md b/examples/tcp_reverse/README.md
new file mode 100644
index 0000000..6a17772
--- /dev/null
+++ b/examples/tcp_reverse/README.md
@@ -0,0 +1,33 @@
+Ranch TCP reverse example
+=========================
+
+This example uses a `gen_server` to handle a protocol to revese input.
+See `reverse_protocol.erl` for the implementation. Documentation about
+this topic can be found in the guide:
+
+ http://ninenines.eu/docs/en/ranch/HEAD/guide/protocols/#using_gen_server
+
+To try this example, you need GNU `make` and `git` in your PATH.
+
+To build the example, run the following command:
+
+``` bash
+$ make
+```
+
+To start the release in the foreground:
+
+``` bash
+$ ./_rel/bin/tcp_reverse_example console
+```
+
+Then start a telnet session to port 5555:
+
+``` bash
+$ telnet localhost 5555
+```
+
+Type in a few words and see them reversed! Amazing!
+
+Be aware that there is a timeout of 5 seconds without receiving
+data before the example server disconnects your session.
diff --git a/examples/tcp_reverse/relx.config b/examples/tcp_reverse/relx.config
new file mode 100644
index 0000000..2a83916
--- /dev/null
+++ b/examples/tcp_reverse/relx.config
@@ -0,0 +1,2 @@
+{release, {tcp_reverse_example, "1"}, [tcp_reverse]}.
+{extended_start_script, true}.
diff --git a/examples/tcp_reverse/src/reverse_protocol.erl b/examples/tcp_reverse/src/reverse_protocol.erl
new file mode 100644
index 0000000..6f7c770
--- /dev/null
+++ b/examples/tcp_reverse/src/reverse_protocol.erl
@@ -0,0 +1,73 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+-module(reverse_protocol).
+-behaviour(gen_server).
+-behaviour(ranch_protocol).
+
+%% API.
+-export([start_link/4]).
+
+%% gen_server.
+-export([init/1]).
+-export([init/4]).
+-export([handle_call/3]).
+-export([handle_cast/2]).
+-export([handle_info/2]).
+-export([terminate/2]).
+-export([code_change/3]).
+
+-define(TIMEOUT, 5000).
+
+-record(state, {socket, transport}).
+
+%% API.
+
+start_link(Ref, Socket, Transport, Opts) ->
+ proc_lib:start_link(?MODULE, init, [Ref, Socket, Transport, Opts]).
+
+%% gen_server.
+
+%% This function is never called. We only define it so that
+%% we can use the -behaviour(gen_server) attribute.
+init([]) -> {ok, undefined}.
+
+init(Ref, Socket, Transport, _Opts = []) ->
+ ok = proc_lib:init_ack({ok, self()}),
+ ok = ranch:accept_ack(Ref),
+ ok = Transport:setopts(Socket, [{active, once}]),
+ gen_server:enter_loop(?MODULE, [],
+ #state{socket=Socket, transport=Transport},
+ ?TIMEOUT).
+
+handle_info({tcp, Socket, Data}, State=#state{
+ socket=Socket, transport=Transport}) ->
+ Transport:setopts(Socket, [{active, once}]),
+ Transport:send(Socket, reverse_binary(Data)),
+ {noreply, State, ?TIMEOUT};
+handle_info({tcp_closed, _Socket}, State) ->
+ {stop, normal, State};
+handle_info({tcp_error, _, Reason}, State) ->
+ {stop, Reason, State};
+handle_info(timeout, State) ->
+ {stop, normal, State};
+handle_info(_Info, State) ->
+ {stop, normal, State}.
+
+handle_call(_Request, _From, State) ->
+ {reply, ok, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% Internal.
+
+reverse_binary(B) when is_binary(B) ->
+ [list_to_binary(lists:reverse(binary_to_list(
+ binary:part(B, {0, byte_size(B)-2})
+ ))), "\r\n"].
diff --git a/examples/tcp_reverse/src/tcp_reverse.app.src b/examples/tcp_reverse/src/tcp_reverse.app.src
new file mode 100644
index 0000000..46cfca7
--- /dev/null
+++ b/examples/tcp_reverse/src/tcp_reverse.app.src
@@ -0,0 +1,15 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+{application, tcp_reverse, [
+ {description, "Ranch TCP reverse example."},
+ {vsn, "1"},
+ {modules, []},
+ {registered, [tcp_reverse_sup]},
+ {applications, [
+ kernel,
+ stdlib,
+ ranch
+ ]},
+ {mod, {tcp_reverse_app, []}},
+ {env, []}
+]}.
diff --git a/examples/tcp_reverse/src/tcp_reverse_app.erl b/examples/tcp_reverse/src/tcp_reverse_app.erl
new file mode 100644
index 0000000..106e527
--- /dev/null
+++ b/examples/tcp_reverse/src/tcp_reverse_app.erl
@@ -0,0 +1,19 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @private
+-module(tcp_reverse_app).
+-behaviour(application).
+
+%% API.
+-export([start/2]).
+-export([stop/1]).
+
+%% API.
+
+start(_Type, _Args) ->
+ {ok, _} = ranch:start_listener(tcp_reverse, 10,
+ ranch_tcp, [{port, 5555}], reverse_protocol, []),
+ tcp_reverse_sup:start_link().
+
+stop(_State) ->
+ ok.
diff --git a/examples/tcp_reverse/src/tcp_reverse_sup.erl b/examples/tcp_reverse/src/tcp_reverse_sup.erl
new file mode 100644
index 0000000..4264d18
--- /dev/null
+++ b/examples/tcp_reverse/src/tcp_reverse_sup.erl
@@ -0,0 +1,22 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @private
+-module(tcp_reverse_sup).
+-behaviour(supervisor).
+
+%% API.
+-export([start_link/0]).
+
+%% supervisor.
+-export([init/1]).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% supervisor.
+
+init([]) ->
+ {ok, {{one_for_one, 10, 10}, []}}.
diff --git a/guide/introduction.md b/guide/introduction.md
index c63eaef..531f68c 100644
--- a/guide/introduction.md
+++ b/guide/introduction.md
@@ -15,11 +15,3 @@ Prerequisites
It is assumed the developer already knows Erlang and has some experience
with socket programming and TCP protocols.
-
-In order to run the examples available in this user guide, you will need
-Erlang and rebar installed and in your $PATH.
-
-Please see the [rebar repository](https://github.com/basho/rebar) for
-downloading and building instructions. Please look up the environment
-variables documentation of your system for details on how to update the
-$PATH information.
diff --git a/guide/listeners.md b/guide/listeners.md
index a941c22..4d01544 100644
--- a/guide/listeners.md
+++ b/guide/listeners.md
@@ -56,9 +56,8 @@ examples directory. To do so, open a shell in the `examples/tcp_echo/`
directory and run the following commands:
``` bash
-% rebar get-deps compile
-% ./start.sh
-Listening on port 5555
+$ make
+$ ./_rel/bin/tcp_echo console
```
You can then connect to it using telnet and see the echo server reply
@@ -67,7 +66,7 @@ the `Ctrl+]` key to escape to the telnet command line and type
`quit` to exit.
```
-% telnet localhost 5555
+$ telnet localhost 5555
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
diff --git a/guide/parsers.md b/guide/parsers.md
new file mode 100644
index 0000000..e6e9ece
--- /dev/null
+++ b/guide/parsers.md
@@ -0,0 +1,92 @@
+Writing parsers
+===============
+
+There are three kinds of protocols:
+
+ * Text protocols
+ * Schema-less binary protocols
+ * Schema-based binary protocols
+
+This chapter introduces the first two kinds. It will not cover
+more advanced topics such as continuations or parser generators.
+
+This chapter isn't specifically about Ranch, we assume here that
+you know how to read data from the socket. The data you read and
+the data that hasn't been parsed is saved in a buffer. Every
+time you read from the socket, the data read is appended to the
+buffer. What happens next depends on the kind of protocol. We
+will only cover the first two.
+
+Parsing text
+------------
+
+Text protocols are generally line based. This means that we can't
+do anything with them until we receive the full line.
+
+A simple way to get a full line is to use `binary:split/{2,3}`.
+
+``` erlang
+case binary:split(Buffer, <<"\n">>) of
+ [_] ->
+ get_more_data(Buffer);
+ [Line, Rest] ->
+ handle_line(Line, Rest)
+end.
+```
+
+In the above example, we can have two results. Either there was
+a line break in the buffer and we get it split into two parts,
+the line and the rest of the buffer; or there was no line break
+in the buffer and we need to get more data from the socket.
+
+Next, we need to parse the line. The simplest way is to again
+split, here on space. The difference is that we want to split
+on all spaces character, as we want to tokenize the whole string.
+
+``` erlang
+case binary:split(Line, <<" ">>, [global]) of
+ [<<"HELLO">>] ->
+ be_polite();
+ [<<"AUTH">>, User, Password] ->
+ authenticate_user(User, Password);
+ [<<"QUIT">>, Reason] ->
+ quit(Reason)
+ %% ...
+end.
+```
+
+Pretty simple, right? Match on the command name, get the rest
+of the tokens in variables and call the respective functions.
+
+After doing this, you will want to check if there is another
+line in the buffer, and handle it immediately if any.
+Otherwise wait for more data.
+
+Parsing binary
+--------------
+
+Binary protocols can be more varied, although most of them are
+pretty similar. The first four bytes of a frame tend to be
+the size of the frame, which is followed by a certain number
+of bytes for the type of frame and then various parameters.
+
+Sometimes the size of the frame includes the first four bytes,
+sometimes not. Other times this size is encoded over two bytes.
+And even other times little-endian is used instead of big-endian.
+
+The general idea stays the same though.
+
+``` erlang
+<< Size:32, _/bits >> = Buffer,
+case Buffer of
+ << Frame:Size/binary, Rest/bits >> ->
+ handle_frame(Frame, Buffer);
+ _ ->
+ get_more_data(Buffer)
+end.
+```
+
+You will then need to parse this frame using binary pattern
+matching, and handle it. Then you will want to check if there
+is another frame fully received in the buffer, and handle it
+immediately if any. Otherwise wait for more data.
diff --git a/guide/toc.md b/guide/toc.md
index eac5338..4f17a22 100644
--- a/guide/toc.md
+++ b/guide/toc.md
@@ -1,37 +1,25 @@
Ranch User Guide
================
+The Ranch User Guide explores how to make best use of Ranch
+for writing powerful TCP applications.
+
+Introducing Ranch
+-----------------
+
* [Introduction](introduction.md)
- * Purpose
- * Prerequisites
+
+Using Ranch
+-----------
+
* [Listeners](listeners.md)
- * Purpose
- * Starting and stopping
- * Default transport options
- * Listening on a random port
- * Listening on privileged ports
- * Accepting connections on an existing socket
- * Limiting the number of concurrent connections
- * Upgrading
* [Transports](transports.md)
- * Purpose
- * TCP transport
- * SSL transport
- * Sending and receiving data
- * Writing a transport handler
* [Protocols](protocols.md)
- * Purpose
- * Writing a protocol handler
- * Using gen_server
+ * [Writing parsers](parsers.md)
+
+Advanced topics
+---------------
+
* [SSL client authentication](ssl_auth.md)
- * Purpose
- * Obtaining client certificates
- * Transport configuration
- * Authentication
* [Embedded mode](embedded.md)
- * Purpose
- * Embedding
* [Internals](internals.md)
- * Architecture
- * Number of acceptors
- * Platform-specific TCP features
diff --git a/manual/ranch.md b/manual/ranch.md
new file mode 100644
index 0000000..cf4ebe5
--- /dev/null
+++ b/manual/ranch.md
@@ -0,0 +1,171 @@
+ranch
+=====
+
+The `ranch` module provides functions for starting and
+manipulating Ranch listeners.
+
+Types
+-----
+
+### max_conns() = non_neg_integer() | infinity
+
+> Maximum number of connections allowed on this listener.
+>
+> This is a soft limit. The actual number of connections
+> might be slightly above the limit due to concurrency
+> when accepting new connections. Some connections may
+> also be removed from this count explicitly by the user
+> code.
+
+### ref() = any()
+
+> Unique name used to refer to a listener.
+
+Exports
+-------
+
+### accept_ack(Ref) -> ok
+
+> Types:
+> * Ref = ref()
+>
+> Acknowledge that the connection is accepted.
+>
+> This function MUST be used by a connection process to inform
+> Ranch that it initialized properly and let it perform any
+> additional operations before the socket can be safely used.
+
+### child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
+ -> supervisor:child_spec()
+
+> Types:
+> * Ref = ref()
+> * NbAcceptors = non_neg_integer()
+> * Transport = module()
+> * TransOpts = any()
+> * Protocol = module()
+> * ProtoOpts = any()
+>
+> Return child specifications for a new listener.
+>
+> This function can be used to embed a listener directly
+> in an application instead of letting Ranch handle it.
+
+### get_max_connections(Ref) -> MaxConns
+
+> Types:
+> * Ref = ref()
+> * MaxConns = max_conns()
+>
+> Return the max number of connections allowed for the given listener.
+
+### get_port(Ref) -> Port
+
+> Types:
+> * Ref = ref()
+> * Port = inet:port_number()
+>
+> Return the port for the given listener.
+
+### get_protocol_options(Ref) -> ProtoOpts
+
+> Types:
+> * Ref = ref()
+> * ProtoOpts = any()
+>
+> Return the protocol options set for the given listener.
+
+### remove_connection(Ref) -> ok
+
+> Types:
+> * Ref = ref()
+>
+> Do not count this connection when limiting the number of connections.
+>
+> You can use this function for long-running connection processes
+> which spend most of their time idling rather than consuming
+> resources. This allows Ranch to accept a lot more connections
+> without sacrificing the latency of the system.
+>
+> This function may only be called from a connection process.
+
+### set_max_connections(Ref, MaxConns) -> ok
+
+> Types:
+> * Ref = ref()
+> * MaxConns = max_conns()
+>
+> Set the max number of connections for the given listener.
+>
+> The change will be applied immediately. If the new value is
+> smaller than the previous one, Ranch will not kill the extra
+> connections, but will wait for them to terminate properly.
+
+### set_protocol_options(Ref, ProtoOpts) -> ok
+
+> Types:
+> * Ref = ref()
+> * ProtoOpts = any()
+>
+> Set the protocol options for the given listener.
+>
+> The change will be applied immediately for all new connections.
+> Old connections will not receive the new options.
+
+### start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
+ -> {ok, pid()} | {error, badarg}
+
+> Types:
+> * Ref = ref()
+> * NbAcceptors = non_neg_integer()
+> * Transport = module()
+> * TransOpts = any()
+> * Protocol = module()
+> * ProtoOpts = any()
+>
+> Start listening for connections using the given transport
+> and protocol. Returns the pid for this listener's supervisor.
+>
+> There are five additional transport options that apply
+> regardless of transport. They allow configuring how the
+> connections are supervised, rate limited and allow using
+> an already open listening socket.
+>
+> The `ack_timeout` option defines how long post-accept socket
+> initialization should take at a maximum. It defaults to `5000`.
+>
+> The `connection_type` option defines the type of process
+> that will handle the connection. It can be either `worker`
+> or `supervisor`. It defaults to `worker`.
+>
+> The `max_connections` option determines how many active
+> connections are allowed before Ranch starts throttling
+> the accept rate. This is a soft limit. It defaults to `1024`.
+> Using the value `infinity` will disable this functionality
+> entirely.
+>
+> The `shutdown` option determines the policy used with
+> regards to connection processes when shutting down the listener.
+> It can be either a positive integer indicating the max number
+> of ms the supervisor will wait before forcibly killing the
+> children, or the atom `brutal_kill`. It defaults to `5000`.
+>
+> The `socket` option allow passing an already open listening
+> socket. In this case, Ranch will not call `Transport:listen/1`
+> and so none of the transport specific options apply.
+
+### stop_listener(Ref) -> ok | {error, not_found}
+
+> Types:
+> * Ref = ref()
+>
+> Stop the given listener.
+>
+> The listener is stopped gracefully, first by closing the
+> listening port, then by stopping the connection processes.
+> These processes are stopped according to the `shutdown`
+> transport option, which may be set to brutally kill all
+> connection processes or give them some time to stop properly.
+>
+> This function does not return until the listener is
+> completely stopped.
diff --git a/manual/ranch_app.md b/manual/ranch_app.md
new file mode 100644
index 0000000..380931c
--- /dev/null
+++ b/manual/ranch_app.md
@@ -0,0 +1,28 @@
+The Ranch Application
+=====================
+
+Socket acceptor pool for TCP protocols.
+
+Dependencies
+------------
+
+The `ranch` application has no particular dependency required
+to start.
+
+It has optional dependencies that are only required when
+listening for SSL connections. The dependencies are `crypto`,
+`asn1`, `public_key` and `ssl`. They are started automatically
+if they weren't before.
+
+Environment
+-----------
+
+The `ranch` application defines one application environment
+configuration parameter.
+
+ - profile (false)
+ - When enabled, Ranch will start `etop` profiling automatically.
+
+You can use the `ranch_app:profile_output/0` function to stop
+profiling and output the results to the files `procs.profile`
+and `total.profile`. Do not use in production.
diff --git a/manual/ranch_protocol.md b/manual/ranch_protocol.md
new file mode 100644
index 0000000..3e8b62e
--- /dev/null
+++ b/manual/ranch_protocol.md
@@ -0,0 +1,35 @@
+ranch_protocol
+==============
+
+The `ranch_protocol` behaviour defines the interface used
+by Ranch protocols.
+
+Types
+-----
+
+None.
+
+Callbacks
+---------
+
+### start_link(Ref, Socket, Transport, ProtoOpts) -> {ok, pid()}
+
+> Types:
+> * Ref = ranch:ref()
+> * Socket = any()
+> * Transport = module()
+> * ProtoOpts = any()
+>
+> Start a new connection process for the given socket.
+>
+> The only purpose of this callback is to start a process that
+> will handle the socket. It must spawn the process, link and
+> then return the new pid. This function will always be called
+> from inside a supervisor.
+>
+> If any other value is returned, the supervisor will close the
+> socket and assume no process has been started.
+>
+> Do not perform any operation in this callback, as this would
+> block the supervisor responsible for starting connection
+> processes and degrade performance severely.
diff --git a/manual/ranch_ssl.md b/manual/ranch_ssl.md
new file mode 100644
index 0000000..d8bb140
--- /dev/null
+++ b/manual/ranch_ssl.md
@@ -0,0 +1,97 @@
+ranch_ssl
+=========
+
+The `ranch_ssl` module implements an SSL Ranch transport.
+
+Types
+-----
+
+### opts() = [{backlog, non_neg_integer()}
+ | {cacertfile, string()}
+ | {cacerts, [Der::binary()]}
+ | {cert, Der::binary()}
+ | {certfile, string()}
+ | {ciphers, [ssl:erl_cipher_suite()] | string()}
+ | {fail_if_no_peer_cert, boolean()}
+ | {hibernate_after, integer() | undefined}
+ | {ip, inet:ip_address()}
+ | {key, Der::binary()}
+ | {keyfile, string()}
+ | {next_protocols_advertised, [binary()]}
+ | {nodelay, boolean()}
+ | {password, string()}
+ | {port, inet:port_number()}
+ | {raw, non_neg_integer(), non_neg_integer(), non_neg_integer() | binary()}
+ | {reuse_session, fun()}
+ | {reuse_sessions, boolean()}
+ | {secure_renegotiate, boolean()}
+ | {verify, ssl:verify_type()}
+ | {verify_fun, {fun(), InitialUserState::term()}}]
+
+> Listen options.
+>
+> This does not represent the entirety of the options that can
+> be set on the socket, but only the options that should be
+> set independently of protocol implementation.
+
+Option descriptions
+-------------------
+
+Specifying a certificate is mandatory, either through the `cert`
+or the `certfile` option. None of the other options are required.
+
+The default value is given next to the option name.
+
+ - backlog (1024)
+ - Max length of the queue of pending connections.
+ - cacertfile
+ - Path to PEM encoded trusted certificates file used to verify peer certificates.
+ - cacerts
+ - List of DER encoded trusted certificates.
+ - cert
+ - DER encoded user certificate.
+ - certfile
+ - Path to the PEM encoded user certificate file. May also contain the private key.
+ - ciphers
+ - List of ciphers that clients are allowed to use.
+ - fail_if_no_peer_cert (false)
+ - Whether to refuse the connection if the client sends an empty certificate.
+ - hibernate_after (undefined)
+ - Time in ms after which SSL socket processes go into hibernation to reduce memory usage.
+ - ip
+ - Interface to listen on. Listen on all interfaces by default.
+ - key
+ - DER encoded user private key.
+ - keyfile
+ - Path to the PEM encoded private key file, if different than the certfile.
+ - next_protocols_advertised
+ - List of protocols to send to the client if it supports the Next Protocol extension.
+ - nodelay (true)
+ - Whether to enable TCP_NODELAY.
+ - password
+ - Password to the private key file, if password protected.
+ - port (0)
+ - TCP port number to listen on. 0 means a random port will be used.
+ - reuse_session
+ - Custom policy to decide whether a session should be reused.
+ - reuse_sessions (false)
+ - Whether to allow session reuse.
+ - secure_renegotiate (false)
+ - Whether to reject renegotiation attempts that do not conform to RFC5746.
+ - verify (verify_none)
+ - Use `verify_peer` to request a certificate from the client.
+ - verify_fun
+ - Custom policy to decide whether a client certificate is valid.
+
+Note that the client will not send a certificate unless the
+value for the `verify` option is set to `verify_peer`. This
+means that the `fail_if_no_peer_cert` only apply when combined
+with the `verify` option. The `verify_fun` option allows
+greater control over the client certificate validation.
+
+The `raw` option is unsupported.
+
+Exports
+-------
+
+None.
diff --git a/manual/ranch_tcp.md b/manual/ranch_tcp.md
new file mode 100644
index 0000000..d0f6054
--- /dev/null
+++ b/manual/ranch_tcp.md
@@ -0,0 +1,47 @@
+ranch_tcp
+=========
+
+The `ranch_tcp` module implements a TCP Ranch transport.
+
+Note that due to bugs in OTP up to at least R16B02, it is
+recommended to disable async threads when using the
+`sendfile` function of this transport, as it can make
+the threads stuck indefinitely.
+
+Types
+-----
+
+### opts() = [{backlog, non_neg_integer()}
+ | {ip, inet:ip_address()}
+ | {nodelay, boolean()}
+ | {port, inet:port_number()}
+ | {raw, non_neg_integer(), non_neg_integer(), non_neg_integer() | binary()}]
+
+> Listen options.
+>
+> This does not represent the entirety of the options that can
+> be set on the socket, but only the options that should be
+> set independently of protocol implementation.
+
+Option descriptions
+-------------------
+
+None of the options are required.
+
+The default value is given next to the option name.
+
+ - backlog (1024)
+ - Max length of the queue of pending connections.
+ - ip
+ - Interface to listen on. Listen on all interfaces by default.
+ - nodelay (true)
+ - Whether to enable TCP_NODELAY.
+ - port (0)
+ - TCP port number to listen on. 0 means a random port will be used.
+
+The `raw` option is unsupported.
+
+Exports
+-------
+
+None.
diff --git a/manual/ranch_transport.md b/manual/ranch_transport.md
new file mode 100644
index 0000000..291b0e4
--- /dev/null
+++ b/manual/ranch_transport.md
@@ -0,0 +1,197 @@
+ranch_transport
+===============
+
+The `ranch_transport` behaviour defines the interface used
+by Ranch transports.
+
+Types
+-----
+
+### sendfile_opts() = [{chunk_size, non_neg_integer()}]
+
+> Options used by the sendfile function and callbacks.
+>
+> Allows configuring the chunk size, in bytes. Defaults to 8191 bytes.
+
+Callbacks
+---------
+
+### accept(LSocket, Timeout)
+ -> {ok, CSocket} | {error, closed | timeout | atom()}
+
+> Types:
+> * LSocket = CSocket = any()
+> * Timeout = timeout()
+>
+> Accept a connection on the given listening socket.
+>
+> The `accept_ack` callback will be used to initialize the socket
+> after accepting the connection. This is most useful when the
+> transport is not raw TCP, like with SSL for example.
+
+### accept_ack(CSocket, Timeout) -> ok
+
+> Types:
+> * CSocket = any()
+> * Timeout = timeout()
+>
+> Perform post-accept initialization of the connection.
+>
+> This function will be called by connection processes
+> before performing any socket operation. It allows
+> transports that require extra initialization to perform
+> their task and make the socket ready to use.
+
+### close(CSocket) -> ok
+
+> Types:
+> * CSocket = any()
+>
+> Close the given socket.
+
+### controlling_process(CSocket, Pid)
+ -> ok | {error, closed | not_owner | atom()}
+
+> Types:
+> * CSocket = any()
+> * Pid = pid()
+>
+> Change the controlling process for the given socket.
+>
+> The controlling process is the process that is allowed to
+> perform operations on the socket, and that will receive
+> messages from the socket when active mode is used. When
+> the controlling process dies, the socket is closed.
+
+### listen(TransOpts) -> {ok, LSocket} | {error, atom()}
+
+> Types:
+> * TransOpts = any()
+> * LSocket = any()
+>
+> Listen for connections on the given port.
+>
+> The port is given as part of the transport options under
+> the key `port`. Any other option is transport dependent.
+>
+> The socket returned by this call can then be used to
+> accept connections. It is not possible to send or receive
+> data from the listening socket.
+
+### messages() -> {OK, Closed, Error}
+
+> Types:
+> * OK = Closed = Error = atom()
+>
+> Return the atoms used to identify messages sent in active mode.
+
+### name() -> Name
+
+> Types:
+> * Name = atom()
+>
+> Return the name of the transport.
+
+### peername(CSocket) -> {ok, {IP, Port}} | {error, atom()}
+
+> Types:
+> * CSocket = any()
+> * IP = inet:ip_address()
+> * Port = inet:port_number()
+>
+> Return the IP and port of the remote endpoint.
+
+### recv(CSocket, Length, Timeout)
+ -> {ok, Packet} | {error, closed | timeout | atom()}
+
+> Types:
+> * CSocket = any()
+> * Length = non_neg_integer()
+> * Timeout = timeout()
+> * Packet = iodata() | any()
+>
+> Receive data from the given socket when in passive mode.
+>
+> Trying to receive data from a socket that is in active mode
+> will return an error.
+>
+> A length of 0 will return any data available on the socket.
+>
+> While it is possible to use the timeout value `infinity`,
+> this is highly discouraged as this could cause your process
+> to get stuck waiting for data that will never come. This may
+> happen when a socket becomes half-open due to a crash of the
+> remote endpoint. Wi-Fi going down is another common culprit
+> of this issue.
+
+### send(CSocket, Packet) -> ok | {error, atom()}
+
+> Types:
+> * CSocket = any()
+> * Packet = iodata()
+>
+> Send data to the given socket.
+
+### sendfile(CSocket, File)
+ -> sendfile(CSocket, File, 0, 0, [])
+### sendfile(CSocket, File, Offset, Bytes)
+ -> sendfile(CSocket, File, Offset, Bytes, [])
+### sendfile(CSocket, File, Offset, Bytes, SfOpts)
+ -> {ok, SentBytes} | {error, atom()}
+
+> Types:
+> * CSocket = any()
+> * File = file:filename_all() | file:fd()
+> * Offset = non_neg_integer()
+> * Bytes = SentBytes = non_neg_integer()
+> * SfOpts = sendfile_opts()
+>
+> Send data from a file to the given socket.
+>
+> The file may be sent full or in parts, and may be specified
+> by its filename or by an already open file descriptor.
+>
+> Transports that manipulate TCP directly may use the
+> `file:sendfile/{2,4,5}` function, which calls the sendfile
+> syscall where applicable (on Linux, for example). Other
+> transports can use the `sendfile/6` function exported from
+> this module.
+
+### setopts(CSocket, TransOpts) -> ok | {error, atom()}
+
+> Types:
+> * CSocket = any()
+> * TransOpts = any()
+>
+> Change transport options for the given socket.
+>
+> This is mainly useful for switching to active or passive mode.
+
+### sockname(CSocket) -> {ok, {IP, Port}} | {error, atom()}
+
+> Types:
+> * CSocket = any()
+> * IP = inet:ip_address()
+> * Port = inet:port_number()
+>
+> Return the IP and port of the local endpoint.
+
+Exports
+-------
+
+### sendfile(Transport, CSocket, File, Offset, Bytes, SfOpts)
+ -> {ok, SentBytes} | {error, atom()}
+
+> Types:
+> * Transport = module()
+> * CSocket = any()
+> * File = file:filename_all() | file:fd()
+> * Offset = non_neg_integer()
+> * Bytes = SentBytes = non_neg_integer()
+> * SfOpts = sendfile_opts()
+>
+> Send data from a file to the given socket.
+>
+> This function emulates the function `file:sendfile/{2,4,5}`
+> and may be used when transports are not manipulating TCP
+> directly.
diff --git a/manual/toc.md b/manual/toc.md
new file mode 100644
index 0000000..af99d14
--- /dev/null
+++ b/manual/toc.md
@@ -0,0 +1,11 @@
+Ranch Function Reference
+========================
+
+The function reference documents the public interface of Ranch.
+
+ * [The Ranch Application](ranch_app.md)
+ * [ranch](ranch.md)
+ * [ranch_protocol](ranch_protocol.md)
+ * [ranch_ssl](ranch_ssl.md)
+ * [ranch_tcp](ranch_tcp.md)
+ * [ranch_transport](ranch_transport.md)
diff --git a/src/ranch.app.src b/src/ranch.app.src
index 03b8dae..bb6db94 100644
--- a/src/ranch.app.src
+++ b/src/ranch.app.src
@@ -14,7 +14,7 @@
{application, ranch, [
{description, "Socket acceptor pool for TCP protocols."},
- {vsn, "0.8.5"},
+ {vsn, "0.9.0"},
{modules, []},
{registered, [ranch_sup, ranch_server]},
{applications, [
diff --git a/src/ranch.erl b/src/ranch.erl
index 74497f0..641fc4d 100644
--- a/src/ranch.erl
+++ b/src/ranch.erl
@@ -120,7 +120,7 @@ child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
andalso is_atom(Protocol) ->
{{ranch_listener_sup, Ref}, {ranch_listener_sup, start_link, [
Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts
- ]}, permanent, 5000, supervisor, [ranch_listener_sup]}.
+ ]}, permanent, infinity, supervisor, [ranch_listener_sup]}.
%% @doc Acknowledge the accepted connection.
%%
@@ -128,7 +128,9 @@ child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
%% the protocol process before starting to use it.
-spec accept_ack(ref()) -> ok.
accept_ack(Ref) ->
- receive {shoot, Ref} -> ok end.
+ receive {shoot, Ref, Transport, Socket, AckTimeout} ->
+ Transport:accept_ack(Socket, AckTimeout)
+ end.
%% @doc Remove the calling process' connection from the pool.
%%
@@ -175,7 +177,7 @@ set_protocol_options(Ref, Opts) ->
%% It takes a list of options, a list of allowed keys and an accumulator.
%% This accumulator can be used to set default options that should never
%% be overriden.
--spec filter_options([{atom(), any()} | {atom(), any(), any(), any()}],
+-spec filter_options([{atom(), any()} | {raw, any(), any(), any()}],
[atom()], Acc) -> Acc when Acc :: [any()].
filter_options([], _, Acc) ->
Acc;
@@ -200,7 +202,7 @@ set_option_default(Opts, Key, Value) ->
end.
%% @doc Start the given applications if they were not already started.
--spec require(list(module())) -> ok.
+-spec require([atom()]) -> ok.
require([]) ->
ok;
require([App|Tail]) ->
diff --git a/src/ranch_acceptor.erl b/src/ranch_acceptor.erl
index f838b7d..da1aac5 100644
--- a/src/ranch_acceptor.erl
+++ b/src/ranch_acceptor.erl
@@ -48,4 +48,15 @@ loop(LSocket, Transport, ConnsSup) ->
{error, Reason} when Reason =/= closed ->
ok
end,
+ flush(),
?MODULE:loop(LSocket, Transport, ConnsSup).
+
+flush() ->
+ receive Msg ->
+ error_logger:error_msg(
+ "Ranch acceptor received unexpected message: ~p~n",
+ [Msg]),
+ flush()
+ after 0 ->
+ ok
+ end.
diff --git a/src/ranch_conns_sup.erl b/src/ranch_conns_sup.erl
index 245a5e0..308a1ab 100644
--- a/src/ranch_conns_sup.erl
+++ b/src/ranch_conns_sup.erl
@@ -20,34 +20,38 @@
-module(ranch_conns_sup).
%% API.
--export([start_link/4]).
+-export([start_link/6]).
-export([start_protocol/2]).
-export([active_connections/1]).
%% Supervisor internals.
--export([init/5]).
+-export([init/7]).
-export([system_continue/3]).
-export([system_terminate/4]).
-export([system_code_change/4]).
-type conn_type() :: worker | supervisor.
+-type shutdown() :: brutal_kill | timeout().
-record(state, {
parent = undefined :: pid(),
ref :: ranch:ref(),
conn_type :: conn_type(),
+ shutdown :: shutdown(),
transport = undefined :: module(),
protocol = undefined :: module(),
opts :: any(),
- max_conns = undefined :: non_neg_integer() | infinity
+ ack_timeout :: timeout(),
+ max_conns = undefined :: ranch:max_conns()
}).
%% API.
--spec start_link(ranch:ref(), conn_type(), module(), module()) -> {ok, pid()}.
-start_link(Ref, ConnType, Transport, Protocol) ->
+-spec start_link(ranch:ref(), conn_type(), shutdown(), module(),
+ timeout(), module()) -> {ok, pid()}.
+start_link(Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol) ->
proc_lib:start_link(?MODULE, init,
- [self(), Ref, ConnType, Transport, Protocol]).
+ [self(), Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol]).
%% We can safely assume we are on the same node as the supervisor.
%%
@@ -92,26 +96,28 @@ active_connections(SupPid) ->
%% Supervisor internals.
--spec init(pid(), ranch:ref(), conn_type(), module(), module()) -> no_return().
-init(Parent, Ref, ConnType, Transport, Protocol) ->
+-spec init(pid(), ranch:ref(), conn_type(), shutdown(),
+ module(), timeout(), module()) -> no_return().
+init(Parent, Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol) ->
process_flag(trap_exit, true),
ok = ranch_server:set_connections_sup(Ref, self()),
MaxConns = ranch_server:get_max_connections(Ref),
Opts = ranch_server:get_protocol_options(Ref),
ok = proc_lib:init_ack(Parent, {ok, self()}),
loop(#state{parent=Parent, ref=Ref, conn_type=ConnType,
- transport=Transport, protocol=Protocol, opts=Opts,
- max_conns=MaxConns}, 0, 0, []).
+ shutdown=Shutdown, transport=Transport, protocol=Protocol,
+ opts=Opts, ack_timeout=AckTimeout, max_conns=MaxConns}, 0, 0, []).
loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
transport=Transport, protocol=Protocol, opts=Opts,
- max_conns=MaxConns}, CurConns, NbChildren, Sleepers) ->
+ ack_timeout=AckTimeout, max_conns=MaxConns},
+ CurConns, NbChildren, Sleepers) ->
receive
{?MODULE, start_protocol, To, Socket} ->
case Protocol:start_link(Ref, Socket, Transport, Opts) of
{ok, Pid} ->
Transport:controlling_process(Socket, Pid),
- Pid ! {shoot, Ref},
+ Pid ! {shoot, Ref, Transport, Socket, AckTimeout},
put(Pid, true),
CurConns2 = CurConns + 1,
if CurConns2 < MaxConns ->
@@ -122,8 +128,12 @@ loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
loop(State, CurConns2, NbChildren + 1,
[To|Sleepers])
end;
- _ ->
+ Ret ->
To ! self(),
+ error_logger:error_msg(
+ "Ranch listener ~p connection process start failure; "
+ "~p:start_link/4 returned: ~999999p~n",
+ [Ref, Protocol, Ret]),
Transport:close(Socket),
loop(State, CurConns, NbChildren, Sleepers)
end;
@@ -147,7 +157,7 @@ loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
loop(State#state{opts=Opts2},
CurConns, NbChildren, Sleepers);
{'EXIT', Parent, Reason} ->
- exit(Reason);
+ terminate(State, Reason, NbChildren);
{'EXIT', Pid, Reason} when Sleepers =:= [] ->
report_error(Ref, Protocol, Pid, Reason),
erase(Pid),
@@ -186,12 +196,59 @@ loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
[Ref, Msg])
end.
+-spec terminate(#state{}, any(), non_neg_integer()) -> no_return().
+%% Kill all children and then exit. We unlink first to avoid
+%% getting a message for each child getting killed.
+terminate(#state{shutdown=brutal_kill}, Reason, _) ->
+ Pids = get_keys(true),
+ _ = [begin
+ unlink(P),
+ exit(P, kill)
+ end || P <- Pids],
+ exit(Reason);
+%% Attempt to gracefully shutdown all children.
+terminate(#state{shutdown=Shutdown}, Reason, NbChildren) ->
+ shutdown_children(),
+ _ = if
+ Shutdown =:= infinity ->
+ ok;
+ true ->
+ erlang:send_after(Shutdown, self(), kill)
+ end,
+ wait_children(NbChildren),
+ exit(Reason).
+
+%% Monitor processes so we can know which ones have shutdown
+%% before the timeout. Unlink so we avoid receiving an extra
+%% message. Then send a shutdown exit signal.
+shutdown_children() ->
+ Pids = get_keys(true),
+ _ = [begin
+ monitor(process, P),
+ unlink(P),
+ exit(P, shutdown)
+ end || P <- Pids],
+ ok.
+
+wait_children(0) ->
+ ok;
+wait_children(NbChildren) ->
+ receive
+ {'DOWN', _, process, Pid, _} ->
+ _ = erase(Pid),
+ wait_children(NbChildren - 1);
+ kill ->
+ Pids = get_keys(true),
+ _ = [exit(P, kill) || P <- Pids],
+ ok
+ end.
+
system_continue(_, _, {State, CurConns, NbChildren, Sleepers}) ->
loop(State, CurConns, NbChildren, Sleepers).
-spec system_terminate(any(), _, _, _) -> no_return().
-system_terminate(Reason, _, _, _) ->
- exit(Reason).
+system_terminate(Reason, _, _, {State, _, NbChildren, _}) ->
+ terminate(State, Reason, NbChildren).
system_code_change(Misc, _, _, _) ->
{ok, Misc}.
diff --git a/src/ranch_listener_sup.erl b/src/ranch_listener_sup.erl
index 0392105..30017d0 100644
--- a/src/ranch_listener_sup.erl
+++ b/src/ranch_listener_sup.erl
@@ -36,15 +36,15 @@ start_link(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) ->
%% supervisor.
init({Ref, NbAcceptors, Transport, TransOpts, Protocol}) ->
+ AckTimeout = proplists:get_value(ack_timeout, TransOpts, 5000),
ConnType = proplists:get_value(connection_type, TransOpts, worker),
+ Shutdown = proplists:get_value(shutdown, TransOpts, 5000),
ChildSpecs = [
- %% conns_sup
{ranch_conns_sup, {ranch_conns_sup, start_link,
- [Ref, ConnType, Transport, Protocol]},
+ [Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol]},
permanent, infinity, supervisor, [ranch_conns_sup]},
- %% acceptors_sup
{ranch_acceptors_sup, {ranch_acceptors_sup, start_link,
- [Ref, NbAcceptors, Transport, TransOpts]
- }, permanent, infinity, supervisor, [ranch_acceptors_sup]}
+ [Ref, NbAcceptors, Transport, TransOpts]},
+ permanent, infinity, supervisor, [ranch_acceptors_sup]}
],
{ok, {{rest_for_one, 10, 10}, ChildSpecs}}.
diff --git a/src/ranch_ssl.erl b/src/ranch_ssl.erl
index 097b31c..dc29a18 100644
--- a/src/ranch_ssl.erl
+++ b/src/ranch_ssl.erl
@@ -30,6 +30,7 @@
-export([messages/0]).
-export([listen/1]).
-export([accept/2]).
+-export([accept_ack/2]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
@@ -165,13 +166,18 @@ listen(Opts) ->
%% @see ssl:transport_accept/2
%% @see ssl:ssl_accept/2
-spec accept(ssl:sslsocket(), timeout())
- -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom() | tuple()}.
+ -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}.
accept(LSocket, Timeout) ->
- case ssl:transport_accept(LSocket, Timeout) of
- {ok, CSocket} ->
- ssl_accept(CSocket, Timeout);
+ ssl:transport_accept(LSocket, Timeout).
+
+-spec accept_ack(ssl:sslsocket(), timeout()) -> ok.
+accept_ack(CSocket, Timeout) ->
+ case ssl:ssl_accept(CSocket, Timeout) of
+ ok ->
+ ok;
{error, Reason} ->
- {error, Reason}
+ ok = close(CSocket),
+ error(Reason)
end.
%% @private Experimental. Open a connection to the given host and port number.
@@ -209,7 +215,7 @@ send(Socket, Packet) ->
ssl:send(Socket, Packet).
%% @equiv sendfile(Socket, Filename, 0, 0, [])
--spec sendfile(ssl:sslsocket(), file:name_all())
+-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, Filename) ->
sendfile(Socket, Filename, 0, 0, []).
@@ -275,21 +281,6 @@ close(Socket) ->
%% Internal.
-%% This call always times out, either because a numeric timeout value
-%% was given, or because we've decided to use 5000ms instead of infinity.
-%% This value should be reasonable enough for the moment.
--spec ssl_accept(ssl:sslsocket(), timeout())
- -> {ok, ssl:sslsocket()} | {error, {ssl_accept, atom()}}.
-ssl_accept(Socket, infinity) ->
- ssl_accept(Socket, 5000);
-ssl_accept(Socket, Timeout) ->
- case ssl:ssl_accept(Socket, Timeout) of
- ok ->
- {ok, Socket};
- {error, Reason} ->
- {error, {ssl_accept, Reason}}
- end.
-
%% Unfortunately the implementation of elliptic-curve ciphers that has
%% been introduced in R16B01 is incomplete. Depending on the particular
%% client, this can cause the TLS handshake to break during key
diff --git a/src/ranch_tcp.erl b/src/ranch_tcp.erl
index abf7612..d5d5003 100644
--- a/src/ranch_tcp.erl
+++ b/src/ranch_tcp.erl
@@ -24,6 +24,7 @@
-export([messages/0]).
-export([listen/1]).
-export([accept/2]).
+-export([accept_ack/2]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
@@ -90,6 +91,10 @@ listen(Opts) ->
accept(LSocket, Timeout) ->
gen_tcp:accept(LSocket, Timeout).
+-spec accept_ack(inet:socket(), timeout()) -> ok.
+accept_ack(_, _) ->
+ ok.
+
%% @private Experimental. Open a connection to the given host and port number.
%% @see gen_tcp:connect/3
%% @todo Probably filter Opts?
@@ -126,7 +131,7 @@ send(Socket, Packet) ->
gen_tcp:send(Socket, Packet).
%% @equiv sendfile(Socket, File, Offset, Bytes, [])
--spec sendfile(inet:socket(), file:name_all())
+-spec sendfile(inet:socket(), file:name_all() | file:fd())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, Filename) ->
sendfile(Socket, Filename, 0, 0, []).
diff --git a/src/ranch_transport.erl b/src/ranch_transport.erl
index fe06420..5cf10d1 100644
--- a/src/ranch_transport.erl
+++ b/src/ranch_transport.erl
@@ -46,7 +46,10 @@
%% Accept connections with the given listening socket.
-callback accept(socket(), timeout())
- -> {ok, socket()} | {error, closed | timeout | atom() | tuple()}.
+ -> {ok, socket()} | {error, closed | timeout | atom()}.
+
+%% Perform post-accept operations on the socket.
+-callback accept_ack(socket(), timeout()) -> ok.
%% Experimental. Open a connection to the given host and port number.
-callback connect(string(), inet:port_number(), opts())
@@ -65,7 +68,7 @@
-callback send(socket(), iodata()) -> ok | {error, atom()}.
%% Send a file on a socket.
--callback sendfile(socket(), file:name())
+-callback sendfile(socket(), file:name() | file:fd())
-> {ok, non_neg_integer()} | {error, atom()}.
%% Send part of a file on a socket.
diff --git a/test/sendfile_SUITE.erl b/test/sendfile_SUITE.erl
index dc05fe6..c74659e 100644
--- a/test/sendfile_SUITE.erl
+++ b/test/sendfile_SUITE.erl
@@ -305,6 +305,7 @@ sockets(Config) ->
end,
_ = spawn_link(Fun),
{ok, Server} = Transport:accept(LSocket, 500),
+ ok = Transport:accept_ack(Server, 500),
receive
{ok, Client} ->
ok = Transport:close(LSocket),
diff --git a/test/shutdown_SUITE.erl b/test/shutdown_SUITE.erl
new file mode 100644
index 0000000..109c381
--- /dev/null
+++ b/test/shutdown_SUITE.erl
@@ -0,0 +1,164 @@
+%% Copyright (c) 2013, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(shutdown_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+
+%% ct.
+-export([all/0]).
+-export([init_per_suite/1]).
+-export([end_per_suite/1]).
+
+%% Tests.
+
+-export([brutal_kill/1]).
+-export([infinity/1]).
+-export([infinity_trap_exit/1]).
+-export([timeout/1]).
+-export([timeout_trap_exit/1]).
+
+%% ct.
+
+all() ->
+ [brutal_kill, infinity, infinity_trap_exit, timeout, timeout_trap_exit].
+
+init_per_suite(Config) ->
+ ok = application:start(ranch),
+ Config.
+
+end_per_suite(_) ->
+ application:stop(ranch),
+ ok.
+
+%% Tests.
+
+brutal_kill(_) ->
+ Name = brutal_kill,
+ {ok, ListenerSup} = ranch:start_listener(Name, 1,
+ ranch_tcp, [{port, 0}, {shutdown, brutal_kill}],
+ echo_protocol, []),
+ Port = ranch:get_port(Name),
+ {ok, _} = gen_tcp:connect("localhost", Port, []),
+ receive after 100 -> ok end,
+ ListenerSupChildren = supervisor:which_children(ListenerSup),
+ {_, ConnsSup, _, _}
+ = lists:keyfind(ranch_conns_sup, 1, ListenerSupChildren),
+ [{_, Pid, _, _}] = supervisor:which_children(ConnsSup),
+ true = is_process_alive(Pid),
+ ranch:stop_listener(Name),
+ receive after 100 -> ok end,
+ false = is_process_alive(Pid),
+ false = is_process_alive(ListenerSup),
+ {error, _} = gen_tcp:connect("localhost", Port, []),
+ ok.
+
+infinity(_) ->
+ Name = infinity,
+ {ok, ListenerSup} = ranch:start_listener(Name, 1,
+ ranch_tcp, [{port, 0}, {shutdown, infinity}],
+ echo_protocol, []),
+ Port = ranch:get_port(Name),
+ {ok, _} = gen_tcp:connect("localhost", Port, []),
+ receive after 100 -> ok end,
+ ListenerSupChildren = supervisor:which_children(ListenerSup),
+ {_, ConnsSup, _, _}
+ = lists:keyfind(ranch_conns_sup, 1, ListenerSupChildren),
+ [{_, Pid, _, _}] = supervisor:which_children(ConnsSup),
+ true = is_process_alive(Pid),
+ ranch:stop_listener(Name),
+ receive after 100 -> ok end,
+ false = is_process_alive(Pid),
+ false = is_process_alive(ListenerSup),
+ {error, _} = gen_tcp:connect("localhost", Port, []),
+ ok.
+
+infinity_trap_exit(_) ->
+ Name = infinity_trap_exit,
+ {ok, ListenerSup} = ranch:start_listener(Name, 1,
+ ranch_tcp, [{port, 0}, {shutdown, infinity}],
+ trap_exit_protocol, []),
+ Port = ranch:get_port(Name),
+ {ok, _} = gen_tcp:connect("localhost", Port, []),
+ receive after 100 -> ok end,
+ ListenerSupChildren = supervisor:which_children(ListenerSup),
+ {_, ConnsSup, _, _}
+ = lists:keyfind(ranch_conns_sup, 1, ListenerSupChildren),
+ [{_, Pid, _, _}] = supervisor:which_children(ConnsSup),
+ true = is_process_alive(Pid),
+ %% This call will block infinitely.
+ SpawnPid = spawn(fun() -> ranch:stop_listener(Name) end),
+ receive after 100 -> ok end,
+ %% The protocol traps exit signals, and ignore them, so it won't die.
+ true = is_process_alive(Pid),
+ %% The listener will stay up forever too.
+ true = is_process_alive(ListenerSup),
+ %% We can't connect, though.
+ {error, _} = gen_tcp:connect("localhost", Port, []),
+ %% Killing the process unblocks everything.
+ exit(Pid, kill),
+ receive after 100 -> ok end,
+ false = is_process_alive(ListenerSup),
+ false = is_process_alive(SpawnPid),
+ ok.
+
+%% Same as infinity because the protocol doesn't trap exits.
+timeout(_) ->
+ Name = timeout,
+ {ok, ListenerSup} = ranch:start_listener(Name, 1,
+ ranch_tcp, [{port, 0}, {shutdown, 500}],
+ echo_protocol, []),
+ Port = ranch:get_port(Name),
+ {ok, _} = gen_tcp:connect("localhost", Port, []),
+ receive after 100 -> ok end,
+ ListenerSupChildren = supervisor:which_children(ListenerSup),
+ {_, ConnsSup, _, _}
+ = lists:keyfind(ranch_conns_sup, 1, ListenerSupChildren),
+ [{_, Pid, _, _}] = supervisor:which_children(ConnsSup),
+ true = is_process_alive(Pid),
+ ranch:stop_listener(Name),
+ receive after 100 -> ok end,
+ false = is_process_alive(Pid),
+ false = is_process_alive(ListenerSup),
+ {error, _} = gen_tcp:connect("localhost", Port, []),
+ ok.
+
+timeout_trap_exit(_) ->
+ Name = timeout_trap_exit,
+ {ok, ListenerSup} = ranch:start_listener(Name, 1,
+ ranch_tcp, [{port, 0}, {shutdown, 500}],
+ trap_exit_protocol, []),
+ Port = ranch:get_port(Name),
+ {ok, _} = gen_tcp:connect("localhost", Port, []),
+ receive after 100 -> ok end,
+ ListenerSupChildren = supervisor:which_children(ListenerSup),
+ {_, ConnsSup, _, _}
+ = lists:keyfind(ranch_conns_sup, 1, ListenerSupChildren),
+ [{_, Pid, _, _}] = supervisor:which_children(ConnsSup),
+ true = is_process_alive(Pid),
+ %% This call will block for the duration of the shutdown.
+ SpawnPid = spawn(fun() -> ranch:stop_listener(Name) end),
+ receive after 100 -> ok end,
+ %% The protocol traps exit signals, and ignore them, so it won't die.
+ true = is_process_alive(Pid),
+ %% The listener will stay up for now too.
+ true = is_process_alive(ListenerSup),
+ %% We can't connect, though.
+ {error, _} = gen_tcp:connect("localhost", Port, []),
+ %% Wait for the timeout to finish and see that everything is killed.
+ receive after 500 -> ok end,
+ false = is_process_alive(Pid),
+ false = is_process_alive(ListenerSup),
+ false = is_process_alive(SpawnPid),
+ ok.
diff --git a/test/trap_exit_protocol.erl b/test/trap_exit_protocol.erl
new file mode 100644
index 0000000..a0c4329
--- /dev/null
+++ b/test/trap_exit_protocol.erl
@@ -0,0 +1,23 @@
+-module(trap_exit_protocol).
+-behaviour(ranch_protocol).
+
+-export([start_link/4]).
+-export([init/4]).
+
+start_link(Ref, Socket, Transport, Opts) ->
+ Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
+ {ok, Pid}.
+
+init(Ref, Socket, Transport, _Opts = []) ->
+ process_flag(trap_exit, true),
+ ok = ranch:accept_ack(Ref),
+ loop(Socket, Transport).
+
+loop(Socket, Transport) ->
+ case Transport:recv(Socket, 0, infinity) of
+ {ok, Data} ->
+ Transport:send(Socket, Data),
+ loop(Socket, Transport);
+ _ ->
+ ok = Transport:close(Socket)
+ end.