[execnet-dev] Socket gateway windows service

holger krekel holger at merlinux.eu
Mon Jul 12 09:47:53 CEST 2010


Hi Charles, 

very good.  Let's see about the rsync-performance issue after
we tackled doing the new-style mediators.  They involve one less 
gateway and thus one less receiver-thread and thus much less indirection 
between Invocation and Worker, i think.  Subprocess-pipes are quite fast usually
so i wouldn't expect much of a slow down.  Btw, I am myself not able to do much
coding before some time after EuroPython 2010 - are you subscribed to 
execnet-commit?  

If you want to go ahead with some internal refactorings just clone 
the execnet repository and send me (pull) requests for review. 

cheers,
holger

On Sun, Jul 11, 2010 at 12:01 -0500, Charles Solar wrote:
> Comments inline for this one :)
> 
> On Sun, Jul 11, 2010 at 7:01 AM, holger krekel <holger at merlinux.eu> wrote:
> > Hi Charles,
> >
> > On Thu, Jul 08, 2010 at 12:03 -0500, Charles Solar wrote:
> >> Well its a windows service so on any windows machine you would just do
> >>
> >> python socketserver.py install
> >> python socketserver.py start
> >>
> >> to get the socketserver running on port 8888.
> >
> > i've never seen or done this, actually.  Thanks :)
> >
> >> You can connect to it from any machine with
> >> import execnet
> >> gw=execnet.makegateway( "socket=[hostname]:8888" )
> >>
> >> on the socket server it will spawn a new popen gateway and connect the
> >> socketIO to the popenIO through IOJoiner.
> >>
> >> IOJoiner replaces the io that the gateways on the server side use to
> >> serve requests.  So when the slave socket gateway calls io.read(1) in
> >> the serve loop, its actually calling IOJoiner.read().  IOJoiner.read()
> >> will then read from the socket, and write all incoming data to the
> >> popen gateway.
> >
> > Let me introduce some terms for the three processes in question:
> >
> >    Invocation         Mediator      Worker
> >              <socket>          <popen>
> >
> > And then try to rephrase what you said to see if i understood:
> > The Mediator and Invocation process use a SocketGateway
> > and the Worker and Mediator a PopenGateway.  The Mediator
> > replaces both the Mediator-Invocation IO and and the Mediator-Worker
> > IO with Joiner instances.
> >
> > Then you run the SocketGateway serve code which reads from the
> > Mediator-Invocation Joiner-IO and forward the raw data
> > to the *original* Worker-IO.  The Receiver-Thread of the Mediator-Worker
> > PopenGateway also uses the joiner and what it reads it sends
> > to the original Invocation IO.
> 
> yep, absolutely correct.
> 
> >
> > Requires some concentration to think about this :)
> 
> took me several hours of meditation to come up with it haha.
> 
> >
> > This method you are using requires that the socketservice side
> > has execnet installed and is a bit fragile wrt to execnet
> > integration (the "L" bit although we could maybe introduce a
> > special exception for that). Cool that it works :)
> >
> > I am not sure yet how an alternative could look like.
> > Here are my current thoughts helping also myself
> > to get clearer.
> >
> > I'd like to have something higher level, maybe introduce
> > internally the concept of an "Instantiator", that would
> > get called to "open a subprocess with a bootstrap" and
> > return an IO object.  It would be naturally used by
> > popen-style gateways. This internal refactoring is
> > just an increment how things are now.
> >
> > We could then extend the instantiator to make it
> > call "subprocess.Popen" not directly but rather
> > through an existing Gateway, returning an IO
> > object on the invocation side that is based
> > on communication with the mediator through the/a channel.
> >
> > For bootstrapping a new gateway the Invocation/instantiator
> > would send an "open a subprocess with bootstrap" command
> > to a loop which it installed on the Mediator.
> > The Mediator would call "subprocess.Popen" with the
> > given bootstrap and then read/write from the subprocess
> > pipes and read/write to the channel which connects
> > the Invocation and Mediator.
> 
> Yea that is where I was stuck because I could not modify what the
> invoker to send a different bootstrap to the socket server.  I
> actually had to do a string replace with the given bootstrap code to
> make it use the joiner class.  Which I really did not like doing.
> 
> >
> > If i see it correctly the Mediator would basically
> > do popen.stdin.read(n) and if it reads something
> > write it to the Invocation/Mediator channel.
> > And if the Mediator receives data a channel callback
> > (usually running in the receiver-thread)
> > could send it to Popen.write(s).
> >
> > This way execnet would not need to be installed
> > at the Mediator side and the concept should work
> > uniformly for all gateway types ...
> >
> > cheers,
> > holger
> >
> 
> Sounds good but I have to go digging around in the code more to
> understand where things are connected more clearly.  I was limited by
> not wanting to change execnet internally but if you think this can be
> done with a minimal change to add new features to the popen gateway
> then that sounds like the best route.
> There is something I found out while using my solution the other day.
> I was using RSync to a machine using this socket service and the file
> transfers were taking minutes to send files of 1-2 megs in size.  It
> appears this io mediation takes its toll on communication speed.
> Because for rsync its not necessary to spawn a child process and
> communicate through pipes, I decided to open a new socket that acts
> like the original socket server and executes the commands without
> spawning a child process.  Which fixes the rsync issue.
> I mention it because while we are refactoring this io a bit it might
> be a good idea to make a signal for 'dont spawn child process' or
> something.
> 
> Charles
> 
> >> However, the socket slave gateway still wants a return value.
> >>
> >> Returning what we just read from the socket will produce incorrect
> >> results, so I have it just return the Unserializer's None type, which
> >> will cause the serve loop to not do anything, exactly what I wanted.
> >>
> >> On the popen end, I write the incoming socket data to the master
> >> gateway's io, which is then read by the slave gateway and used.  Also,
> >> all data read from the popen slave gateway is written to the socket
> >> io, same as how socket io passes to popen io.
> >>
> >> IOJoiner needed to use Unserializer types because I did not want the
> >> slave socket gateway or the master popen io do anything, as their only
> >> purpose is to facilitate communication between the other io gateways.
> >>
> >> I have no doubt there is a better way to do this, but with my limited
> >> understanding of execnet internals at the moment, this seems like the
> >> easiest solution.
> >>
> >> Charles
> >>
> >> On Thu, Jul 8, 2010 at 11:33 AM, holger krekel <holger at merlinux.eu> wrote:
> >> > Hi Charles,
> >> >
> >> > am quite busy with releasing some bits before i leave for a couple of days
> >> > but i am wondering:
> >> >
> >> > a) could you provide a full example on how to use your code?
> >> >
> >> > b) why does the IOJoiner need to be aware of (Un)Serializer details?
> >> >
> >> > cheers,
> >> > holger
> >> >
> >> > On Thu, Jul 08, 2010 at 10:39 -0500, Charles Solar wrote:
> >> >> Ok I have a solution that works for my purposes.  I rethought my
> >> >> original idea and figured out how to proxy the io instances to the new
> >> >> popengateway that socketserver creates, so no modifications to execnet
> >> >> internals was necessary.
> >> >> I am attaching my solution, but there are some lines I would like you
> >> >> to look at and probably fix marked by XXX
> >> >> For those lines I either used a trick that could break at any update,
> >> >> or I had to assume things that I am not sure should be assumed.
> >> >>
> >> >> Look forward to seeing what you do with it :)
> >> >>
> >> >> Charles
> >> >>
> >> >> On Sat, Jul 3, 2010 at 3:02 AM, holger krekel <holger at merlinux.eu> wrote:
> >> >> > Hi Charles,
> >> >> >
> >> >> > On Fri, Jul 02, 2010 at 16:31 -0500, Charles Solar wrote:
> >> >> >> Ah, a few hours after sending this I found the scripts folder, and here I
> >> >> >> thought socketserver.py was just an example.. oh well.
> >> >> >
> >> >> > Your code still makes sense - an installable windows service would be great.
> >> >> >
> >> >> >> Anyway after using this a bit I am afraid I need to modify how the
> >> >> >> Socketgateway works and I would like a tip or two about a couple things.
> >> >> >>
> >> >> >> I noticed that socketgateways will run the remote code in their own world,
> >> >> >> which is a bit different from ssh gateways where the ssh session terminates
> >> >> >> when the job is done.  The sshd remains untainted because the remote server
> >> >> >> spawned a nice python process for us.
> >> >> >> However the socketgateways run the remote code in themselves and thus if the
> >> >> >> remote code dirties up the interpreter the whole daemon is bad.
> >> >> >
> >> >> > True.
> >> >> >
> >> >> >> In my case I am starting a twisted reactor on the remote server, and once a
> >> >> >> twisted reactor has been created its expected that it is alive as long as
> >> >> >> the python process itself.  Because of this, the socket gateway daemon is
> >> >> >> only good for 1 connection, then it crashes because twisted's reactor has
> >> >> >> issues.
> >> >> >>
> >> >> >> The solution to this problem would be to execute the incoming remote code in
> >> >> >> a new popen gateway on the remote server instead of inside the socket
> >> >> >> gateway instance itself.  I have been skimming a few files but I am not
> >> >> >> completely sure how or where would be a good place to put the popen gateway.
> >> >> >> My gut tells me the proxy should be put in SocketIO, but I figured it might
> >> >> >> save some time to send out an email to find out how these systems interact.
> >> >> >
> >> >> > not sure, but for now i'd rather not try to nest execnet gateways,
> >> >> > mostly because it will be fun to debug (there is some logging and
> >> >> > nested gateways generally work though).  Also, a nice property of
> >> >> > the socketgateway server is that it's rather independent from execnet
> >> >> > impl details.
> >> >> >
> >> >> > Rather, the socketgateway.py service could learn to act just like
> >> >> > the ssh-daemon by using subprocess.Popen and allowing multiple connections.
> >> >> > IMHO just using threads that own a socket and proxy IO to their subprocess'ed
> >> >> > gateway could be straightforward.
> >> >> >
> >> >> > In any case, I am happy to review and integrate your code into the next release
> >> >> > if you go down this route, both for the service and the socketserver issue.
> >> >> >
> >> >> > cheers,
> >> >> > holger
> >> >> >
> >> >> >
> >> >> >>
> >> >> >> Thanks
> >> >> >>
> >> >> >> On Fri, Jul 2, 2010 at 11:31 AM, Charles Solar <charlessolar at gmail.com>wrote:
> >> >> >>
> >> >> >> > I wrote up a windows service script for starting a socket gateway.  Thought
> >> >> >> > other people might like to use it.  Its pretty basic, but gets the job
> >> >> >> > done.  PyWin32 is required.
> >> >> >> >
> >> >> >> > Thanks for the great library.
> >> >> >> >
> >> >> >> > Charles
> >> >> >> >
> >> >> >
> >> >> >> _______________________________________________
> >> >> >> execnet-dev mailing list
> >> >> >> execnet-dev at codespeak.net
> >> >> >> http://codespeak.net/mailman/listinfo/execnet-dev
> >> >> >
> >> >> >
> >> >> > --
> >> >> >
> >> >
> >> >> """
> >> >> Windows service for handling incomming socket gateways
> >> >> """
> >> >>
> >> >> import threading, SocketServer
> >> >>
> >> >> import win32serviceutil
> >> >> import win32service
> >> >> import win32event
> >> >> import servicemanager
> >> >>
> >> >> import execnet
> >> >> from execnet.gateway_socket import SocketIO
> >> >>
> >> >> class IOJoiner():
> >> >>     """
> >> >>     Joins two io instances so when one wants to read, the incoming data is
> >> >>     sent straight to the other io.  Useful for tieing the Popen gateway IO
> >> >>     and the SocketIO.
> >> >>     """
> >> >>     def __init__( self, primaryIO, secondaryIO ):
> >> >>         self.prim = primaryIO
> >> >>         self.sec = secondaryIO
> >> >>
> >> >>     def read( self, numbytes ):
> >> >>         buf = self.prim.read( numbytes )
> >> >>         self.sec.write( buf )
> >> >>         # 'L' corresponds to the 'NoneType' in the Unserializer
> >> >>         # Do this so the person who called read does not freak out
> >> >>         # XXX I should return an object ACTUALLY representing NoneType, but not sure atm
> >> >>         # how to access that data.
> >> >>         return 'L'
> >> >>
> >> >>     def write( self, data ):
> >> >>         # XXX I do not see any reason for the other io to worry
> >> >>         # About things the primary is writing, I could be wrong though.
> >> >>         self.prim.write( data )
> >> >>
> >> >>     def close_read( self ):
> >> >>         self.prim.close_read()
> >> >>
> >> >>     def close_write( self ):
> >> >>         self.prim.close_write()
> >> >>
> >> >>
> >> >> class PythonService(win32serviceutil.ServiceFramework):
> >> >>     _svc_name_ = "PythonSocketServer"
> >> >>     _svc_display_name_ = "Python execnet socket server"
> >> >>     _svc_description_ = "Gateway server to allow execnet to connect to windows machines"
> >> >>     def __init__(self, args):
> >> >>         win32serviceutil.ServiceFramework.__init__(self, args)
> >> >>         # Create an event which we will use to wait on.
> >> >>         # The "service stop" request will set this event.
> >> >>         self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
> >> >>
> >> >>     def SvcStop(self):
> >> >>         # Before we do anything, tell the SCM we are starting the stop process.
> >> >>         self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
> >> >>         # And set my event.
> >> >>         win32event.SetEvent(self.hWaitStop)
> >> >>
> >> >>     def SvcDoRun(self):
> >> >>
> >> >>         servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,servicemanager.PYS_SERVICE_STARTED,(self._svc_name_, ''))
> >> >>
> >> >>         self.main()
> >> >>
> >> >>         while True:
> >> >>             rc = win32event.WaitForSingleObject(self.hWaitStop, 1000)
> >> >>             if rc == win32event.WAIT_OBJECT_0:
> >> >>                 self._server.shutdown()
> >> >>                 self._serverThread.join()
> >> >>                 servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,servicemanager.PYS_SERVICE_STOPPED,(self._svc_name_, ''))
> >> >>                 break
> >> >>
> >> >>     def main( self ):
> >> >>
> >> >>         class TCPHandler( SocketServer.StreamRequestHandler ):
> >> >>             def handle( self ):
> >> >>                 source = self.rfile.readline().rstrip()
> >> >>
> >> >>                 # Spawn a child python process for the new connection
> >> >>                 # XXX It would be nice if there was a way to remotely request a new ironpython or jython instance instead of a generic cpython instance
> >> >>                 # but that would require modifications to SocketGateway I believe.
> >> >>                 gw = execnet.PopenGateway(python='python')
> >> >>
> >> >>                 sockio = SocketIO( self.request )
> >> >>                 gwio = gw._io
> >> >>
> >> >>                 source = source.replace( "io = SocketIO(clientsock)", "io = joiner" )
> >> >>
> >> >>                 g = { 'joiner': IOJoiner( sockio, gwio ), 'address': self.client_address }
> >> >>                 gw._io = IOJoiner( gwio, sockio )
> >> >>
> >> >>                 source = eval(source)
> >> >>
> >> >>                 if source:
> >> >>                     co = compile( source+'\n', source, 'exec' )
> >> >>
> >> >>                     try:
> >> >>                         exec co in g
> >> >>                     except Exception as e:
> >> >>                         servicemanager.LogErrorMsg( "Execution of received source code raised the following exception: %r" % e )
> >> >>
> >> >>         self._server = SocketServer.ThreadingTCPServer( ('0.0.0.0', 8888), TCPHandler )
> >> >>         self._serverThread = threading.Thread( target=self._server.serve_forever )
> >> >>         self._serverThread.setDaemon( True )
> >> >>         self._serverThread.start()
> >> >>
> >> >> if __name__ == '__main__':
> >> >>     win32serviceutil.HandleCommandLine(PythonService)
> >> >
> >> >
> >> > --
> >> >
> >>
> >
> > --
> >
> 

-- 



More information about the execnet-dev mailing list