Unexpected difference between timeouts in large io.Copy() and many io.Copyn()

616 views
Skip to first unread message

Rémy Oudompheng

unread,
Aug 24, 2011, 2:27:35 AM8/24/11
to golan...@googlegroups.com
Hello,

Following my last message, I notice something that appears to be a larger
issue to me: basically, I have two methods to send a large byte array
over a socket. I can create a bytes.Buffer() and either io.Copy() it
to the TCPConn or do many io.Copyn().

And the behaviour of both is different when I set a timeout on the TCP
connection. In the attached code, I have a client that reads at a
specific transfer rate from a server that sends 10 million bytes. When
using io.Copy() to send the buffer, I am getting a timeout if the client
does not read *fast enough*.

With a large io.Copy() :

$ ./test2
accept: %!s(<nil>)
server: write tcp 192.168.1.4:49344: resource temporarily unavailable
after 3784704 bytes
client: EOF

With many small io.Copyn(), the expected behaviour

$ ./test2
accept: %!s(<nil>)
server: EOF after 10000000 bytes
client: EOF

When having a look at the source code for io.Copy() and
TCPConn.ReadFrom(), it didn't seem to me that there would be any
difference between the two approches. (I m using Go r59 in a Linux
mchine).

Regards,
--
R�my.

test2.go

Russ Cox

unread,
Aug 24, 2011, 10:37:00 AM8/24/11
to remyoud...@gmail.com, golan...@googlegroups.com
I think the solution is to delete the timeout support.

Russ Cox

unread,
Aug 24, 2011, 10:38:46 AM8/24/11
to remyoud...@gmail.com, golan...@googlegroups.com
On Wed, Aug 24, 2011 at 10:37, Russ Cox <r...@golang.org> wrote:
> I think the solution is to delete the timeout support.

That said, I know no one will go for that.
Please run your program under strace -f
and see where the error comes from.
If it's a partial write that we're not handling
correctly, we should fix that.

Russ

Rémy Oudompheng

unread,
Aug 24, 2011, 1:55:21 PM8/24/11
to golang-nuts

I ran
strace -f -r -e read,write ./test2
and got the attached output.

The first write is :
write(8, "\0\0\0"..., 10000000) = 65536
which is a bit puzzling.

Now the things seem clearer: the netFD struct receives
a giant write, and the (*netFD)Write() function loops until
the whole buffer has been written. Since the deadlines are
not reset after a partial write (which is probably a desirable
feature to avoid extremely slow peers), the routine times out.

It is probably specific to the case where I
io.Copy(net.TCPConn, buffer.Buffer)
since bytes.Buffer implements io.WriterTo interface, and
TCPConn.ReadFrom() is ignored unless it can sendfile().
Maybe it would be a good idea to implement a ReadFrom()
with a reasonable buffer size. I'm not sure relying on
WriteTo() being smart enough to have an adequate buffer size
is good for a network connection.

I see (*bytes.Buffer)WriteTo(...) is just
w.Write(b.buf[b.off:])
which confirms the origin of the giant write.

Regards,
--
R�my

test2.txt

Russ Cox

unread,
Aug 24, 2011, 3:51:24 PM8/24/11
to remyoud...@gmail.com, golang-nuts
We should probably make the write timeout reset
on each byte sent. Otherwise the write timeout
is sensitive to the size of the data written, which is
too strange. I think there might even be a TODO
about this already.

Russ

John Arbash Meinel

unread,
Aug 25, 2011, 5:45:48 AM8/25/11
to r...@golang.org, remyoud...@gmail.com, golang-nuts
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

There is an argument that you should still timeout. For example, in a
web server, you could have someone DOS you by making lots of
connections for very big contents, and then consume the result at the
slowest rate possible (1byte per second, etc.)

I think a better answer is: "don't send 100MB in a single write call".
At least IMO.

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk5WGcwACgkQJdeBCYSNAAMSIwCgllTlM5sftQM0SqNy757XlZlz
m2IAnjGhhTE40/WECk+8ykKmivLzDWU2
=m8KG
-----END PGP SIGNATURE-----

Reply all
Reply to author
Forward
0 new messages