Message6117

Author amak
Recipients amak, gdoutch, zyasoft
Date 2010-10-01.19:36:58
SpamBayes Score 1.1657342e-15
Marked as misclassified No
Message-id <1285961819.78.0.709980500715.issue1660@psf.upfronthosting.co.za>
In-reply-to
Content
The attached code should not run. Instead, it should crash. On Windows, it does so with a socket.error, "Address already in use".

The reason for this is because of the deferred way that socket objects are created in jython. Java has different objects for Client (java.net.Socket+java.nio.channels.SocketChannel) and Server (java.net.ServerSocket+java.nio.channels.ServerSocketChannel) objects. Jython cannot know whether to create a client or serever socket until some client or server specific behaviour is observed. For clients, this is a connect() call, and for servers, this is a listen() call. When a connect() is made, jython then knows to create a java.net.Socket+java.nio.channels.SocketChannel to implement the socket. Similarly, when a listen() call is made, jython then knows to create a java.net.ServerSocket+java.nio.channels.ServerSocketChannel to implement the socket. Any attempt to get information about a socket before either a connect() or listen() call is made, i.e. before the implementation socket is created, will fail, although attempts are made to return already set configuration values, if possible.

But the default TCPServer code from SocketServer.py is not aware of this subtlety of jython sockets. The following methods illustrate the issue (comments snipped to make the code smaller).

class TCPServer(BaseServer):

    def __init__(self, server_address, RequestHandlerClass):
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        self.server_bind()
        self.server_activate()

    def server_bind(self):
        if self.allow_reuse_address:
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)
        self.server_address = self.socket.getsockname()

    def server_activate(self):
        self.socket.listen(self.request_queue_size)

Note that the server_address is set before after the bind() call, but before the listen() call. Meaning self.socket.getsockname() will return 0 for the port number, which was the requested value. The actual ephemeral port number wll not be assigned until after the listen() call.

So, in your attached code, the (host, port) tuple ("localhost", 0) will be returned for server_address, which is not a valid socket address for the client to connect to. As I mentioned, on Windows this crashes with an "Address already in use" exception.

So, in order to get your code working, I had to assign an explicit port number, e.g. 8888. Then your code runs as expected.

Notes:

1. The above is really a bug in the jython implementation of SocketServer.TCPServer, which we took directly from cpython without modification. Our implementation needs to be aware of the deferred creation issues mentioned above. I'm surprised if it runs on any platform, i.e. no platform should permit an attempt to connect port 0.

2. Once the code is running, by specifying an explicit port number, it does seem to slowly consume memory. In 30 mins of running, it seems to have consumed about 15M of memory. I do not know why this is yet, and will be investigating.
History
Date User Action Args
2010-10-01 19:36:59amaksetmessageid: <1285961819.78.0.709980500715.issue1660@psf.upfronthosting.co.za>
2010-10-01 19:36:59amaksetrecipients: + amak, zyasoft, gdoutch
2010-10-01 19:36:59amaklinkissue1660 messages
2010-10-01 19:36:58amakcreate