Skip to content

Commit

Permalink
Merge branch 'ph/fix-enfile/OTP-9990' into maint
Browse files Browse the repository at this point in the history
* ph/fix-enfile/OTP-9990:
  Update preloaded prim_inet.beam
  gen_tcp: Use correct specs for accept and listen
  prim_inet: Catch system_limit in open_port
  Fix returned error from gen_tcp:accept/1,2 when running out of ports
  • Loading branch information
psyeugenic committed Mar 19, 2012
2 parents 662e3ff + 65ba37d commit 68ba8d7
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 28 deletions.
2 changes: 1 addition & 1 deletion erts/emulator/beam/io.c
Expand Up @@ -743,7 +743,7 @@ driver_create_port(ErlDrvPort creator_port_ix, /* Creating port */
return (ErlDrvTermData) -1; /* pid does not exist */
}
if ((port_num = get_free_port()) < 0) {
errno = ENFILE;
errno = SYSTEM_LIMIT;
erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
erts_smp_mtx_unlock(&erts_driver_list_lock);
return (ErlDrvTermData) -1;
Expand Down
38 changes: 22 additions & 16 deletions erts/emulator/drivers/common/inet_drv.c
Expand Up @@ -553,6 +553,12 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n)
# define VALGRIND_MAKE_MEM_DEFINED(ptr,size)
#endif

/*
Magic errno value used locally for return of {error, system_limit}
- the emulator definition of SYSTEM_LIMIT is not available here.
*/
#define INET_ERRNO_SYSTEM_LIMIT (15 << 8)

/*----------------------------------------------------------------------------
** Interface constants.
**
Expand Down Expand Up @@ -1645,6 +1651,17 @@ static struct erl_drv_entry dummy_sctp_driver_entry =

#endif

/* return lowercase string form of errno value */
static char *errno_str(int err)
{
switch (err) {
case INET_ERRNO_SYSTEM_LIMIT:
return "system_limit";
default:
return erl_errno_id(err);
}
}

/* general control reply function */
static ErlDrvSSizeT ctl_reply(int rep, char* buf, ErlDrvSizeT len,
char** rbuf, ErlDrvSizeT rsize)
Expand All @@ -1665,13 +1682,9 @@ static ErlDrvSSizeT ctl_reply(int rep, char* buf, ErlDrvSizeT len,
/* general control error reply function */
static ErlDrvSSizeT ctl_error(int err, char** rbuf, ErlDrvSizeT rsize)
{
char response[256]; /* Response buffer. */
char* s;
char* t;
char* s = errno_str(err);

for (s = erl_errno_id(err), t = response; *s; s++, t++)
*t = tolower(*s);
return ctl_reply(INET_REP_ERROR, response, t-response, rbuf, rsize);
return ctl_reply(INET_REP_ERROR, s, strlen(s), rbuf, rsize);
}

static ErlDrvSSizeT ctl_xerror(char* xerr, char** rbuf, ErlDrvSizeT rsize)
Expand All @@ -1683,14 +1696,7 @@ static ErlDrvSSizeT ctl_xerror(char* xerr, char** rbuf, ErlDrvSizeT rsize)

static ErlDrvTermData error_atom(int err)
{
char errstr[256];
char* s;
char* t;

for (s = erl_errno_id(err), t = errstr; *s; s++, t++)
*t = tolower(*s);
*t = '\0';
return driver_mk_atom(errstr);
return driver_mk_atom(errno_str(err));
}


Expand Down Expand Up @@ -8051,7 +8057,7 @@ static ErlDrvData tcp_inet_start(ErlDrvPort port, char* args)

/* Copy a descriptor, by creating a new port with same settings
* as the descriptor desc.
* return NULL on error (ENFILE no ports avail)
* return NULL on error (SYSTEM_LIMIT no ports avail)
*/
static tcp_descriptor* tcp_inet_copy(tcp_descriptor* desc,SOCKET s,
ErlDrvTermData owner, int* err)
Expand Down Expand Up @@ -8090,7 +8096,7 @@ static tcp_descriptor* tcp_inet_copy(tcp_descriptor* desc,SOCKET s,
/* The new port will be linked and connected to the original caller */
port = driver_create_port(port, owner, "tcp_inet", (ErlDrvData) copy_desc);
if ((long)port == -1) {
*err = ENFILE;
*err = INET_ERRNO_SYSTEM_LIMIT;
FREE(copy_desc);
return NULL;
}
Expand Down
Binary file modified erts/preloaded/ebin/prim_inet.beam
Binary file not shown.
6 changes: 4 additions & 2 deletions erts/preloaded/src/prim_inet.erl
Expand Up @@ -83,8 +83,10 @@ open(Protocol, Family, Type, Req, Data) ->
end
catch
%% The only (?) way to get here is to try to open
%% the sctp driver when it does not exist
error:badarg -> {error,eprotonosupport}
%% the sctp driver when it does not exist (badarg)
error:badarg -> {error, eprotonosupport};
%% system_limit if out of port slots
error:system_limit -> {error, system_limit}
end.

enc_family(inet) -> ?INET_AF_INET;
Expand Down
4 changes: 3 additions & 1 deletion lib/kernel/doc/src/gen_tcp.xml
Expand Up @@ -235,7 +235,9 @@ do_recv(Sock, Bs) ->
<p>Returns <c>{ok, <anno>Socket</anno>}</c> if a connection is established,
or <c>{error, closed}</c> if <c><anno>ListenSocket</anno></c> is closed,
or <c>{error, timeout}</c> if no connection is established
within the specified time. May also return a POSIX error
within the specified time,
or <c>{error, system_limit}</c> if all available ports in the
Erlang emulator are in use. May also return a POSIX error
value if something else goes wrong, see inet(3) for possible
error values.</p>
<p>Packets can be sent to the returned socket <c><anno>Socket</anno></c>
Expand Down
2 changes: 1 addition & 1 deletion lib/kernel/doc/src/inet.xml
Expand Up @@ -149,7 +149,7 @@ fe80::204:acff:fe17:bf38
<fsummary>Return a descriptive string for an error reason</fsummary>
<desc>
<p>Returns a diagnostic error string. See the section below
for possible <c><anno>Posix</anno></c> values and the corresponding
for possible Posix values and the corresponding
strings.</p>
</desc>
</func>
Expand Down
6 changes: 3 additions & 3 deletions lib/kernel/src/gen_tcp.erl
Expand Up @@ -175,7 +175,7 @@ try_connect([], _Port, _Opts, _Timer, _Mod, Err) ->
Port :: inet:port_number(),
Options :: [listen_option()],
ListenSocket :: socket(),
Reason :: inet:posix().
Reason :: system_limit | inet:posix().

listen(Port, Opts) ->
Mod = mod(Opts, undefined),
Expand All @@ -194,7 +194,7 @@ listen(Port, Opts) ->
-spec accept(ListenSocket) -> {ok, Socket} | {error, Reason} when
ListenSocket :: socket(),
Socket :: socket(),
Reason :: closed | timeout | inet:posix().
Reason :: closed | timeout | system_limit | inet:posix().

accept(S) ->
case inet_db:lookup_socket(S) of
Expand All @@ -208,7 +208,7 @@ accept(S) ->
ListenSocket :: socket(),
Timeout :: timeout(),
Socket :: socket(),
Reason :: closed | timeout | inet:posix().
Reason :: closed | timeout | system_limit | inet:posix().

accept(S, Time) when is_port(S) ->
case inet_db:lookup_socket(S) of
Expand Down
6 changes: 4 additions & 2 deletions lib/kernel/src/inet.erl
Expand Up @@ -1218,11 +1218,13 @@ port_list(Name) ->
%% utils
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec format_error(Posix) -> string() when
Posix :: posix().
-spec format_error(Reason) -> string() when
Reason :: posix() | system_limit.

format_error(exbadport) -> "invalid port state";
format_error(exbadseq) -> "bad command sequence";
format_error(system_limit) ->
"a system limit was hit, probably not enough ports";
format_error(Tag) ->
erl_posix_msg:message(Tag).

Expand Down
53 changes: 51 additions & 2 deletions lib/kernel/test/gen_tcp_misc_SUITE.erl
Expand Up @@ -39,7 +39,8 @@
accept_timeouts_in_order/1,accept_timeouts_in_order2/1,
accept_timeouts_in_order3/1,accept_timeouts_mixed/1,
killing_acceptor/1,killing_multi_acceptors/1,killing_multi_acceptors2/1,
several_accepts_in_one_go/1,active_once_closed/1, send_timeout/1, send_timeout_active/1,
several_accepts_in_one_go/1, accept_system_limit/1,
active_once_closed/1, send_timeout/1, send_timeout_active/1,
otp_7731/1, zombie_sockets/1, otp_7816/1, otp_8102/1,
otp_9389/1]).

