Probleme mit Memoization in einem SocketServer

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
skirnir
User
Beiträge: 33
Registriert: Sonntag 25. Januar 2015, 10:59

Hallo!

Ich habe vor, über einen SocketServer Daten zur Verfügung zu stellen, deren Berechnung ziemlich zeitaufwändig ist. Daher habe ich der Klasse (eine Unterklasse von 'dict'), die diese Daten liefert (im Beispiel unten 'MyClass') einen member 'cache' gegeben, der bereits berechnete Daten für zukünftige Anfragen abrufbar halten soll.

Das ganze sieht so aus:

Code: Alles auswählen

import SocketServer
import logging
import time

class MyRequestHandler(SocketServer.BaseRequestHandler):
    def __init__(self, request, client_address, server):
        SocketServer.BaseRequestHandler.__init__(self, request, client_address, server)
        return

    def handle(self):
        self.request.setblocking(0)
        total_data = []
        data = ''
        begin = time.time()
        timeout = 2
        while True:
            if total_data and time.time() - begin > timeout:
                break
            elif time.time() - begin > timeout * 2:
                break
            try:
                data = self.request.recv(1024)
                if data:
                    total_data.append(data)
                    begin - time.time()
                else:
                    time.sleep(0.1)
            except Exception as e:
                pass #logging.exception(e)
        complete_request = ''.join(total_data)
        result = self.server.handle_request(complete_request)
        self.request.send(result)
        return

class MyClass(dict):
    """
    Autovivifying dictionary with some additional functionality
    """
    def __getitem__(self, item):
        """
        The autovivification part
        """
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

    def __init__(self):
        self.logger = logging.getLogger('MyClass')
        self.cache = {}

    def fill_dict(self):
        self['mykey'] = 'myvalue'

    def get_result(self, request):
        self.logger.info('my keys: %s' % self.keys())
        self.logger.info('cache keys: %s' % self.cache.keys())
        if request not in self.cache:
            self.logger.info('no cached value')
            self.cache[request] = 'bar'
        self.logger.info('cache keys: %s' % self.cache.keys())
        self.logger.info('-' * 80)
        return self.cache[request]


class MyServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
    def __init__(self, server_address, handler_class=MyRequestHandler):
        self.logger = logging.getLogger('MyServer')
        self.myclass_instance = MyClass() 
        self.myclass_instance.fill_dict()
        SocketServer.TCPServer.__init__(self, server_address, handler_class)
        return

    def handle_request(self, request):
        return self.myclass_instance.get_result(request)

def main():
    import logging.config
    logging.config.fileConfig('/path/to/logconf.ini')
    server_address = ('', 8080)
    server = MyServer(server_address, MyRequestHandler)
    server.serve_forever()

if __name__ == '__main__':
    main()
Und ich mache meine Anfragen mit Hilfe dieses Clients:

Code: Alles auswählen

import socket

def query(message):
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(('localhost', 8080))
    len_sent = client.send(message)
    complete_response = []
    while True:
        chunk = client.recv(4096)
        if chunk == '':
            break
        else:
            complete_response.append(chunk)
    return ''.join(complete_response)
Im Log des Servers steht dann folgendes:

Code: Alles auswählen

2015-09-16 13:06:49,887: [INFO] caching:57 my keys: ['mykey']
2015-09-16 13:06:49,887: [INFO] caching:58 cache keys: []
2015-09-16 13:06:49,887: [INFO] caching:60 no cached value
2015-09-16 13:06:49,887: [INFO] caching:62 cache keys: ['foo']
2015-09-16 13:06:49,887: [INFO] caching:63 --------------------------------------------------------------------------------
2015-09-16 13:06:55,073: [INFO] caching:57 my keys: ['mykey']
2015-09-16 13:06:55,073: [INFO] caching:58 cache keys: []
2015-09-16 13:06:55,073: [INFO] caching:60 no cached value
2015-09-16 13:06:55,074: [INFO] caching:62 cache keys: ['foo']
2015-09-16 13:06:55,074: [INFO] caching:63 --------------------------------------------------------------------------------
Das sind zwei aufeinanderfolgende Anfragen der Form:

Code: Alles auswählen

In [13]: cclient.query('foo')
Out[13]: 'bar'

In [14]: cclient.query('foo')
Out[14]: 'bar'
Anscheinend habe ich eine falsche Vorstellung davon, wie self.myclass_instance.cache funktioniert. Insbesondere wundert mich, dass die Instanz meiner dict-Unterklasse sich verhält wie erwartet, ihr Attribut aber nicht.

Wo ist hier mein Denkfehler? Wie kann ich erreichen, dass der Inhalt von self.myclass_instance.cache über mehrere Anfragen hinweg zur Verfügung steht?
BlackJack

@skirnir: Der Denkfehler ist das `SocketServer.ForkingMixIn` wodurch für jede Anfrage eine Kopie des Prozesses erzeugt wird deren Speicher nichts mehr mit dem zu tun hat in dem der ursprüngliche SocketServer läuft. Das Ergebnis wird dann in eine Kopie von Deinen Datenstrukturen eingetragen, die zudem auch noch verschwindet wenn die Abfrage abgearbeitet wurde weil dann der neue Prozess einfach endet.
skirnir
User
Beiträge: 33
Registriert: Sonntag 25. Januar 2015, 10:59

@BlackJack: Vielen Dank!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@skirnir Also mach es mit SocketServer.ThreadingMixIn. Dann hast Du statt verschiedenen Prozessen verschiedene Threads. Und wenn die irgendwo etwas hineinschreiben sollen, wo sie sich ins Gehege kommen könnten, dann verwende:

Code: Alles auswählen

with lock:
oder verwende eine Queue, die von einem anderen Thread bearbeitet wird nach triggern eines Events
Antworten