Hi,

2015-01-29 5:19 GMT+01:00 Glyph <[email protected]>:
> Twisted always immediately reports the connectionMade to the
> application-level protocol, so a TLS protocol's connectionMade means the
> same thing as a TCP protocol's connectionMade.

IMO asyncio behaviour is more convinient: only call connection_made()
when the SSL handshake succeeded. It avoids the problem of buffering
sent data, as done by Twisted. Changing when connection_made() is
called for SSL connections in asyncio would break the backward
compatibility.

Note: With the new SSL implementation in asyncio, sending data too
early from the application protocol may send it as clear text, which
is not what is expected :-) I'm not sure that it can occurs, it
depends when connection_made() method the application protocol is
called exactly. To avoid this issue, it must be called after the SSL
handshake started, which is occurs when SSLProtocol.connection_made()
is called. (Buffering as Twisted would solve this issue.)

For subprocess transports, we have the problem of buffering because we
may get input data from stdout or stderr before connection_made() is
called. BaseSubprocessTransport has a list of "pending callbacks",
callbacks scheduled after the connection_made() method has been
called.

I used the example of the SSL handshake failure, because an SSL
handshake failing because of the client is simple to explain and
reproduce. But the issue occurs in other cases. Example when a simple
TCP connection fails during the creation of the transport:
---
import asyncio
import socket

def func():
    sock = socket.socket()
    # you should connect the socket, but it doesn't matter on this example
    coro = loop.create_connection(asyncio.Protocol, sock=sock)
    task = loop.create_task(coro)
    # the cancellation can be scheduled indirectly by a timeout using wait_for()
    loop.call_soon(task.cancel)
    yield from task

loop = asyncio.get_event_loop()
loop.run_until_complete(func())
loop.close()
---

Since the call to waiter.set_result() is scheduled by loop.call_soon()
instead of direct call, there is a small time window when the task can
be cancelled while the transport and protocol are created but are not
connected yet.

The creation of other transports are more complex than TCP
connections: the subprocess transport requires many loop iterations to
connect stdin, stdout and stderr pipes, and then to call
protocol.connection_made().

But as Guido noticed, the caller of create_connection(),
subprocess_exec(), etc. already get the exception and so must handle
it. So the requirement of calling a connection_failed() method is less
important.

An advantage of connection_failed() is that it gives access to the
transport before it is closed. It gives more information than a simple
Python exception. You may get the IP address or identifier of the
child process. You may also call methods of the transport.

Currently, if the creation of a child process fails, we kill the child
process (SIGKILL signal). I don't know if it would be possible to
change how the child process is killed using a custom
connection_failed() method. Maybe this use case is not very useful :-)
Trying to interact with a transport which failed to be connected is
maybe a bad idea.

Victor

Reply via email to