Expand Down Expand Up @@ -71,7 +72,7 @@ all() ->
accept_timeouts_in_order, accept_timeouts_in_order2,
accept_timeouts_in_order3, accept_timeouts_mixed,
killing_acceptor, killing_multi_acceptors,
killing_multi_acceptors2, several_accepts_in_one_go,
killing_multi_acceptors2, several_accepts_in_one_go, accept_system_limit,
active_once_closed, send_timeout, send_timeout_active, otp_7731,
zombie_sockets, otp_7816, otp_8102, otp_9389].

Expand Down Expand Up @@ -1837,6 +1838,54 @@ wait_until_accepting(Proc,N) ->
end.


accept_system_limit(suite) ->
[];
accept_system_limit(doc) ->
["Check that accept returns {error, system_limit} "
"(and not {error, enfile}) when running out of ports"];
accept_system_limit(Config) when is_list(Config) ->
?line {ok, LS} = gen_tcp:listen(0, []),
?line {ok, TcpPort} = inet:port(LS),
?line Connector = spawn_link(fun () -> connector(TcpPort) end),
?line ok = acceptor(LS, false, []),
?line Connector ! stop,
ok.

acceptor(LS, GotSL, A) ->
case gen_tcp:accept(LS, 1000) of
{ok, S} ->
acceptor(LS, GotSL, [S|A]);
{error, system_limit} ->
acceptor(LS, true, A);
{error, timeout} when GotSL ->
ok;
{error, timeout} ->
error
end.

connector(TcpPort) ->
ManyPorts = open_ports([]),
ConnF = fun (Port) ->
case catch gen_tcp:connect({127,0,0,1}, TcpPort, []) of
{ok, Sock} ->
Sock;
_Error ->
port_close(Port)
end
end,
R = [ConnF(Port) || Port <- lists:sublist(ManyPorts, 10)],
receive stop -> R end.

open_ports(L) ->
case catch open_port({spawn_driver, "ram_file_drv"}, []) of
Port when is_port(Port) ->
open_ports([Port|L]);
{'EXIT', {system_limit, _}} ->
{L1, L2} = lists:split(5, L),
[port_close(Port) || Port <- L1],
L2
end.


active_once_closed(suite) ->
[];
Expand Down

0 comments on commit 68ba8d7

Please sign in to comment.