Search code examples
pythonmultiprocessingimmutabilityserversocket

Changing mutables inside Socketserver.handle() - Python 3.3


I have a problem to change the data variable in the class NetworkManagerData. Everytime a request with 'SIT' comes to the server the variable 'master_ip' and 'time_updated' are updated. I have chosen a dictionary for my values as a container because it is mutable. But everytime i get a new request it has it old values in it.

Like:

First Request: 
>>False
>>True

Second Request:
>>False
>>True

Third Request without 'SIT':
>>False
>>False

Do I have some missunderstanding with these mutables. Or are there some special issues with using dictionarys in multiprocessing?

Code to start the server:

HOST, PORT = "100.0.0.1", 11880
network_manager = NetworkManagerServer((HOST, PORT), NetworkManagerHandler)

network_manager_process = 
              multiprocessing.Process(target=network_manager.serve_forever)

network_manager_process.daemon = True
network_manager_process.start()

while True:
    if '!quit' in input():
        network_manager_process.terminate()
        sys.exit()

Server:

from multiprocessing import Lock
import os
import socketserver

class NetworkManagerData():
    def __init__(self):
        self.lock = Lock()
        self.data = {'master_ip': '0.0.0.0', 'time_updated': False}


class NetworkManagerServer(socketserver.ForkingMixIn, socketserver.TCPServer):
    def __init__(self, nmw_server, RequestHandlerClass):
        socketserver.TCPServer.__init__(self, nmw_server, RequestHandlerClass)
        self.nmd = NetworkManagerData()

    def finish_request(self, request, client_address):
        self.RequestHandlerClass(request, client_address, self, self.nmd)


class NetworkManagerHandler(socketserver.StreamRequestHandler):
    def __init__(self, request, client_address, server, nmd):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        self.nmd = nmd
        try:
            self.handle(self.nmd)
        finally:
            self.finish()

    def handle(self, nmd):

        print(nmd.data.get('time_updated')) # <<<- False ->>>

        while True:
            self.data = self.rfile.readline()

            if self.data:
                ds = self.data.strip().decode('ASCII')
                header = ds[0:3]
                body = ds[4:]

                if 'SIT' in header:

                    # ...    

                    nmd.lock.acquire()
                    nmd.data['master_ip'] = self.client_address[0] # <-
                    nmd.data['time_updated'] = True # <-
                    nmd.lock.release()

                    # ...

                print(nmd.data.get('time_updated')) # <<<- True ->>>

            else:
                print("Connection closed: " + self.client_address[0] + ":" +
                    str(self.client_address[1]))
                return

Thanks!


Solution

  • Ok, the use of multiprocessing.Value and multiprocessing.Array have solved my problem. :)

    If you give some variables that are not part of the multiprocessing library to a process it will only copy the variables for its own process, there is no connection between the original and the copy. The variable is still mutable, but only in its own copy.

    To work on the original variable in the memory you have to use multiprocessing.Array or multiprocessing.Value. There are other things like variable managers or queues to get this done. What you want to use depends on your case.

    So I changed the datamanager class:

    class NetworkManagerData():
        def __init__(self):
            self.lock = multiprocessing.Lock()
            self.master_ip = multiprocessing.Array('B', (255,255,255,255))
            self.time_updated = multiprocessing.Value('B', False)
    

    To set the IP I am using this now:

            nmd.lock.acquire()
            ip_array = []
            for b in self.client_address[0].split('.'):
            ip_array.append(int(b))
            nmd.master_ip[:] = ip_array
            nmd.lock.release()
    

    To read the IP I am using this:

            self.wfile.write(("GIP|%s.%s.%s.%s" % 
                             (nmd.master_ip[0], nmd.master_ip[1], nmd.master_ip[2],
                                         nmd.master_ip[3]) + '\n').encode('ASCII'))