COM server threads/unloading

Uncle Enzo uncleenzo at hushmail.com
Thu Jun 27 01:29:34 CEST 2002


Hello, 

I have noticed that my multithreaded COM servers are not unloading
when their clients have unloaded.

To reproduce this in a simple case, I am using the py2exe COM server
recipe. Running the server example with the client code below results
in the testserver.exe loading after the dispatch call, and unloading
after the server reference is set to None.

Then I added the googlelogger import statement and the two lines
initializing and calling the log method. The logger that has two
threads. Using the same client code results in the testserver.exe
loading after the dispatch call, but it does not unload after the
server reference is set to None, nor does it unload after the client
terminates.

What is the proper way to get a multithreaded python COM server to
unload when there are no external references to it?

Thanks in advance,

Enzo

ps, comments about my logger design are welcome, but I'm mostly
interested in the threading issues :)

-----------------------------

Python 2.2.1 (#34, Apr  9 2002, 19:34:33) [MSC 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import win32com.client
>>> server = win32com.client.Dispatch("Python.TestServer")
>>> print server.Hello('enzo')
Hello enzo
>>> server = None
>>> ^Z

----------------------------
file: testcomserver.py

import googlelogger
import sys
import pythoncom

if hasattr(sys, 'importers'):
    # we are running as py2exe-packed executable
    pythoncom.frozen = 1

class HelloWorld:
    _reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER
    if hasattr(sys, 'importers'):
        # In the py2exe-packed version, specify the module.class
        # to use. In the python script version, python is able
        # to figure it out itself.
        _reg_class_spec_ = "__main__.HelloWorld"
    _reg_clsid_ = "{592C15CE-894D-11D6-9A35-0050DA05A582}"
    _reg_desc_ = "Python Test COM Server"
    _reg_progid_ = "Python.TestServer"
    _public_methods_ = ['Hello']
    _public_attrs_ = ['softspace', 'noCalls']
    _readonly_attrs_ = ['noCalls']
    def __init__(self):
        self.softspace = 1
        self.noCalls = 0
        self.mylogger = googlelogger.getLogger()
    # __init__()

    def Hello(self, who):
        self.noCalls = self.noCalls + 1
        # insert "softspace" number of spaces
        self.mylogger.log('caller', str(who))
        return "Hello" + " " * self.softspace + str(who)

if __name__ == '__main__':
    if hasattr(sys, 'importers'):
        # running as packed executable.
        if '--register' in sys.argv[1:] or '--unregister' in
sys.argv[1:]:
            # --register and --unregister work as usual
            import win32com.server.register
            win32com.server.register.UseCommandLine(HelloWorld)
        else:
            # start the server.
            from win32com.server import localserver
            localserver.main()
    else:
        import win32com.server.register
        win32com.server.register.UseCommandLine(HelloWorld)

----------------------
file: setup.py

from distutils.core import setup
import py2exe

setup(name='testCOMserver',
      scripts=['testCOMserver.py'])

------------------------
file: googlelogger.py

import threading
import Queue
import sys
import pavlnchcfg
import time
import win32com.client

# This thread waits for ("name", "value") items to appear in the
queue.
# It then checks for a log named "name", creates it if it doesn't
exist,
# and appends the message to that log, with a timestamp.
class Writer(threading.Thread):
    def setLogger(self, logger):
        #print "setting self._logger to ", logger
        self._logger = logger
        
    def run(self):
        print "starting Writer.run thread"        
        while 1 :
            # blocks until there are items in the queue
            event = self._logger._logeventqueue.get(1) 
            #get returns a tuple, (name, message)            
            if event[0] == 'QUIT': #magic quit event
                print "logger.Writer.run: got magic event: returning"
                return
            
            logname = event[0] 
            message = event[1]
            try:
                filename = self._logger._dir + '\\' + logname + ".txt"
                logfile = open(filename, 'a')    
                logfile.write(time.asctime() + " " + message + '\n')
                logfile.flush()
                logfile.close()
                print "logger.Writer.run: logged ", message, " to ",
logname
            except:
                print "logger.Writer.run: with to log: ", logname, "
failed with message: ", message

class Logger:
    # class variables
    _logeventqueue = Queue.Queue() #one thread safe multiple user
queue
    _dir = "" 
    
    def __init__(self):          
        try:            
            Logger._dir="C:\\" # for our example
            print "logger.Logger.init: got log directory",
Logger._dir, "from config"
        except:
            Logger._dir = "C:\\"
            print "logger.Logger.init: No log directory specified in
config, _dir set to  C:\\"
            
        writer = Writer()
        writer.setName("writerthread")
        writer.setLogger(self)
        writer.start()
        
    def log(self, logname, message):
        event = logname, message  #making the event tuple
        # event tuple is tuple[0] = name, tuple[1] = event
        Logger._logeventqueue.put(event)      
            

# factory pattern as module instance
_loggerInstance = None
       
def getLogger():
    import logger
    if logger._loggerInstance == None:
        logger._loggerInstance = Logger()
        return logger._loggerInstance
    else:
        return logger._loggerInstance

if __name__ == '__main__':  #test main
    import logger
    print "starting main"
    l = getLogger()
    print l
    l.log("DEBUG", "l, debug message 1")
    l.log("USAGE", "l, usagestat 1")
    l.log("DEBUG", "l, debug message 2")
    lg = getLogger()
    print lg
    lg.log("DEBUG", "lg, debug message 1")
    lg.log("USAGE", "lg, usagestat 1")
    lg.log("DEBUG", "lg, debug message 2")    
    l.log("QUIT", "QUIT")
    print "done"



More information about the Python-list mailing list