Thursday, April 21, 2011

XMLRPC Register instance is stateful. Do not want that

I have an XMLserver that uses the method SimpleXMLRPCServer.register_instance to register a class and it's methods. Something like:
    inst = ServiceClass(init_params)

The serviceclass looks like:
class ServiceClass(object):
    """ServiceClass calls the xxxx executable and returns stdout & stderr"""
    def __init__(self, params):
        super(ServiceClass, self).__init__()
        self.params = params
        print "Init with %s" % self.params

    def _shell_exec(self, cmd=[]):
    def dbupdate(self, sometext=""):
        """Given an object, try call the xxxx binary and
        return the output of the command
        Keyword arguments:                                                                                                                 
            sometext -- text of the object
        if not len('sometext'):
            raise MissingObjectError("No text passed for command")

        # Create a tempfile
        (tmpfd, fname) = tempfile.mkstemp()
        os.write(tmpfd,  sometext)
        # Execute
        cmd_exe = self.params['cmd']                                                                                                       
        ret_stdout, ret_stderr = self._shell_exec(cmd_exe)

        # Cleanup file

        print "cmd: %s\nself.cmd: %s\nfname: %s\n" %(cmd_exe, self.params['cmd'], fname)
        return (ret_stdout, ret_stderr)
Turns out that python keeps state since... the instance is created only once at server startup. All subsequent calls share this instance. And that affects the class variables. Notice that tempfiles from a prior run are visible to a later run which might be from a different client. :(
:!python -d 1 -p 8000                                                                                        
Listening on :8000...
Service URI is http://:8000/
Use Control-C to exit
CMD: ['/path/to/executable', '-c', '/path/to/config.conf', '-f', '/tmp/tmpkCXRS-']
cmd: ['/path/to/executable', '-c', '/path/to/config.conf', '-f', '/tmp/tmpkCXRS-']
self.cmd: ['/path/to/executable', '-c', '/path/to/config.conf', '-f', '/tmp/tmpkCXRS-']
fname: /tmp/tmpkCXRS- - - [21/Apr/2011 14:53:08] "POST / HTTP/1.0" 200 -
CMD: ['/path/to/executable', '-c', '/path/to/config.conf', '-f', '/tmp/tmpkCXRS-', '/tmp/tmpBGDHHU']
cmd: ['/path/to/executable', '-c', '/path/to/config.conf', '-f', '/tmp/tmpkCXRS-', '/tmp/tmpBGDHHU']
self.cmd: ['/path/to/executable', '-c', '/path/to/config.conf', '-f', '/tmp/tmpkCXRS-', '/tmp/tmpBGDHHU']
I tried different solutions from del cmd prior to return to initializing all variables to None|"" at the start of the method. Didn't work.. till I remembered shallow copying.   With a deep copy,
#        cmd_exe.extend([fname])
#Replace with
        cmd_exe = copy.deepcopy(self.params['cmd'])
things behave as expected:
Listening on :8000...
Service URI is http://:8000/
Use Control-C to exit
Init with {'debug': 1, 'cmd': ['/path/to/executable', '-c', '/path/to/config.conf', '-f']}
CMD: ['/path/to/executable', '-c', '/path/to/config.conf', '-f', '/tmp/tmpLTR0-S']
cmd: ['/path/to/executable', '-c', '/path/to/config.conf', '-f', '/tmp/tmpLTR0-S']
self.cmd: ['/path/to/executable', '-c', '/path/to/config.conf', '-f']
fname: /tmp/tmpLTR0-S - - [21/Apr/2011 14:58:19] "POST / HTTP/1.0" 200 -
CMD: ['/path/to/executable', '-c', '/path/to/config.conf', '-f', '/tmp/tmpxdCOtw']
cmd: ['/path/to/executable', '-c', '/path/to/config.conf', '-f', '/tmp/tmpxdCOtw']
self.cmd: ['/path/to/executable', '-c', '/path/to/config.conf', '-f']
fname: /tmp/tmpxdCOtw - - [21/Apr/2011 14:58:26] "POST / HTTP/1.0" 200 -

No comments:

Post a Comment