diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c index f10820653cc5..dbdc29310e36 100644 --- a/erts/emulator/drivers/common/inet_drv.c +++ b/erts/emulator/drivers/common/inet_drv.c @@ -839,6 +839,7 @@ static size_t my_strnlen(const char *s, size_t maxlen) #define TCP_ADDF_SHUTDOWN_WR_DONE 256 /* A shutdown(sock, SHUT_WR) or SHUT_RDWR was made */ #define TCP_ADDF_LINGER_ZERO 512 /* Discard driver queue on port close */ #define TCP_ADDF_SENDFILE 1024 /* Send from an fd instead of the driver queue */ +#define TCP_ADDF_NO_READ_AHEAD 2048 /* Don't read ahead in packet modes */ /* *_REQ_* replies */ #define INET_REP_ERROR 0 @@ -896,6 +897,7 @@ static size_t my_strnlen(const char *s, size_t maxlen) #define INET_OPT_TTL 46 /* IP_TTL */ #define INET_OPT_RECVTTL 47 /* IP_RECVTTL ancillary data */ #define TCP_OPT_NOPUSH 48 /* super-Nagle, aka TCP_CORK */ +#define INET_LOPT_TCP_READ_AHEAD 49 /* Read ahead of packet data */ #define INET_LOPT_DEBUG 99 /* Enable/disable DEBUG for a socket */ /* SCTP options: a separate range, from 100: */ @@ -7082,6 +7084,21 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len) } continue; + case INET_LOPT_TCP_READ_AHEAD: + DDBG(desc, + ("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] " + "inet_set_opts(read_ahead) -> %d (%s)\r\n", + __LINE__, + desc->s, driver_caller(desc->port), ival, B2S(ival)) ); + if (desc->sprotocol == IPPROTO_TCP) { + tcp_descriptor* tdesc = (tcp_descriptor*) desc; + if (! ival) + tdesc->tcp_add_flags |= TCP_ADDF_NO_READ_AHEAD; + else + tdesc->tcp_add_flags &= ~TCP_ADDF_NO_READ_AHEAD; + } + continue; + case INET_LOPT_LINE_DELIM: DDBG(desc, ("INET-DRV-DBG[%d][" SOCKET_FSTR ",%T] " @@ -7754,8 +7771,16 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len) /* XXX: UDP sockets could also trigger immediate read here NIY */ if ((desc->stype==SOCK_STREAM) && desc->active) { - if (!old_active || (desc->htype != old_htype)) { - /* passive => active change OR header type change in active mode */ + if (! old_active) { + /* passive => active change */ + return 1; + } + if (desc->htype != old_htype) { + tcp_descriptor *tdesc = (tcp_descriptor *) desc; + /* Header type change in active mode. + * Invalidate the calculated packet remaining length. + */ + tdesc->i_remain = 0; return 1; } @@ -9035,6 +9060,17 @@ static ErlDrvSSizeT inet_fill_opts(inet_descriptor* desc, } continue; + case INET_LOPT_TCP_READ_AHEAD: + if (desc->sprotocol == IPPROTO_TCP) { + tcp_descriptor* tdesc = (tcp_descriptor*) desc; + *ptr++ = opt; + ival = !(tdesc->tcp_add_flags & TCP_ADDF_NO_READ_AHEAD); + put_int32(ival, ptr); + } else { + TRUNCATE_TO(0,ptr); + } + continue; + case INET_OPT_PRIORITY: #ifdef SO_PRIORITY type = SO_PRIORITY; @@ -11267,8 +11303,9 @@ static void inet_input_count(inet_descriptor* desc, ErlDrvSizeT len) /* ** Set new size on buffer, used when packet size is determined -** and the buffer is to small. -** buffer must have a size of at least len bytes (counting from ptr_start!) +** and the buffer is to small. Expand the buffer to len bytes +** from ptr_start, don't move ptr_start's position and +** keep the content before ptr_start. */ static int tcp_expand_buffer(tcp_descriptor* desc, int len) { @@ -11365,9 +11402,16 @@ static void tcp_clear_output(tcp_descriptor* desc) /* Move data so that ptr_start point at buf->orig_bytes */ static void tcp_restart_input(tcp_descriptor* desc) { + ASSERT( desc->i_buf != NULL ); + if (desc->i_ptr_start != desc->i_buf->orig_bytes) { - int n = desc->i_ptr - desc->i_ptr_start; + int n; + + ASSERT( (desc->i_ptr_start != NULL) && + (desc->i_buf != NULL) && + (desc->i_buf->orig_bytes != NULL) ); + n = desc->i_ptr - desc->i_ptr_start; DEBUGF(("tcp_restart_input: move %d bytes\r\n", n)); sys_memmove(desc->i_buf->orig_bytes, desc->i_ptr_start, n); desc->i_ptr_start = desc->i_buf->orig_bytes; @@ -11457,7 +11501,8 @@ static tcp_descriptor* tcp_inet_copy(tcp_descriptor* desc,SOCKET s, copy_desc->send_timeout_close = desc->send_timeout_close; copy_desc->tcp_add_flags = desc->tcp_add_flags - & (TCP_ADDF_SHOW_ECONNRESET | TCP_ADDF_LINGER_ZERO); + & (TCP_ADDF_SHOW_ECONNRESET | TCP_ADDF_LINGER_ZERO | + TCP_ADDF_NO_READ_AHEAD); /* The new port will be linked and connected to the original caller */ port = driver_create_port(port, owner, "tcp_inet", (ErlDrvData) copy_desc); @@ -12427,19 +12472,79 @@ static int tcp_recv_error(tcp_descriptor* desc, int err) } +/* To be called after packet_get_length() has returned that + * more bytes are needed (or when we know that it would). + */ +static int packet_header_length(tcp_descriptor *desc) { + int n, hlen; + + if (! (desc->tcp_add_flags & TCP_ADDF_NO_READ_AHEAD)) + return 0; /* Read ahead */ + + switch (desc->inet.htype) { + case TCP_PB_RAW: + return 0; + + /* Return how many more bytes we should read to make + * packet_get_length() return the packet length. + * + * Set hlen to the minimal header bytes, for starters. + */ + case TCP_PB_1: hlen = 1; break; + case TCP_PB_2: hlen = 2; break; + case TCP_PB_4: hlen = 4; break; + case TCP_PB_RM: hlen = 4; break; + case TCP_PB_ASN1: hlen = 2; break; + case TCP_PB_SSL_TLS: hlen = 5; break; + case TCP_PB_CDR: hlen = 12; break; + case TCP_PB_FCGI: hlen = sizeof(struct fcgi_head); break; + case TCP_PB_TPKT: hlen = 4; break; + default: + /* We should always be able to read another byte + * to see if we then can deduce the packet length. + * Note that for line mode packet formats, + * not a length in a header, this is very inefficient, + * but there is no other way to not read ahead. + * For TCP_PB_ASN1 it is also inefficient, but we + * would have to re-implement quite some decoding rules + * here to figure out a better value that probably isn't + * that much better since some field have to be read one byte + * at the time to find the end of the field. + * + * Just don't combine TCP_ADDF_NO_READ_AHEAD + * with non-suitable packet types. + */ + return 1; + } + n = desc->i_ptr - desc->i_ptr_start; + ASSERT(n >= 0); + + if (hlen > n) + return hlen - n; + else + /* Since packet_get_length() couldn't return a length + * and since the minimal header size above apprently isn't enough + * we need at least another byte + */ + return 1; +} + /* ** Calculate number of bytes that remain to read before deliver ** Assume buf, ptr_start, ptr has been setup ** -** return > 0 if more to read -** = 0 if holding complete packet -** < 0 on error -** -** if return value == 0 then *len will hold the length of the first packet -** return value > 0 then if *len == 0 then value means upperbound -** *len > 0 then value means exact -** +** return == 0 if we have a complete packet. *len holds the packet length. +** > 0 is how many bytes to read. +** *len is the packet's length. +** == 0 if we don't know, then the return value is either +** the minimum bytes to read to figure out the packet's +** length or the maximum bytes to read which avoids +** numerous calls to sock_recv(), depending on +** TCP_ADDF_NO_READ_AHEAD. +** > 0 Implies that the return value is +** exactly what's missing. +** < 0 if there is a decode error */ static int tcp_remain(tcp_descriptor* desc, int* len) { @@ -12449,7 +12554,7 @@ static int tcp_remain(tcp_descriptor* desc, int* len) int n = desc->i_ptr - ptr; /* number of bytes read */ int tlen; - tlen = packet_get_length(desc->inet.htype, ptr, n, + tlen = packet_get_length(desc->inet.htype, ptr, n, desc->inet.psize, desc->i_bufsz, desc->inet.delimiter, &desc->http_state); @@ -12457,38 +12562,54 @@ static int tcp_remain(tcp_descriptor* desc, int* len) desc->inet.port, desc->inet.s, n, nfill, nsz, tlen)); if (tlen > 0) { + *len = tlen; if (tlen <= n) { /* got a packet */ - *len = tlen; DEBUGF((" => nothing remain packet=%d\r\n", tlen)); return 0; } else { /* need known more */ + int nread; if (tcp_expand_buffer(desc, tlen) < 0) return -1; - *len = tlen - n; - DEBUGF((" => remain=%d\r\n", *len)); - return *len; + nread = tlen - n; + DEBUGF((" => remain=%d\r\n", nread)); + return nread; } } else if (tlen == 0) { /* need unknown more */ + int nread; + *len = 0; - if (nsz == 0) { - if (nfill == n) { + nread = packet_header_length(desc); + if (nread != 0) { + tcp_restart_input(desc); /* Move the data to buffer start */ + return nread; + } + + if (nsz == 0) { /* No remaining space - buffer is full */ + if (nfill == n) { /* The current packet itself fills the buffer */ if (desc->inet.psize != 0 && desc->inet.psize > nfill) { + /* There is a max packet size that is larger than + * the current buffer - expand the buffer + * to max packet size + */ if (tcp_expand_buffer(desc, desc->inet.psize) < 0) return -1; - return desc->inet.psize; + return desc->inet.psize - n;; } else goto error; } DEBUGF((" => restart more=%d\r\n", nfill - n)); + tcp_restart_input(desc); /* Move the data to buffer start */ + /* Return the unused buffer space before desc->i_ptr_start */ return nfill - n; } else { DEBUGF((" => more=%d \r\n", nsz)); - return nsz; - } + tcp_restart_input(desc); /* Move the data to buffer start */ + return nsz; /* Remaining buffer space */ + } } error: @@ -12498,7 +12619,7 @@ static int tcp_remain(tcp_descriptor* desc, int* len) /* ** Deliver all packets ready -** if len == 0 then check start with a check for ready packet +** if len == 0 then start with a check for ready packet */ static int tcp_deliver(tcp_descriptor* desc, int len) { @@ -12510,13 +12631,13 @@ static int tcp_deliver(tcp_descriptor* desc, int len) /* empty buffer or waiting for more input */ if ((desc->i_buf == NULL) || (desc->i_remain > 0)) return 0; - if ((n = tcp_remain(desc, &len)) != 0) { - if (n < 0) /* packet error */ - return n; - if (len > 0) /* more data pending */ - desc->i_remain = len; - return 0; - } + if ((n = tcp_remain(desc, &len)) < 0) /* Packet error */ + return n; + else if (0 < n) { /* Packet incomplete */ + if (0 < len) /* We know n bytes are missing */ + desc->i_remain = n; + return 0; + } } while (len > 0) { @@ -12564,24 +12685,24 @@ static int tcp_deliver(tcp_descriptor* desc, int len) } count++; - len = 0; - if (!desc->inet.active) { + if (! desc->inet.active) { cancel_multi_timer(desc, INETP(desc)->port, &tcp_inet_recv_timeout); sock_select(INETP(desc),(FD_READ|FD_CLOSE),0); if (desc->i_buf != NULL) tcp_restart_input(desc); + len = 0; } - else if (desc->i_buf != NULL) { - if ((n = tcp_remain(desc, &len)) != 0) { - if (n < 0) /* packet error */ - return n; - tcp_restart_input(desc); - if (len > 0) - desc->i_remain = len; - len = 0; - } - } + else if (desc->i_buf == NULL) { + len = 0; + } + else if ((n = tcp_remain(desc, &len)) < 0) /* Packet error */ + return n; + else if (0 < n) { /* Packet incomplete */ + if (0 < len) /* We know n bytes are missing */ + desc->i_remain = n; + len = 0; + } } return count; } @@ -12589,8 +12710,9 @@ static int tcp_deliver(tcp_descriptor* desc, int len) static int tcp_recv(tcp_descriptor* desc, int request_len) { - int n; - int len; + /* request_len is 0 unless called from TCP_REQ_RECV, and then + * inet.htype == TCP_PB_RAW and inet.active is INET_PASSIVE (false). + */ int nread; if (desc->i_buf == NULL) { /* allocate a read buffer */ @@ -12604,77 +12726,93 @@ static int tcp_recv(tcp_descriptor* desc, int request_len) desc->i_bufsz = sz; /* use i_bufsz not i_buf->orig_size ! */ desc->i_ptr_start = desc->i_buf->orig_bytes; desc->i_ptr = desc->i_ptr_start; - nread = sz; - if (request_len > 0) - desc->i_remain = request_len; - else + if (request_len > 0) { + nread = desc->i_remain = request_len; + } + else { + nread = packet_header_length(desc); + if (nread == 0) + nread = sz; /* Read ahead */ desc->i_remain = 0; + } } - else if (request_len > 0) { /* we have a data in buffer and a request */ - n = desc->i_ptr - desc->i_ptr_start; + else if (request_len > 0) { /* we have data in buffer and a request */ + int n = desc->i_ptr - desc->i_ptr_start; + if (n >= request_len) return tcp_deliver(desc, request_len); else if (tcp_expand_buffer(desc, request_len) < 0) return tcp_recv_error(desc, ENOMEM); - else - desc->i_remain = nread = request_len - n; + desc->i_remain = nread = request_len - n; } else if (desc->i_remain == 0) { /* poll remain from buffer data */ - if ((nread = tcp_remain(desc, &len)) < 0) + int len; + + if ((nread = tcp_remain(desc, &len)) < 0) /* Packet error */ return tcp_recv_error(desc, EMSGSIZE); - else if (nread == 0) - return tcp_deliver(desc, len); - else if (len > 0) - desc->i_remain = len; /* set remain */ + else if (nread == 0) /* We have a complete packet */ + return tcp_deliver(desc, len); + else if (0 < len) /* We know nread bytes are missing */ + desc->i_remain = nread; } else /* remain already set use it */ nread = desc->i_remain; - - DEBUGF(("tcp_recv(%p): s=%d about to read %d bytes...\r\n", - desc->inet.port, desc->inet.s, nread)); - n = sock_recv(desc->inet.s, desc->i_ptr, nread, 0); + for (;;) { + int n; - if (IS_SOCKET_ERROR(n)) { - int err = sock_errno(); - if (err == ECONNRESET) { - DEBUGF((" => detected close (connreset)\r\n")); - if (desc->tcp_add_flags & TCP_ADDF_SHOW_ECONNRESET) - return tcp_recv_error(desc, err); - else - return tcp_recv_closed(desc); - } - if (err == ERRNO_BLOCK) { - DEBUGF((" => would block\r\n")); - return 0; - } - else { - DEBUGF((" => error: %d\r\n", err)); - return tcp_recv_error(desc, err); - } - } - else if (n == 0) { - DEBUGF((" => detected close\r\n")); - return tcp_recv_closed(desc); - } + DEBUGF(("tcp_recv(%p): s=%d about to read %d bytes...\r\n", + desc->inet.port, desc->inet.s, nread)); - DEBUGF((" => got %d bytes\r\n", n)); - desc->i_ptr += n; - if (desc->i_remain > 0) { - desc->i_remain -= n; - if (desc->i_remain == 0) - return tcp_deliver(desc, desc->i_ptr - desc->i_ptr_start); - } - else { - nread = tcp_remain(desc, &len); - if (nread < 0) - return tcp_recv_error(desc, EMSGSIZE); - else if (nread == 0) - return tcp_deliver(desc, len); - else if (len > 0) - desc->i_remain = len; /* set remain */ - } - return 0; + n = sock_recv(desc->inet.s, desc->i_ptr, nread, 0); + + if (IS_SOCKET_ERROR(n)) { + int err = sock_errno(); + if (err == ECONNRESET) { + DEBUGF((" => detected close (connreset)\r\n")); + if (desc->tcp_add_flags & TCP_ADDF_SHOW_ECONNRESET) + return tcp_recv_error(desc, err); + else + return tcp_recv_closed(desc); + } + if (err == ERRNO_BLOCK) { + DEBUGF((" => would block\r\n")); + return 0; + } + else { + DEBUGF((" => error: %d\r\n", err)); + return tcp_recv_error(desc, err); + } + } + else if (n == 0) { + DEBUGF((" => detected close\r\n")); + return tcp_recv_closed(desc); + } + ASSERT(nread >= n); + + DEBUGF((" => got %d bytes\r\n", n)); + desc->i_ptr += n; + if (desc->i_remain > 0) { + desc->i_remain -= n; + if (desc->i_remain == 0) + return tcp_deliver(desc, desc->i_ptr - desc->i_ptr_start); + else + /* We just tried to read the whole packet, + * no point in trying again immediately + */ + return 0; + } + else { + int len; + + if ((nread = tcp_remain(desc, &len)) < 0) /* Packet error */ + return tcp_recv_error(desc, EMSGSIZE); + else if (nread == 0) /* We have a complete packet */ + return tcp_deliver(desc, len); + else if (0 < len) /* We know nread bytes are missing */ + desc->i_remain = nread; + } + } /* for (;;) */ } diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam index 960065c0d80f..73d42afa1744 100644 Binary files a/erts/preloaded/ebin/prim_inet.beam and b/erts/preloaded/ebin/prim_inet.beam differ diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl index b12a29bf83b2..bdc4e97ad333 100644 --- a/erts/preloaded/src/prim_inet.erl +++ b/erts/preloaded/src/prim_inet.erl @@ -1576,6 +1576,7 @@ enc_opt(show_econnreset) -> ?INET_LOPT_TCP_SHOW_ECONNRESET; enc_opt(line_delimiter) -> ?INET_LOPT_LINE_DELIM; enc_opt(raw) -> ?INET_OPT_RAW; enc_opt(bind_to_device) -> ?INET_OPT_BIND_TO_DEVICE; +enc_opt(read_ahead) -> ?INET_LOPT_TCP_READ_AHEAD; enc_opt(debug) -> ?INET_OPT_DEBUG; % Names of SCTP opts: enc_opt(sctp_rtoinfo) -> ?SCTP_OPT_RTOINFO; @@ -1647,6 +1648,7 @@ dec_opt(?INET_LOPT_TCP_SHOW_ECONNRESET) -> show_econnreset; dec_opt(?INET_LOPT_LINE_DELIM) -> line_delimiter; dec_opt(?INET_OPT_RAW) -> raw; dec_opt(?INET_OPT_BIND_TO_DEVICE) -> bind_to_device; +dec_opt(?INET_LOPT_TCP_READ_AHEAD) -> read_ahead; dec_opt(?INET_OPT_DEBUG) -> debug; dec_opt(I) when is_integer(I) -> undefined. @@ -1759,6 +1761,7 @@ type_opt_1(read_packets) -> uint; type_opt_1(netns) -> binary; type_opt_1(show_econnreset) -> bool; type_opt_1(bind_to_device) -> binary; +type_opt_1(read_ahead) -> bool; type_opt_1(debug) -> bool; %% %% SCTP options (to be set). If the type is a record type, the corresponding diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index f6efbea077ad..0008a1304870 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -999,7 +999,7 @@ connect_options() -> header, active, packet, packet_size, buffer, mode, deliver, line_delimiter, exit_on_close, high_watermark, low_watermark, high_msgq_watermark, low_msgq_watermark, send_timeout, send_timeout_close, delay_send, raw, - show_econnreset, bind_to_device]. + show_econnreset, bind_to_device, read_ahead]. connect_options(Opts, Mod) -> BaseOpts = @@ -1089,7 +1089,7 @@ listen_options() -> header, active, packet, buffer, mode, deliver, backlog, ipv6_v6only, exit_on_close, high_watermark, low_watermark, high_msgq_watermark, low_msgq_watermark, send_timeout, send_timeout_close, delay_send, - packet_size, raw, show_econnreset, bind_to_device]. + packet_size, raw, show_econnreset, bind_to_device, read_ahead]. listen_options(Opts, Mod) -> BaseOpts = diff --git a/lib/kernel/src/inet_int.hrl b/lib/kernel/src/inet_int.hrl index 2f50f2c23cbd..76b3f3ce023c 100644 --- a/lib/kernel/src/inet_int.hrl +++ b/lib/kernel/src/inet_int.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2022. All Rights Reserved. +%% Copyright Ericsson AB 1997-2024. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -167,6 +167,7 @@ -define(INET_OPT_TTL, 46). -define(INET_OPT_RECVTTL, 47). -define(TCP_OPT_NOPUSH, 48). +-define(INET_LOPT_TCP_READ_AHEAD, 49). -define(INET_OPT_DEBUG, 99). % Specific SCTP options: separate range: -define(SCTP_OPT_RTOINFO, 100). diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index c93bb2759634..776886e6fe32 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2023. All Rights Reserved. +%% Copyright Ericsson AB 2011-2024. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -233,7 +233,8 @@ listen(Name, Host) -> fam_listen(Family, Name, Host) -> ForcedOptions = - [Family, {active, false}, {packet, 4}, {nodelay, true}], + [Family, {active, false}, {packet, 4}, + {read_ahead, false}, {nodelay, true}], ListenFun = fun (First, Last, ListenOptions) -> listen_loop( @@ -640,7 +641,8 @@ do_setup( inet_tcp_dist:merge_options( ConnectOptions, get_ssl_options(client)), - [Family, binary, {active, false}, {packet, 4}, {nodelay, true}], + [Family, binary, {active, false}, {packet, 4}, + {read_ahead, false}, {nodelay, true}], [{server_name_indication, Host}]), KTLS = proplists:get_value(ktls, Opts, false), dist_util:reset_timer(Timer), @@ -943,7 +945,7 @@ inet_set_ktls( inet:setopts( Socket, [list, {packet, Packet}, {packet_size, PacketSize}, - {header, Header}, {active, Active}]) + {header, Header}, {read_ahead, true}, {active, Active}]) of ok -> ok; diff --git a/lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl b/lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl index d350cc81391a..2c06ad2fcb2a 100644 --- a/lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl +++ b/lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2023. All Rights Reserved. +%% Copyright Ericsson AB 2023-2024. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ listen_open(_NetAddress, Options) -> inet_epmd_dist:merge_options( Options, [{active, false}, {mode, binary}, {packet, 0}, - inet_epmd_dist:nodelay()], + {read_ahead, false}, inet_epmd_dist:nodelay()], [])}. %% ------------------------------------------------------------ @@ -111,7 +111,7 @@ connect(NetAddress, _Timer, Options) -> inet_epmd_dist:merge_options( Options, [{active, false}, {mode, binary}, {packet, 0}, - inet_epmd_dist:nodelay()], + {read_ahead, false}, inet_epmd_dist:nodelay()], []), #net_address{ address = {Ip, Port} } = NetAddress, maybe