[erlang-questions] gen_tcp/inet/flow control/misc
Matthias Lang
matthias@REDACTED
Fri Nov 14 22:13:34 CET 2008
On Friday, November 14, Florian Zumbiehl wrote:
> 1. I'm not quite sure how write-side flow control on tcp sockets works.
...
> At the read side,
Your description of the read side agrees with what I know. I have nothing
to add.
> At the write side, though, it's rather unclear to me how flow control
> works. Given that there is a send_timeout option and that gen_tcp:send/2
> can return {error,timeout}, it seems likely that gen_tcp:send/2 works
> at least somewhat synchronously.
With default options, everything's straightforward: gen_tcp:send/2
blocks once the TCP window is full. Easy to verify by just trying it.
If you use send_timeout, things get a bit weird and, I agree, the
documentation doesn't really tell you what to expect. IIRC, it used to
be weirder and undocumented, so things are improving.
Knowing what the underlying C interface to sockets looks like, I
expected gen_tcp:send/2 to tell me how much of the data in the call
actually got sent. But no, gen_tcp:send/2 just returns {error,
timeout}. Actually trying it with R12B, I saw that everything gets
buffered inside Erlang and sent when the remote end starts empting the
window again. That's a bit surprising, but actually nice and reasonable.
An example:
28> f(), {ok, S} = gen_tcp:connect(localhost, 2222, [{send_timeout, 1000}]).
{ok,#Port<0.113>}
29> Bin = list_to_binary(lists:duplicate(200000, 65)).
<<"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...>>
30> gen_tcp:send(S, Bin).
ok
31> gen_tcp:send(S, Bin).
ok
32> gen_tcp:send(S, Bin).
{error,timeout}
33> gen_tcp:send(S, "the end").
{error,timeout}
34> gen_tcp:send(S, "is nigh").
ok
After command 32, the window was full. Same for 33. I then turned on
the receiver again and ran 34. The receiver got all the data from
30--34. Erlang must be buffering it. I'm not curious enough to know
where exactly.
> On the other hand, there is the delay_send option, the
> documentation for which claims that "the driver will use any
> means available to queue up the message"--which sounds to me like
> completely asynchronous operation, so that no back-pressure would
> be felt by the inet:send/2-ing process, no matter how slow the
> data sink. However, it's neither stated that these options were
> somehow incompatible, nor can I find any explanation as to what
> interaction between these options to expect.
You can probably just ignore delay_send, I don't think it's relevant
to what I think you're trying to do.
> So, the question basically condenses down to: What buffers are
> there in between the fd and the gen_tcp API, once again, and
> how do they interact with the various options?
I think there's only really one. If you don't use send_delay, it's
conceptually not there. If you do, it gets as large as it needs to be
and empties when it can.
> And how would I implement flow control at the write side?
Avoid the problem: use a protocol with flow control.
The engineer's solution: use {send_timeout, N} and resign yourself to
not being able to know when a blocked socket becomes unblocked, at least
not without sending it more data.
The "pure Erlang" solution: don't use {send_timeout, N}. Instead, use
multiple (Erlang) processes to keep track of whether the socket is
blocked or not.
Matt
(I know, you asked more questions. Maybe someone else can pick up
where I left off, otherwise, maybe next week. Or read the source and
experiment.)
More information about the erlang-questions
mailing list