0

I'm currently working on a websocket implementation that allows multiprocessing over the same listening socket. I'm able to achieve an amazing performance with 4 processes on a quad core machine.

When I go upper, like 8 processes, after 4 request, the epoll.poll don't fire any event anymore. Interestingly, I tried running the same program , with 2 listener on 2 different ports. With 4 processes per listener, it blocks after 2 requests per socket. With 2 processes per listener, il all go fine through it.

Any thought?

main.py (extract)

#create the WSServer
wsserver = WSServer(s.bind_ip, s.bind_port, s.max_connections)
# specify on how many process we'll run
wsserver.num_process = s.num_process
Process(target=wsserver.run,args=()).start()

wsserver.py (extract)

def serve_forever_epoll(wsserver):
    log(current_process())
    epoll = select.epoll()
    epoll.register(wsserver.socket.fileno(), select.EPOLLIN)
    try:
        client_map = {}
        while wsserver.run:
            events = epoll.poll(1)
            for fileno, event in events:
                if fileno == wsserver.socket.fileno():
                    channel, details = wsserver.socket.accept()
                    channel.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
                    aclient = wsclient.WSClient(channel, wsserver, process_server.client_manager)
                    client_map[channel.fileno()] = aclient
                    epoll.register(channel.fileno(), select.EPOLLIN )
                    log('Accepting client on %s' % current_process())
                    aclient.do_handshake()

                elif event & select.EPOLLIN:
                        aclient = client_map[fileno]
                        threading.Thread(target=aclient.interact).start()

    except Exception, e:
        log(e)
    finally:
        epoll.unregister(wsserver.socket.fileno())
        epoll.close()
        wsserver.socket.close()

class WSServer():

    def __init__(self, address, port, connections):
        self.address = address
        self.port = port
        self.connections = connections
        self.onopen = onopen
        self.onclose = onclose
        log('server init')
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        #self.socket.setblocking(0)
        self.socket.bind((self.address, int(self.port)))
        self.socket.listen(self.connections)

    def run(self, *args):
        multiprocessing.log_to_stderr(logging.DEBUG)
        log("Run server")
        try:

            log("Starting Server")
            self.run = True
            serve_forever = serve_forever_epoll
            for i in range(self.num_process-1):
                log('Starting Process')
                Process(target=serve_forever,args=(self,)).start()
            serve_forever(self)

        except Exception as e:
            log("Exception-- %s " % e)
            pass
flovilmart
  • 1,759
  • 1
  • 11
  • 18
  • Just curious why don't you use any of existing Python websocket implementations? Python is known for its poor support for parallelism (I mean its performance). – Ihor Kaharlichenko Aug 17 '12 at 18:57
  • because the existing implementations don't scale properly. The goal here is to provide an extensible implementation, that can spawn on multiple servers, multiple processes... The case study is in python, a C rewrite in potentially on the roadmap if we really can't scale properly... – flovilmart Aug 17 '12 at 18:57
  • May I know which ones have you tested and whether have you posted the comparison results somewhere (maybe a blog post)? That info might be handy. – Ihor Kaharlichenko Aug 17 '12 at 19:00
  • Private information for the moment... sorry about that... The main problem on pyws implementation are that they mostly rely on the 'real' web server behind, that doesn't work for us... Tornado is not bad, but not ideal as it's limited term of multiprocessing/threading – flovilmart Aug 17 '12 at 19:04
  • 1
    Maybe a little off topic but, have you tried both python2 and python3? Do they behave similarly? – tMC Aug 17 '12 at 19:10
  • So multithreading/multiprocessing is a goal on its own, not the performance? Have you considered [Autobahn.ws](http://autobahn.ws/python)? It's asynchronous just like Tornado ('cos based on Twisted). – Ihor Kaharlichenko Aug 17 '12 at 19:10
  • @tMC I definitely forgot to test that :) – flovilmart Aug 18 '12 at 12:46
  • @IhorKaharlichenko, the goal here is not to get looking for a WS Implementation, but to provide one, that behaves better than the existing ones. The goal of our implementation is a zero-coding server (file configuration), multi-instance capable, and extremely long client connections (3 days +)... – flovilmart Aug 18 '12 at 12:47
  • tested with python3, same results... I guess this can be a bug when pre accept fork with python... – flovilmart Aug 20 '12 at 21:33

1 Answers1

0

OK so finally this weird case was caused by another module I was using. I am using Pyro4 as a manager for keeping track of which process holds what client. This simplifies greately the IPC and also permits me for some client filtering based on some user_data.

The problem was the Pyro4 daemon was running on the MainProcess but not on the Main Thread!... As long as I had less that 4 processes, all was OK (don't ask me why). Moving Pyro in the main-process + thread event loop, it was working perfectly! So now, i'm able to achieve 8, 16 or 32 processes for the same listening port, as well as spawning new configuration to replicate it or expose a new endpoint for the websocket server!

Thanks for your contributions, and sorry for your time...

flovilmart
  • 1,759
  • 1
  • 11
  • 18