Author: duncan
Date: Fri Jun  1 18:00:46 2007
New Revision: 9626

Modified:
   branches/rel-1/freevo/ChangeLog
   branches/rel-1/freevo/src/childapp.py

Log:
[ 1729597 ] Use subprocess for child application
Update applied


Modified: branches/rel-1/freevo/ChangeLog
==============================================================================
--- branches/rel-1/freevo/ChangeLog     (original)
+++ branches/rel-1/freevo/ChangeLog     Fri Jun  1 18:00:46 2007
@@ -17,6 +17,7 @@
 --------------------------------
 
  * Added personal web pages to the webserver, using PERSONAL_WWW_PAGE 
(F#1729595)
+ * Updated childapp to use subprocess instead of popen (F#1729597)
  * Fixed tv recordings manager not deleting files when out of space (B#1728182)
  * Fixed problem with FXD.setattr not working correctly in all cases 
(B#1728421)
 

Modified: branches/rel-1/freevo/src/childapp.py
==============================================================================
--- branches/rel-1/freevo/src/childapp.py       (original)
+++ branches/rel-1/freevo/src/childapp.py       Fri Jun  1 18:00:46 2007
@@ -32,10 +32,10 @@
 import sys
 import time
 import os
-import util.popen3
 import threading, thread
 import signal
 import copy
+from subprocess import Popen, PIPE
 
 import config
 import osd
@@ -61,7 +61,6 @@
             app = app.encode(config.LOCALE, 'ignore')
 
         if isinstance(app, str):
-            _debug_('%r is a string' % app, 1)
             # app is a string to execute. It will be executed by 'sh -c '
             # inside the popen code
             if app.find('--prio=') == 0 and not config.RUNAPP:
@@ -75,11 +74,10 @@
             else:
                 self.binary = app.lstrip()
 
-            start_str = '%s %s' % (config.RUNAPP, app)
+            command = '%s %s' % (config.RUNAPP, app)
             debug_name = app[:app.find(' ')]
 
         else:
-            _debug_('%r is a list' % app, 1)
             while '' in app:
                 app.remove('')
 
@@ -93,9 +91,9 @@
             self.binary = str(' ').join(app)
 
             if config.RUNAPP:
-                start_str = [ config.RUNAPP ] + app
+                command = [ config.RUNAPP ] + app
             else:
-                start_str = app
+                command = app
 
             debug_name = app[0]
 
@@ -111,16 +109,39 @@
         if doeslogging or config.CHILDAPP_DEBUG:
             doeslogging = 1
 
-        self.child   = util.popen3.Popen3(start_str)
-        self.outfile = self.child.fromchild
-        self.errfile = self.child.childerr
-        self.infile  = self.child.tochild
+        stdout_logger = os.path.join(config.LOGDIR, '%s-stdout.log' % 
(debug_name))
+        try:
+            self.stdout_log = doeslogging and open(stdout_logger, 'w') or None
+        except OSError, e:
+            _debug_('Cannot open "%s": %s' % (stdout_logger, e), 0)
+            self.stdout_log = None
+
+        stderr_logger = os.path.join(config.LOGDIR, '%s-stderr.log' % 
(debug_name))
+        try:
+            self.stderr_log = doeslogging and open(stderr_logger, 'w') or None
+        except OSError, e:
+            _debug_('Cannot open "%s": %s' % (stderr_logger, e), 0)
+            self.stderr_log = None
+
+        command_isstr = isinstance(command, str)
+        if command_isstr:
+            command = command.split()
+        command_str = ' '.join(command)
+        self.child = None
+        try:
+            self.child = Popen(command, shell=False, stdin=PIPE, stdout=PIPE, 
stderr=PIPE)
+            _debug_('Running (%s) "%s" with pid %s prioity %s' % (\
+                command_isstr and 'str' or 'list', command_str, 
self.child.pid, prio), 1)
+        except OSError, e:
+            _debug_('Cannot run "%s": %s' % (command_str, e), 0)
+            self.ready = False
+            return
 
-        self.so = Read_Thread('stdout', self.outfile, self.stdout_cb, 
debug_name, doeslogging)
+        self.so = Read_Thread('stdout', self.child.stdout, self.stdout_cb, 
debug_name, doeslogging)
         self.so.setDaemon(1)
         self.so.start()
 
-        self.se = Read_Thread('stderr', self.errfile, self.stderr_cb, 
debug_name, doeslogging)
+        self.se = Read_Thread('stderr', self.child.stderr, self.stderr_cb, 
debug_name, doeslogging)
         self.se.setDaemon(1)
         self.se.start()
 
@@ -129,72 +150,83 @@
             os.system('%s %s -p %s 2>/dev/null >/dev/null' % \
                       (config.CONF.renice, prio, self.child.pid))
 
-        if config.DEBUG:
-            _debug_('self.so.isAlive()=%s, self.se.isAlive()=%s' % \
-                (self.so.isAlive(), self.se.isAlive()), 1)
-            time.sleep(0.1)
-            if not isinstance(start_str, str):
-                start_str = ' '.join(start_str)
-            _debug_('ChildApp.__init__(), pid=%s, app=\"%s\", poll=%s' % \
-                  (self.child.pid, start_str, self.child.poll()), 1)
-
         self.ready = True
 
 
     # Write a string to the app.
     def write(self, line):
-        try:
-            self.infile.write(line)
-            self.infile.flush()
-        except (IOError, ValueError):
-            pass
+        _debug_('sending "%s" to pid %s' % (line.strip('\n'), self.child.pid))
+        #self.shild.communicate(line)
+        self.child.stdin.write(line)
+        self.child.stdin.flush()
 
 
-    # Override this method to receive stdout from the child app
-    # The function receives complete lines
     def stdout_cb(self, line):
+        '''Override this method to receive stdout from the child app
+        The function receives complete lines'''
         pass
 
 
-    # Override this method to receive stderr from the child app
-    # The function receives complete lines
     def stderr_cb(self, line):
+        '''Override this method to receive stderr from the child app
+        The function receives complete lines'''
         pass
 
 
     def isAlive(self):
+        if not self.child:
+            return False
         if not self.ready: # return true if constructor has not finished yet
             return True
-        return self.so.isAlive() or self.se.isAlive()
+        return self.child.poll() == None
 
 
     def wait(self):
-        return util.popen3.waitpid(self.child.pid)
+        """
+        wait for the child process to stop
+        returns the (pid, status) tuple
+        """
+        #self.child.wait()
+        #self.status = self.child.returncode
+        #return (self.child.pid, self.status)
+        # this is the wait in ChildApp2
+        try:
+            pid, status = os.waitpid(self.child.pid, os.WNOHANG)
+        except OSError:
+            # strange, no child? So it is finished
+            return True
+
+        if pid == self.child.pid:
+            self.status = self.child.returncode
+            return True
+        return False
 
 
     def kill(self, signal=15):
-        _debug_('kill signal=%s' % (signal), 1)
+        '''
+        Kill the application
+        '''
 
         # killed already
-        if hasattr(self,'child'):
-            if not self.child:
-                _debug_('already dead', 1)
-                return
-        else:
+        if not hasattr(self, 'child'):
             _debug_('This should never happen!', 1)
+            #raise 'no child attribute'
+            return
+
+        if not self.child:
+            _debug_('already dead', 1)
+            #raise 'already dead'
             return
 
         self.lock.acquire()
         try:
             # maybe child is dead and only waiting?
-            if self.wait():
-                _debug_('killed the easy way', 1)
-                self.so.join()
-                self.se.join()
-                _debug_('reading threads stopped', 1)
+            if self.child.poll() != None:
+                _debug_('killed the easy way, status %s' % 
(self.child.returncode), 1)
+                if not self.child.stdin.closed: self.child.stdin.close()
+                if self.stdout_log: self.stdout_log.close()
+                if self.stderr_log: self.stderr_log.close()
                 self.child = None
-                if not self.infile.closed:
-                    self.infile.close()
                 return
 
             if signal:
@@ -210,9 +242,10 @@
                     break
                 time.sleep(0.1)
             else:
-                _debug_('force killing with signal 9', 1)
+                signal = 9
+                _debug_('killing pid %s signal %s' % (self.child.pid, signal), 
1)
                 try:
-                    os.kill(self.child.pid, 9)
+                    os.kill(self.child.pid, signal)
                 except OSError:
                     pass
                 for i in range(20):
@@ -222,23 +255,23 @@
             _debug_('childapp: After wait()', 1)
 
 
-            # now check if the app is really dead. If it is, outfile
-            # should be closed by the reading thread
+            # now check if the app is really dead. If it is, poll()
+            # will return the status code
             for i in range(5):
-                if self.outfile.closed:
+                if self.child.poll() != None:
                     break
                 time.sleep(0.1)
             else:
                 # Problem: the program had more than one thread, each thread 
has a
                 # pid. We killed only a part of the program. The filehandles 
are
                 # still open, the program still lives. If we try to close the 
infile
-                # now, Freevo will be dead.
+                # now, Freevo will die.
                 # Solution: there is no good one, let's try killall on the 
binary. It's
                 # ugly but it's the _only_ way to stop this nasty app
                 _debug_('Oops, command refuses to die, try bad hack....', 1)
                 util.killall(self.binary, sig=15)
                 for i in range(20):
-                    if self.outfile.closed:
+                    if self.child.poll() != None:
                         break
                     time.sleep(0.1)
                 else:
@@ -247,97 +280,17 @@
                     _debug_('Try harder to kill the app....', 1)
                     util.killall(self.binary, sig=9)
                     for i in range(20):
-                        if self.outfile.closed:
+                        if self.child.poll() != None:
                             break
                         time.sleep(0.1)
                     else:
                         _debug_('PANIC can\'t kill program', 0)
-                if not self.infile.closed:
-                    self.infile.close()
-            self.child = None
         finally:
             self.lock.release()
-
-
-
-class Read_Thread(threading.Thread):
-    """
-    Thread for reading stdout or stderr from the child
-    """
-    def __init__(self, name, fp, callback, logger=None, doeslogging=0):
-        threading.Thread.__init__(self)
-        self.name = name
-        self.fp = fp
-        self.callback = callback
-        self.logger = None
-        if logger and doeslogging:
-            logger = os.path.join(config.LOGDIR, '%s-%s.log' % (logger, name))
-            try:
-                try:
-                    os.unlink(logger)
-                except:
-                    pass
-                self.logger = open(logger, 'w')
-                _debug_(String(_('logging child to "%s"')) % logger, 1)
-            except IOError:
-                _debug_(String(_('ERROR'))+': '+String(_('Cannot open "%s" for 
logging!')) \
-                    % logger, 1)
-                _debug_(String(_('Set CHILDAPP_DEBUG=0 in local_conf.py, or 
make %s writable!')) \
-                    % config.LOGDIR, 1)
-
-
-    def run(self):
-        try:
-            self._handle_input()
-        except (IOError, ValueError):
-            pass
-
-
-    def _handle_input(self):
-        saved = ''
-        while 1:
-
-            data = self.fp.readline(300)
-            if not data:
-                _debug_('%s: No data, stopping (pid %s)!' % (self.name, 
os.getpid()), 1)
-                self.fp.close()
-                if self.logger:
-                    self.logger.close()
-                break
-            else:
-                data  = data.replace('\r', '\n')
-                lines = data.split('\n')
-
-                # Only one partial line?
-                if len(lines) == 1:
-                    saved += data
-                else:
-                    # Combine saved data and first line, send to app
-                    if self.logger:
-                        line = (saved + lines[0]).strip('\n')
-                        self.logger.write(line+'\n')
-                    rc.register(self.callback, False, 0, saved + lines[0])
-                    saved = ''
-
-                    # There's one or more lines + possibly a partial line
-                    if lines[-1] != '':
-                        # The last line is partial, save it for the next time
-                        saved = lines[-1]
-
-                        # Send all lines except the last partial line to the 
app
-                        for line in lines[1:-1]:
-                            if self.logger:
-                                line = line.strip('\n')
-                                self.logger.write(line+'\n')
-                            rc.register(self.callback, False, 0, line)
-                    else:
-                        # Send all lines to the app
-                        for line in lines[1:]:
-                            if self.logger:
-                                line = line.strip('\n')
-                                self.logger.write(line+'\n')
-                            rc.register(self.callback, False, 0, line)
-
+        if not self.child.stdin.closed: self.child.stdin.close()
+        if self.stdout_log: self.stdout_log.close()
+        if self.stderr_log: self.stderr_log.close()
+        self.child = None
 
 
 
@@ -400,7 +353,6 @@
         rc.unregister(self.stop)
 
         if cmd and self.isAlive():
-            _debug_('sending exit command to app \"%r\"' % cmd, 1)
             self.write(cmd)
             # wait for the app to terminate itself
             for i in range(60):
@@ -409,7 +361,8 @@
                 time.sleep(0.1)
 
         # kill the app
-        self.kill()
+        if self.isAlive():
+            self.kill()
 
         # Ok, we can use the OSD again.
         if self.stop_osd:
@@ -426,3 +379,81 @@
         if not self.isAlive():
             rc.post_event(self.stop_event())
             self.stop()
+
+
+
+class Read_Thread(threading.Thread):
+    """
+    Thread for reading stdout or stderr from the child
+    """
+    def __init__(self, name, fh, callback, logger=None, doeslogging=0):
+        '''Constructor of Read_Thread'''
+        _debug_('Read_Thread.__init__(name=%r, fh=%r, callback=%r, logger=%r, 
doeslogging=%r' % \
+            (name, fh, callback, logger, doeslogging), 2)
+        threading.Thread.__init__(self)
+        self.name = name
+        self.fh = fh
+        self.callback = callback
+        self.logger = None
+        if logger and doeslogging:
+            logger = os.path.join(config.LOGDIR, '%s-%s.log' % (logger, name))
+            try:
+                try:
+                    os.unlink(logger)
+                except:
+                    pass
+                self.logger = open(logger, 'w')
+                _debug_('logging %s child to "%s"' % (name, logger))
+            except IOError, e:
+                _debug_('cannot open "%s" for logging: %s' % (logger, e))
+
+
+    def run(self):
+        try:
+            self._handle_input()
+        except (IOError, ValueError):
+            pass
+
+
+    def _handle_input(self):
+        saved = ''
+        while 1:
+            data = self.fh.readline(300)
+            if not data:
+                _debug_('%s: no data, closing log' % (self.name), 1)
+                self.fh.close()
+                if self.logger: self.logger.close()
+                break
+            else:
+                data  = data.replace('\r', '\n')
+                lines = data.split('\n')
+
+                # Only one partial line?
+                if len(lines) == 1:
+                    saved += data
+                else:
+                    # Combine saved data and first line, send to app
+                    if self.logger:
+                        line = (saved + lines[0]).strip('\n')
+                        self.logger.write(line+'\n')
+                    rc.register(self.callback, False, 0, saved + lines[0])
+                    saved = ''
+
+                    # There's one or more lines + possibly a partial line
+                    if lines[-1] != '':
+                        # The last line is partial, save it for the next time
+                        saved = lines[-1]
+
+                        # Send all lines except the last partial line to the 
app
+                        for line in lines[1:-1]:
+                            if self.logger:
+                                line = line.strip('\n')
+                                self.logger.write(line+'\n')
+                            rc.register(self.callback, False, 0, line)
+                    else:
+                        # Send all lines to the app
+                        for line in lines[1:]:
+                            if self.logger:
+                                line = line.strip('\n')
+                                self.logger.write(line+'\n')
+                            rc.register(self.callback, False, 0, line)

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/
_______________________________________________
Freevo-cvslog mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog

Reply via email to