[Python-Dev] Socket timeout patch
Guido van Rossum
guido@python.org
Mon, 03 Jun 2002 13:22:16 -0400
[Addressing only points that need attention]
> > - Also please don't put a space between the open parenthesis and the
> > first argument (you do this almost consistently in test_timeout.py).
>
> Couldn't really figure out what you were seeing here. I read that
> you saw something like func( a, b), which I don't see in my local
> copy.
test_timeout.py from the SF page has this. I'm glad you fixed this
already in your own copy.
> I do have something like this for list comprehension:
>
> [ x.find('\n') for x in self._rbuf ]
>
> Er, but I though there were supposed to be surrounding spaces at the
> edges...
I prefer to see that as
[x.find('\n') for x in self._rbuf]
> I screwed up the write. New the write is:
>
[...]
>
> which is pretty much the same as the old. The read should be ok
> though. I could really use someone with a win compiler to test this
> for me.
I'll review it more when you next upload it.
> > - It also looks like you've broken the semantics of size<0 in read().
>
> Maybe I'm misunderstanding the code, but I thought that a size < 0
> meant to read until there are no more? The statement:
>
> while size < 0 or buf_len < size:
>
> accomplishes the same thing as what's in the current socket.py
> implementation. If you look closely, the 'if >= 0' branch *always* returns,
> meaning that the < 0 is equiv to while 1. Due to shortcutting, the same
> thing happens in the above statement. Maybe a comment would make it clearer?
I was referring to this piece of code:
! if buf_len > size:
! self._rbuf.append (data[size:])
! data = data[:size]
Here data[size:] gives you the last byte of the data and data[:size]
chops off the last byte.
> > - Maybe changing the write buffer to a list makes sense too?
>
> I could do this. Then just do a join before the flush. Is the append
> /that/ much faster?
Depends on how small the chunks are you write. Roughly, repeated list
append is O(N log N), while repeated string append is O(N**2).
> > - Since this code appears independent from the timeoutsocket code,
> > maybe we can discuss this separately?
>
> The point of this code was to keep from losing data when an exception
> occurs (as timothy, if I remember correctly, pointed out). Hence the reason
> for keeping a lot more data around in instance variables instead of local
> variables. So the windows version might (in obscure cases) be affected
> by the timeout changes. That's what this patch was addressing.
OK, but given the issues the first version had, I recommand that the
code gets more review and that you write unit tests for all cases.
> > - Please don't introduce more static functions with a 'Py' name
> > prefix.
>
> Only did this in one place, with PyTimeout_Err. The reason was that the
> other Error functions used the Py prefix, so it was done for consistency. I
> can change that.. or change the naming scheme with the others if you like.
I like to do code cleanup that doesn't change semantics (like
renamings) as a separate patch and checkin. You can do this before or
after the timeout changes, but don't merge it into the timeout
changes. I still like the static names that you introduce not to
start with Py.
> > - I believe that you can't reliably maintain a "sock_blocking" flag;
> > there are setsockopt() or ioctl() calls that can make a socket
> > blocking or non-blocking. Also, there's no way to know whether a
> > socket passed to fromfd() is in blocking mode or not.
>
> Well, upon socket creation (in init_sockobject), the socket is
> set to blocking mode. I think that handles fromfd, right? Doesn't
> every initialization means have to call that function?
OK, it looks like you call internal_setblocking(s, 0) to set the
socket in nonblocking mode. (Hm, I don't see any calls to set the
socket in blocking mode!)
So do I understand that you are now always setting the socket in
non-blocking mode, even when there is no timeout specified, and that
you look at the sock_blocking flag to decide whether to do timeouts or
just pass the nonblocking behavior to the user?
This is a change in semantics, and could interfere with existing
applications that pass the socket's file descriptor off to other
code. I think I'd be happier if the behavior wasn't changed at all
until a timeout is set for a socket -- then existing code won't
break.
> The real problem would be someone using an ioctl or setsockopt (Can
> you even do blocking stuff through setsockopt?).
Yes, setblocking() makes a call to setsockopt(). :-)
> Ugh. The original timeoutsocket didn't really deal with anything
> like that. Erm, seems like an interface problem here - using ioctl
> kinda breaks the socket object interface. Perhaps we should be doing
> some sort of getsockopt to figure out the blocking mode and update
> our state accordingly? That would be an extra call for each thing to
> the interface though.
I only really care for sockets passed in to fromfd(). E.g. someone
can currently do:
s1 = socket(AF_INET, SOCK_STREAM)
s1.setblocking(0)
s2 = fromfd(s1.fileno())
# Now s2 is non-blocking too
I'd like this to continue to work as long as s1 doesn't set a timeout.
> One solution is to set/unset blocking mode right before doing each
> call to be sure of the state and based on the internally stored value
> of the blocking attribute... but... then that kind of renders ioctl
> useless.
Don't worry so much about ioctl, but do worry about fromfd.
> Another solution might be to set the blocking mode to on everytime
> someone sets a timeout. That would change the blocking/socket
> interaction already described a bit but not drastically. Also easy
> to implement. That sends the message: Don't use ioctls when using
> timeouts.
I like this.
> Hmm.. Will need to think about this more. Any insight would be
> helpful or some wisdom about how you usually handle this sort of
> thing.
See above. Since we don't know what people out there are doing, I
don't want to break existing code. We do know that existing code
doesn't use (this form of) timeout, so we can exploit that knowledge.
> > - What is s->errorhandler() for? AFAICT, this is always equal to
> > PySocket_Err!
>
> This was always in the module. Not sure why it was put there intially.
> I used it to be consistent.
Argh, you're right. MAL added this; I'll ask him why.
> If you made it this far, it's time for coffee.
When can I expect a new version?
--Guido van Rossum (home page: http://www.python.org/~guido/)