Multithreaded COM server problem...

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • John Lull

    Multithreaded COM server problem...

    I'm writing a multithreaded COM server to manage a pool of hardware resources.
    All objects are designed to be thread-safe, and I've set sys.coinit_flag s to
    COINIT_MULTITHR EADED before importing pythoncom.

    The problem is that all requests to the server seem to be serialized by COM. To
    demonstrate the problem, I'm including a simple test server that exposes one
    interface object, PyComThreads.Ap plication, with a single method sleep(delay).
    I'm also including 2 test programs -- test20.py uses the server to delay 20
    seconds, and test1.py delays only one second. The server prints status messages
    to the trace collector debugging tool when creating the Application object, and
    at the beginning and end of the specified delay.


    When I run the 20-second test program, then a couple seconds later run the
    1-second test program, I had expected to see something like this:

    Object 8087872 in thread 8160416 created
    Object 8087872 in thread 8160416 delays 20 seconds
    Object 8086008 in thread 8156272 created
    Object 8086008 in thread 8156272 delays 1 seconds
    Object 8086008 delay ends
    Object 8087872 delay ends


    Instead, I see:

    Object 8087872 in thread 8160416 created
    Object 8087872 in thread 8160416 delays 20 seconds
    Object 8087872 delay ends
    Object 8086008 in thread 8160416 created
    Object 8086008 in thread 8160416 delays 1 seconds
    Object 8086008 delay ends


    Apparently the requests from both client applications are being serialized by
    the COM interface.

    I need each request (or at least each interface object) to run in its own
    thread, or in a pool of threads, and haven't been able to figure out how to
    accomplish this. Any suggestions would be appreciated.


    Regards,
    John

    ----------
    PyComThreads.py :

    import sys
    import threading
    from time import sleep
    import win32traceutil

    sys.coinit_flag s = 0 # 0 == pythoncom.COINI T_MULTITHREADED # !!!!!
    import pythoncom

    class Application:
    """ Test version of a Multi-threaded local server """

    _reg_progid_ = 'PyComThreads.A pplication'
    _reg_verprogid_ = 'PyComThreads.A pplication.100'
    _reg_clsctx_ = pythoncom.CLSCT X_LOCAL_SERVER
    _reg_clsid_ = '{56BEC27D-EDC4-43A0-AEB7-77E4A1381C0F}'

    _public_methods _ = ['sleep']
    _public_attrs_ = []
    _readonly_attrs _ = []

    def __init__(self):
    print 'Object %s in thread %s created' % \
    (id(self), id(threading.cu rrentThread()))

    def sleep(self, delay):
    print 'Object %s in thread %s delays %s seconds' % \
    (id(self), id(threading.cu rrentThread()), delay)
    sleep(delay)
    print 'Object %s delay ends' % id(self)

    # COM server registration, etc.
    if __name__ == '__main__':
    if hasattr(sys, 'argv'):
    # If *no* command-line arguments, we were not invoked as a server.
    # Assume the user wants us to self-register.
    if len(sys.argv) == 1:
    sys.argv.append ('--register')

    import win32com.server .register
    win32com.server .register.UseCo mmandLine(Appli cation, debug=0)

    ----------
    test20.py:

    from win32com.client import Dispatch
    app=Dispatch('P yComThreads.App lication')
    app.sleep(20)

    ----------
    test1.py:

    from win32com.client import Dispatch
    app=Dispatch('P yComThreads.App lication')
    app.sleep(1)

  • Michel Claveau/Hamster

    #2
    Re: Multithreaded COM server problem...

    Hi !


    * Sorry for my bad english : french is very more easy, and nice ;-) *

    I have try another way. I use a procedure for manage thread :

    GlobalTacheID=[None]*16

    def tache(ordre, numtache=1, nomtache=None, fonction=None):
    global GlobalTacheID
    import threading
    if ordre=="Lance":
    GlobalTacheID[numtache] =
    threading.Threa d(target=foncti on,name=nomtach e)
    GlobalTacheID[numtache].start()
    print(nomtache+ ' num.'+str(numta che))
    elif ordre=="Liste":
    return(string.j oin(threading.e numerate(),'\n' ))
    elif ordre=="Etat":
    return(str(Glob alTacheID[numtache].isAlive()))

    And, in my public_method, i call this procedure, with the parameters
    "ordre=Lanc e" for run a thread, "ordre=Etat " for see his state (fonction=
    the name of the method 'threaded').

    And it's OK. I can run a thread from an Excel-COM-client. I can also run an
    thread on a method caontained in a string gived by Excel.

    Python is very smart-dynamic-agile language.

    Bonne soirée
    --
    Michel Claveau
    mél : http://cerbermail.com/?6J1TthIa8B
    site : http://mclaveau.com


    Comment

    • Mark Hammond

      #3
      Re: Multithreaded COM server problem...

      John Lull wrote:
      [color=blue]
      > I'm writing a multithreaded COM server to manage a pool of hardware resources.
      > All objects are designed to be thread-safe, and I've set sys.coinit_flag s to
      > COINIT_MULTITHR EADED before importing pythoncom.[/color]

      Note that this flag is effeectively ignored for the server. Objects are
      created in the same "apartment" as their creator. This, the code
      implementing your object need not specify this, but the code *creating*
      the object must. This is what determines the appartment.

      COM threading rules are complex, but "Python Programming on Win32" (1/2
      by me :) covers these rules in words I would have trouble finding again :)

      Mark.

      Comment

      • John Lull

        #4
        Re: Multithreaded COM server problem...

        Mark Hammond <mhammond@skipp inet.com.au> wrote (with possible deletions):
        [color=blue]
        > John Lull wrote:
        >[color=green]
        > > I'm writing a multithreaded COM server to manage a pool of hardware resources.
        > > All objects are designed to be thread-safe, and I've set sys.coinit_flag s to
        > > COINIT_MULTITHR EADED before importing pythoncom.[/color]
        >
        > Note that this flag is effeectively ignored for the server. Objects are
        > created in the same "apartment" as their creator. This, the code
        > implementing your object need not specify this, but the code *creating*
        > the object must. This is what determines the appartment.
        >
        > COM threading rules are complex, but "Python Programming on Win32" (1/2
        > by me :) covers these rules in words I would have trouble finding again :)[/color]

        It covers threading rules for in-process servers pretty thoroughly.

        Unfortunately, this one has to be a local server since it's providing shared
        access to a pool of hardware devices from multiple distributed clients. I've
        carefully reviewed chapters 5 & 12, and appendix D, and wasn't able to find
        anything addressing threading models in the local server in detail. If I've
        missed something, I'd be grateful for any additional hints.

        Thanks.


        Regards,
        John

        Comment

        • Mark Hammond

          #5
          Re: Multithreaded COM server problem...

          John Lull wrote:[color=blue]
          > Mark Hammond <mhammond@skipp inet.com.au> wrote (with possible deletions):
          >
          >[color=green]
          >>John Lull wrote:
          >>
          >>[color=darkred]
          >>>I'm writing a multithreaded COM server to manage a pool of hardware resources.
          >>>All objects are designed to be thread-safe, and I've set sys.coinit_flag s to
          >>>COINIT_MULTI THREADED before importing pythoncom.[/color]
          >>
          >>Note that this flag is effeectively ignored for the server. Objects are
          >>created in the same "apartment" as their creator. This, the code
          >>implementin g your object need not specify this, but the code *creating*
          >>the object must. This is what determines the appartment.
          >>
          >>COM threading rules are complex, but "Python Programming on Win32" (1/2
          >>by me :) covers these rules in words I would have trouble finding again :)[/color]
          >
          >
          > It covers threading rules for in-process servers pretty thoroughly.
          >
          > Unfortunately, this one has to be a local server since it's providing shared
          > access to a pool of hardware devices from multiple distributed clients. I've
          > carefully reviewed chapters 5 & 12, and appendix D, and wasn't able to find
          > anything addressing threading models in the local server in detail. If I've
          > missed something, I'd be grateful for any additional hints.[/color]

          The problem is that your client code is not running a message loop. If
          you change the loop of your client test code to something like:

          for i in range(delay)*10 :
          time.sleep(0.1)
          pythoncom.PumpW aitingMessages( )

          It works as you expect. A better choice would probably be
          win32event.MsgW aitForMultipleO bjects, but that depends on what your app
          really does.

          Mark.

          Comment

          • Mark Hammond

            #6
            Re: Multithreaded COM server problem...

            John Lull wrote:[color=blue]
            > Mark Hammond <mhammond@skipp inet.com.au> wrote (with possible
            > deletions):
            >
            >[color=green]
            >>John Lull wrote:[/color]
            >
            > ...
            >[color=green][color=darkred]
            >>>Unfortunatel y, this one has to be a local server since it's providing shared
            >>>access to a pool of hardware devices from multiple distributed clients. I've
            >>>carefully reviewed chapters 5 & 12, and appendix D, and wasn't able to find
            >>>anything addressing threading models in the local server in detail. If I've
            >>>missed something, I'd be grateful for any additional hints.[/color]
            >>
            >>The problem is that your client code is not running a message loop. If
            >>you change the loop of your client test code to something like:
            >>
            >>for i in range(delay)*10 :
            >> time.sleep(0.1)
            >> pythoncom.PumpW aitingMessages( )
            >>
            >>It works as you expect. A better choice would probably be
            >>win32event.Ms gWaitForMultipl eObjects, but that depends on what your app
            >>really does.
            >>
            >>Mark.[/color]
            >
            >
            > I presume you meant my server code.[/color]

            Nope - I meant the client. The server is already running such a loop
            thank to localserver.py, which is hosting the object.

            The client code's main (and only) thread is blocked in a system call,
            but it appears COM wants it to pump messages so the marshalling magic
            happens. I can only speculate why COM needs this to happen in this
            trivial case, but COM's rules do state this requirement.
            [color=blue]
            > This still leaves all calls to the
            > server running in a single thread, however. If I insert a call to
            > PumpWaitingMess ages() in a short operation, and it happens to start a
            > long operation, the result of that short operation will be delayed
            > until the long operation completes. This will make my server unusable.[/color]

            Make the change I suggested, and it works as you hope.
            [color=blue]
            > At first glance this seems to do what I want -- requests to the server
            > seem to run from a thread pool. However, I also get intermittent (but
            > frequest) startup errors, with a Microsoft Visual C++ Runtime Library
            > error dialog (during pythoncom.PumpM essages but before my server
            > module gets imported) reporting:
            > Runtime Error!
            > Program: c:\Apps\Python2 .2\pythonw.exe
            > abnormal program termination[/color]

            That sucks, and is almost certainly a thread-state error. If you have a
            debug build of Python, it will offer to break into the debugger at this
            point.

            Mark.

            Comment

            • John Lull

              #7
              Re: Multithreaded COM server problem...

              Mark Hammond <mhammond@skipp inet.com.au> wrote (with possible
              deletions):
              [color=blue]
              > John Lull wrote:[color=green]
              > > Mark Hammond <mhammond@skipp inet.com.au> wrote (with possible
              > > deletions):
              > >
              > >[color=darkred]
              > >>John Lull wrote:[/color]
              > >
              > > ...
              > >[color=darkred]
              > >>>Unfortunatel y, this one has to be a local server since it's providing shared
              > >>>access to a pool of hardware devices from multiple distributed clients. I've
              > >>>carefully reviewed chapters 5 & 12, and appendix D, and wasn't able to find
              > >>>anything addressing threading models in the local server in detail. If I've
              > >>>missed something, I'd be grateful for any additional hints.
              > >>
              > >>The problem is that your client code is not running a message loop. If
              > >>you change the loop of your client test code to something like:
              > >>
              > >>for i in range(delay)*10 :
              > >> time.sleep(0.1)
              > >> pythoncom.PumpW aitingMessages( )
              > >>
              > >>It works as you expect. A better choice would probably be
              > >>win32event.Ms gWaitForMultipl eObjects, but that depends on what your app
              > >>really does.
              > >>
              > >>Mark.[/color]
              > >
              > >
              > > I presume you meant my server code.[/color]
              >
              > Nope - I meant the client. The server is already running such a loop
              > thank to localserver.py, which is hosting the object.
              >
              > The client code's main (and only) thread is blocked in a system call,
              > but it appears COM wants it to pump messages so the marshalling magic
              > happens. I can only speculate why COM needs this to happen in this
              > trivial case, but COM's rules do state this requirement.[/color]

              Now I'm *really* confused.

              Perhaps I need to clarify a bit. The sleep() method in my sample
              server is a perhaps-too-simplified substitute for what the real server
              is doing. It provides a variety of high-level operations on a piece of
              hardware. Some of the operations take as long as several dozen
              seconds, others take a millisecond or so. I need the client to block
              waiting for completion of each operation, regardless of how long the
              operation takes. I cannot break one of the long operations up into a
              series of calls from the client -- it must be implemented as a single
              call. My example would, perhaps, have been clearer if I'd named the
              method someLongRunning Method() instead of sleep().

              I've tried doing roughly what you suggested inside my test client,
              calling PumpWaitingMess ages() both before and after each COM
              operation. That still leaves me with the same basic problem -- inside
              the server, all of my COM objects are created on the server's main
              thread, instead of on a separate thread for each client. That leaves
              all COM operations serialized through that single thread. My test
              client at the moment looks like this:

              import sys
              from pythoncom import PumpWaitingMess ages
              from win32com.client import Dispatch

              PumpWaitingMess ages()
              app=Dispatch('P yComThreads.App lication')
              PumpWaitingMess ages()
              app.someLongRun ningMethod(20)
              PumpWaitingMess ages()

              If this is not essentially what you meant, could you please let me
              know? The server is exactly like I had posted originally, except for
              renaming its sleep() method to someLongRunning Method().

              [color=blue]
              > That sucks, and is almost certainly a thread-state error.[/color]

              That was my thought.


              Thanks.

              Regards,
              John

              Comment

              • Mark Hammond

                #8
                Re: Multithreaded COM server problem...

                John Lull wrote:[color=blue]
                > Now I'm *really* confused.
                >
                > Perhaps I need to clarify a bit. The sleep() method in my sample
                > server is a perhaps-too-simplified substitute for what the real server
                > is doing. It provides a variety of high-level operations on a piece of
                > hardware. Some of the operations take as long as several dozen
                > seconds, others take a millisecond or so. I need the client to block
                > waiting for completion of each operation, regardless of how long the
                > operation takes. I cannot break one of the long operations up into a
                > series of calls from the client -- it must be implemented as a single
                > call. My example would, perhaps, have been clearer if I'd named the
                > method someLongRunning Method() instead of sleep().[/color]

                Is there any way you can do it asynchronously? The main thread spawns a
                second thread to do the work. The main thread then spins around a
                MsgWaitForMulti pleObjects, with an object set by the second thread. The
                main thread will then be able to run a message pump, but also detect
                when the second thread is done.

                Apart from that, I am afraid I am out of ideas. I believe however that
                you are hitting pure COM issues, and nothing related to Python. Looking
                for the answer beyond the Python world may be fruitful.

                Mark.

                Comment

                • John Lull

                  #9
                  Re: Multithreaded COM server problem...

                  Mark Hammond <mhammond@skipp inet.com.au> wrote (with possible
                  deletions):
                  [color=blue]
                  > Is there any way you can do it asynchronously? The main thread spawns a
                  > second thread to do the work. The main thread then spins around a
                  > MsgWaitForMulti pleObjects, with an object set by the second thread. The
                  > main thread will then be able to run a message pump, but also detect
                  > when the second thread is done.[/color]

                  Yes, except there doesn't seem to be any way to return a value from
                  the first call until after the second call completes.
                  [color=blue]
                  > Apart from that, I am afraid I am out of ideas. I believe however that
                  > you are hitting pure COM issues, and nothing related to Python. Looking
                  > for the answer beyond the Python world may be fruitful.[/color]

                  The three reasonable possibilities I see at the moment are:

                  1. Dig into exactly how apartment-threaded servers are supposed to be
                  written in C or C++ & see how much of that is transferrable to Python.
                  I'm not confident this would give me a solution, though.

                  2. Activate the server in 2 steps -- have the main thread create (on
                  client request) "interlock" object that, when created, fires up a new
                  thread for the client who created it. The new thread then calls
                  factory.Registe rClassFactories () for the real worker object, then
                  starts a message pump. The client then requests COM to create the
                  worker object. The worker object creation method then calls
                  factory.RevokeC lassFactories() to revoke that thread's registration.
                  The interlock and worker classes have to coordinate so that only one
                  worker thread is registered as class factory at any time, and only one
                  client is able to create the worker object at any time. The main
                  thread also has to register as class factory only for the interlock
                  object, not for the worker object. This requires changes to one py2exe
                  module and to all applications that use my server, and will complicate
                  shutdown, since I have to shut down the new thread only when all
                  object's it's pumping messages for are gone. There's also the
                  substantial possibility that the same bug I ran into when setting
                  sys.coinit_flag s=0 in localserver.py will rear its head here.

                  3. Dig into why setting sys.coinit_flag s=0 in localserver.py doesn't
                  work. This is probably the right thing to do, both because I know it
                  will yield a solution, and because it would solve the same issue for
                  anyone else with similar needs. Unfortunately it means I have to dig
                  into Python threading internals, pythoncom internals, and the whole
                  COM mechanism rather more heavily than I'd hoped to.


                  Regards,
                  John

                  Comment

                  • Mark Hammond

                    #10
                    Re: Multithreaded COM server problem...

                    John Lull wrote:
                    [color=blue]
                    > 1. Dig into exactly how apartment-threaded servers are supposed to be
                    > written in C or C++ & see how much of that is transferrable to Python.
                    > I'm not confident this would give me a solution, though.[/color]
                    ....[color=blue]
                    > 3. Dig into why setting sys.coinit_flag s=0 in localserver.py doesn't
                    > work. This is probably the right thing to do, both because I know it
                    > will yield a solution, and because it would solve the same issue for
                    > anyone else with similar needs. Unfortunately it means I have to dig
                    > into Python threading internals, pythoncom internals, and the whole
                    > COM mechanism rather more heavily than I'd hoped to.[/color]

                    I think these are pretty-much the same option. The win32com extensions
                    make no attempt to insulate you from these threading issues - it will
                    blindly do what you ask. In that regard, Python already acts very much
                    like a C/C++ application - under the covers, we have a C++ pointer to a
                    COM object, and when Python code tries to make a call, from whatever
                    thread, we just make the call. The same thing is true in reverse - we
                    hand out a C++ pointer, and when an incoming call is made on that, we
                    blindly call Python on that thread.

                    The Python GIL is in the picture, but I see no evidence it has any
                    bearing in this case. Certainly, in the simple example we started with,
                    no threads were blocked waiting for this lock.

                    Re sys.soinit_flag s - I am fairly confident that this will cause
                    CoInitEx to be called with the specified flag as pythoncom is imported.
                    However, note that any exceptions raised by this function are masked
                    when called implicitly during that import. Try explicitly calling
                    pythoncom.CoIni tializeEx(0) near the top of localserver.py - any
                    exceptions you see would also have happened for the implicit call made
                    at import time.

                    I'd welcome any evidence that a C++ app would behave differently, and am
                    keen to help you resolve (or at least explain) this for everyone's
                    future benefit.

                    Mark.

                    Comment

                    Working...