Discussion:
blocking a non-blocking socket
Bill Janssen
2007-12-02 20:23:01 UTC
Permalink
An interesting question has come up in the development of the SSL module.

The function ssl.wrap_socket() takes a flag, do_handshake_on_connect,
which tells it whether to do the SSL handshake before returning an
SSLSocket object to the caller. If the socket being wrapped is
non-blocking, the code in wrap_socket() must invoke do_handshake()
multiple times, and effectively block until the handshake is done.

Right now, I'm doing it with this loop:

if do_handshake_on_connect:
# have to loop to support non-blocking sockets
while True:
try:
self.do_handshake()
break
except SSLError, err:
if err.args[0] == SSL_ERROR_WANT_READ:
select.select([self], [], [])
elif err.args[0] == SSL_ERROR_WANT_WRITE:
select.select([], [self], [])
else:
raise

But this seems fragile to me. "select" and/or "poll" is awfully
system-dependent. What I'd like to do is just use the socket API,
something like:

blocking = self.getblocking()
try:
self.setblocking(1)
self.do_handshake()
finally:
self.setblocking(blocking)

But there's no "getblocking" method on sockets. Instead, there's
"gettimeout". So I'd write something like

timeout = self.gettimeout()
try:
self.setblocking(1)
self.do_handshake()
finally:
if (timeout == 0.0):
self.setblocking(0)

But my mother taught me never to test for equality against
floating-point zero. But in this case it might just fly...

Or, should I just set the timeout:

timeout = self.gettimeout()
try:
self.settimeout(None)
self.do_handshake()
finally:
self.settimeout(timeout)

This is the solution I'm leaning towards...

Any recommendations?

Bill
Oleg Broytmann
2007-12-02 20:31:44 UTC
Permalink
On Sun, Dec 02, 2007 at 12:23:01PM -0800, Bill Janssen wrote:
[skip]
Post by Bill Janssen
timeout = self.gettimeout()
self.settimeout(None)
self.do_handshake()
self.settimeout(timeout)
Yes, this is the correct solution for all cases: if the timeout is None
(socket is blocking) or 0 (non-blocking) or not-0 (blocking with timeout)
- just set it back.

Oleg.
--
Oleg Broytmann http://phd.pp.ru/ ***@phd.pp.ru
Programmers don't die, they just GOSUB without RETURN.
Greg Ewing
2007-12-03 00:10:44 UTC
Permalink
Post by Bill Janssen
What I'd like to do is just use the socket API,
blocking = self.getblocking()
self.setblocking(1)
self.do_handshake()
self.setblocking(blocking)
I'm not sure this is the right approach. If the calling code
has made the socket non-blocking, then it doesn't want any
operations on it to block. Rather than temporarily
making it blocking by whatever means, some indication needs
to be returned that the operation would block, and a way
provided for the calling code to re-try later.

If that can't reasonably be done, then passing a non-blocking
socket here should be an error.
Post by Bill Janssen
But my mother taught me never to test for equality against
floating-point zero.
That doesn't apply here. If a float is explicitly set to 0.0
you can reasonably expect it to test equal to 0.0. The caveat
only applies to results of a calculation, which may incorporate
roundoff errors.

--
Greg
Bill Janssen
2007-12-03 02:04:34 UTC
Permalink
Post by Greg Ewing
Rather than temporarily
making it blocking by whatever means, some indication needs
to be returned that the operation would block, and a way
provided for the calling code to re-try later.
If that can't reasonably be done, then passing a non-blocking
socket here should be an error.
I tend to agree with you. At this point, we're executing bad code,
because passing in a non-blocking socket and asking the routine to do
the handshake is self-contradictory. Checking for this condition and
raising an exception would probably be best. Other opinions.
Post by Greg Ewing
Post by Bill Janssen
But my mother taught me never to test for equality against
floating-point zero.
That doesn't apply here. If a float is explicitly set to 0.0
you can reasonably expect it to test equal to 0.0. The caveat
only applies to results of a calculation, which may incorporate
roundoff errors.
Yep. Sorry, meant to imply that with the next sentence.

Bill
Bill Janssen
2007-12-03 17:40:45 UTC
Permalink
Thanks, Audun. If you look at the code, you'll see that both a
connect method and a do_handshake method already exist, and work
pretty much as you describe. The issue is what to do when the user
doesn't use them -- specifies do_handshake_on_connect=True.
Another way of doing it could be to expose a connect() method on the ssl
objects. It changes the socket.ssl api, but I'd say it is in the same
spirit as the do_handshake_on_connect parameter since no existing code
will break. The caller then calls connect() until it does not return
Bill
Audun Ostrem Nordal
2007-12-03 13:01:04 UTC
Permalink
Post by Bill Janssen
An interesting question has come up in the development of the
SSL module.
The function ssl.wrap_socket() takes a flag,
do_handshake_on_connect, which tells it whether to do the SSL
handshake before returning an SSLSocket object to the caller.
If the socket being wrapped is non-blocking, the code in
wrap_socket() must invoke do_handshake() multiple times, and
effectively block until the handshake is done.
# have to loop to support non-blocking sockets
self.do_handshake()
break
select.select([self], [], [])
select.select([], [self], [])
raise
Hello Bill,

Another way of doing it could be to expose a connect() method on the ssl
objects. It changes the socket.ssl api, but I'd say it is in the same
spirit as the do_handshake_on_connect parameter since no existing code
will break. The caller then calls connect() until it does not return
SSL_ERROR_WANT_[WRITE|READ]. This only applies when the underlying
socket is non-blocking and the do_handshake_on_connect parameter is
false.

Arguably, this is similar to how normal sockets are treated in asyncore,
only that he caller must be prepared to respond to (multiple?) readable
and writable events for the connection to be established.


Cheers

Audun

Loading...