Tim Peters wrote:
[Sune B. Woeller]
...
I can see (with the excellent (and free) 'Process
Explorer' from sysinternals) that the python
processes always opens port 19999, and connects by
that port to themselves on another port (for
instance 2550).
[Dieter Maurer]
You can find the relevant code in
"ZServer.medusa.thread.select_trigger.trigger.__init__"
In principle, the code should try all sockets between
"19999" down to "19950" and fail only when none of them
could be bound to...
Thanks for the pointer. I have been debugging
select_trigger.py, and has some more info:
The problem is that the call a.accept() sometimes hangs.
Apparently a.bind(self.address) allows us to bind to
a port that another zope instance already is bound to.
The code creates the server socket a, and the client socket w,
and gets the client socket r by connecting w to a. Then it closes a.
a goes out of scope when __init__ terminates, and is probably garbage collected
at some point.
I tried moving the code to the following standalone script, and I can reproduce
the error with that. In the original code w is kept as an instance variable, and
r is passed to asyncore.dispatcher.__init__ and probably kept there. I simulate
that by returning them, then the caller of socktest can keep them around.
I try to call socktest from different processes A and B (two pythons):
(w,r = socktest())
The call in A gets port 19999. The second call, in B, either blocks, or takes
over port 19999 (I see the second process taking over the port in a port scanner.)
a.bind in B does not raise socket.error: (10048, 'Address already in use') as
expected, when the server socket in A is closed, even though the port is used by
the client socket r in A.
If I remove a.close(), and keep a around (by passing it to the caller), a.bind
works as expected - it raises socket.error: (10048, 'Address already in use').
But in the litterature on sockets, I read it should be okay to close the server
socket and keep using the client sockets.
So, is this a possible bug in bind() ?
I have tested the new code from Tim Peters, it apparently works, ports are given
out by windows.
But could the same problem with bind occur here, since a is closed (and garbage
collected) ? (far less chance for that since we do not specify port numbers, I
know).
I tried getting a pair of sockets with Tim's code, and then trying to bind a
third socket to the same port as a/r. And I got the same problem as above.
Sune
+++++++++++++++++++++
import socket, errno
class BindError(Exception):
pass
def socktest():
"""blabla
"""
address = ('127.9.9.9', 19999)
a = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
w = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
# set TCP_NODELAY to true to avoid buffering
w.setsockopt(socket.IPPROTO_TCP, 1, 1)
# tricky: get a pair of connected sockets
host='127.0.0.1'
port=19999
while 1:
print port
try:
a.bind((host, port))
break
except:
if port <= 19950:
raise BindError, 'Cannot bind trigger!'
port=port - 1
a.listen (1)
w.setblocking (0)
try:
w.connect ((host, port))
except:
pass
r, addr = a.accept()
a.close()
w.setblocking (1)
#return (a, w, r)
return (w, r)
#return w
+++++++++++++++++++++
Yup. ZODB has what looks like a copy/paste of this code, in
ZEO/zrpc/trigger.py. I didn't realize where it came from originally
until you pointed out the Medusa code here.
Anyway, it so happens I rewrote ZEO's copy a few weeks ago, in ZODB
3.4. The Windows part is much simpler there now. I don't know why
the original might fail in the way Sune reported, but perhaps the
rewritten version would not.
Before:
# tricky: get a pair of connected sockets
host='127.0.0.1'
port=19999
while 1:
try:
self.address=(host, port)
a.bind(self.address)
break
except:
if port <= 19950:
raise BindError, 'Cannot bind trigger!'
port=port - 1
a.listen (1)
w.setblocking (0)
try:
w.connect (self.address)
except:
pass
r, addr = a.accept()
a.close()
w.setblocking (1)
self.trigger = w
After:
# Specifying port 0 tells Windows to pick a port for us.
a.bind(("127.0.0.1", 0))
connect_address = a.getsockname() # assigned (host, port) pair
a.listen(1)
w.connect(connect_address)
r, addr = a.accept() # r becomes asyncore's (self.)socket
a.close()
self.trigger = w
_______________________________________________
Zope maillist - Zope@zope.org
http://mail.zope.org/mailman/listinfo/zope
** No cross posts or HTML encoding! **
(Related lists -
http://mail.zope.org/mailman/listinfo/zope-announce
http://mail.zope.org/mailman/listinfo/zope-dev )
_______________________________________________
Zope maillist - Zope@zope.org
http://mail.zope.org/mailman/listinfo/zope
** No cross posts or HTML encoding! **
(Related lists -
http://mail.zope.org/mailman/listinfo/zope-announce
http://mail.zope.org/mailman/listinfo/zope-dev )