Question

So i'm toying around with stackless python, writing a very simple webserver to teach myself programming with microthreads/tasklets. But now to my problem, when I run something like ab -n 100000 -c 50 http://192.168.0.192/ (100k requests, 50 concurrency) in apache bench I get something like 6k req/s, the second time I run it I get 5.5k, third time 5k, fourth time, 4.5k, etc. all the way down to 100req/s or something.

The problem goes away when I restart the python script, though.

Now my question is why? Am i forgetting to delete tasklets? I've checked the stackless.getruncount() (and it always seems to return 1, for some reason) so it doesn't seem like there would be any dead tasklets hanging around? I've tried calling .kill() on all tasklets that are done, didn't help. I just can't figure this one out.

import socket
import select
import stackless
import time

class socket_wrapper(object):
    def __init__(self, sock, sockets):
        super(socket_wrapper, self).__init__()
        self.sock = sock
        self.fileno = sock.fileno
        self.sockets_list = sockets
        self.channel = stackless.channel()
        self.writable = False
        self.error = False

    def remove(self):
        self.sock.close()
        self.sockets_list.remove(self)

    def send(self, data):
        self.sock.send(data)

    def push(self, bytes):
        self.channel.send(self.sock.recv(bytes))

def stackless_accept(accept, handler, recv_size=1024, timeout=0):
    sockets = [accept]

    while True:
        read, write, error = select.select(sockets, sockets, sockets, timeout)

        for sock in read:
            if sock is accept:
                # Accept socket and create wrapper
                sock = socket_wrapper(sock.accept()[0], sockets)

                # Create tasklett for this connection
                tasklet = stackless.tasklet(handler)
                tasklet.setup(sock)

                # Store socket
                sockets.append(sock)

            else:
                # Send data to handler
                sock.push(recv_size)

        # Tag all writable sockets
        for sock in write:
            if sock is not accept:
                sock.writable = True

        # Tag all faulty sockets
        for sock in error:
            if sock is not accept:
                sock.error = True
            else:
                pass # should do something here if the main socket is faulty

        timeout = 0 if socket else 1
        stackless.schedule() 

def simple_handler(tsock):
    data = ""

    while data[-4:] != "\r\n\r\n":
        data += tsock.channel.receive()

    while not tsock.writable and not tsock.error:
        stackless.schedule()

    if not tsock.error:
        tsock.send("HTTP/1.1 200 OK\r\nContent-length: 8\r\n\r\nHi there")
        tsock.remove()

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("192.168.0.192", 8000))
sock.listen(5)

stackless.tasklet(stackless_accept)(sock, simple_handler)
stackless.run()
Was it helpful?

Solution

Two things.

First, please make Class Name start with an Upper Case Letter. It's more conventional and easier to read.

More importantly, in the stackless_accept function you accumulate a list of Sock objects, named sockets. This list appears to grow endlessly. Yes, you have a remove, but it isn't always invoked. If the socket gets an error, then it appears that it will be left in the collection forever.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top