gump-general mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Leo Simons <m...@leosimons.com>
Subject Runtime.exec & Ant & Gump3 (was: Re: svn commit: r210128 - in /gump/branches/Gump3)
Date Mon, 11 Jul 2005 15:01:24 GMT
So, ehm,

I've had to learn a lot about process management in the last two days,
most importantly that java is *really* bad at doing it properly. If you
open

  pygump/python/gump/plugins/java/builder.py

and edit the AntBuilderPlugin to have no_cleanup=False instead of
no_cleanup=True, then do a gump run using the vmgump.xml profile, the
run will usually stall trying to invoke java_cup.

The reason for this seems to be that java sometimes deadlocks when
forked from java. I built a "trivial" testcase (basically rewrote the
Execute.java from ant to manually run my demo program and wrote a simple
python wrapper to fire that up) and the problem does not occur there, so
I suspect (after stepping through both python and java debuggers for a
whole lot) that something like multi-threading or garbage collection is
in some way significant.

To be clear, this isn't a bug in gump or a bug in ant, but a bug in the
JDK in interaction with a very specific environment. It'll be
interesting to see if, for instance, the same mess doesn't occur when
using Kaffe. I suspect that using any JVM for which Ant's Execute takes
a different approach (ie not using Runtime.exec) makes the problem "go
away".

We'll have to see if this becomes a problem or not (eg zombie
processes). I'll try hard to, if we run into issues, produce big and
scary stack traces. My hunch is that we'd have to implement a work
around in Ant...I doubt sun is going to fix their jdk...

cheers,

Leo

leosimons@apache.org wrote:
>  * disable process group management for running ant. See inside
>    gump.plugins.java.builder.AntPlugin for some details. This was a *huge*
>    pain to figure out. What triggered this is the invocation of java_cup
>    from the xalan build.xml file, which has a <java fork="true".
...
>      <project name="xalan">
> -        <module name="xml"/>
> +        <module name="xml-xalan"/>
>          
>          <!-- commands -->
>          <ant basedir="java" target="unbundledjar">
> @@ -252,6 +263,8 @@
>      </project>
...
> +    def _do_run_command(self, command, args, workdir, shell=False, no_cleanup=False):
> +        # see gump.plugins.java.builder.AntPlugin for information on the
> +        # no_cleanup flag
> +        
...
> -            cmd = Popen(myargs,shell=False,cwd=workdir,stdout=outputfile,stderr=STDOUT,env=command.env)
> +            cmd = Popen(myargs,shell=False,cwd=workdir,stdout=outputfile,stderr=STDOUT,env=command.env,
no_cleanup=no_cleanup)
...
> -            command.build_log = outputfile.read()
> +            # we need to avoid Unicode errors when people put in 'fancy characters'
> +            # into build outputs
> +            command.build_log = unicode(outputfile.read(), 'iso-8859-1')
> +    import tempfile
...
> -    def _get_new_process_group():
> -        """Get us an unused (or so we hope) process group."""
> -        pid = os.fork()
> -        gid = pid # that *should* be correct. However, let's actually
> -                  # create something in that group.
> -        if pid == 0:
> -            # Child
> -            
> -            # ensure a process group is created
> -            os.setpgrp()
> -            
> -            # sleep for ten days to keep the process group around
> -            # for "a while"
> -            import time
> -            time.sleep(10*24*60*60)
> -            os._exit(0)
> -        else:
> -            # Parent
> -    
> -            # wait for child a little so it can set its group
> -            import time
> -            time.sleep(1)
> -            
> -            # get the gid for the child
> -            gid = os.getpgid(pid)
> -        
> -        return gid
> -
> -    # This is the group we chuck our children in. We don't just want to
> -    # use our own group since we don't want to kill ourselves prematurely!
> -    _our_process_group = _get_new_process_group()
> +    temp_dir = tempfile.mkdtemp("gump_util_executor")
> +    process_list_filename = os.path.join(temp_dir, "processlist.pids")
>  
> +    def savepgid(filename):
> +        """Function called from Popen child process to create new process groups."""
> +        os.setpgrp()
> +        f = None
> +        try:
> +            grp = os.getpgrp()
> +            f = open(filename,'a+')
> +            f.write("%d" % grp)
> +            f.write('\n')
> +        finally:
> +            if f:
> +                try: f.close()
> +                except: pass
> +            
>      class Popen(subprocess.Popen):
>          """This is a thin wrapper around subprocess.Popen which handles
>          process group management. The gump.util.executor.clean_up_processes()
> @@ -106,35 +109,67 @@
>                       stdin=None, stdout=None, stderr=None,
>                       preexec_fn=None, close_fds=False, shell=False,
>                       cwd=None, env=None, universal_newlines=False,
> -                     startupinfo=None, creationflags=0):
> -            """Create a new Popen instance that delegates to the
> -            subprocess Popen."""
> -            if not preexec_fn:
> -                # setpgid to the gump process group inside the child
> -                pre_exec_function = lambda: os.setpgid(0, _our_process_group)
> -            else:
> -                # The below has a "stupid lambda trick" that makes the lambda
> -                # evaluate a tuple of functions. This sticks our own function
> -                # call in there while still supporting the originally provided
> -                # function
> -                pre_exec_function = lambda: (preexec_fn(),os.setpgid(0, _our_process_group))
> -            
> +                     startupinfo=None, creationflags=0, no_cleanup=False):
> +            # see gump.plugins.java.builder.AntPlugin for information on the
> +            # no_cleanup flag
> +
>              # a logger can be set for this module to make us log commands
>              if _log:
>                  _log.info("        Executing command:\n      %s'%s'%s\n       in directory
'%s'" % (ansicolor.Blue, " ".join(args), ansicolor.Black, os.path.abspath(cwd or os.curdir)))
> -            
> -            subprocess.Popen.__init__(self, args, bufsize=bufsize, executable=executable,
> -                     stdin=stdin, stdout=stdout, stderr=stderr,
> -                     # note our custom function in there...
> -                     preexec_fn=pre_exec_function, close_fds=close_fds, shell=shell,
> -                     cwd=cwd, env=env, universal_newlines=universal_newlines,
> -                     startupinfo=startupinfo, creationflags=creationflags)
> +
> +            if not no_cleanup:
> +                global process_list_filename
> +                """Create a new Popen instance that delegates to the
> +                subprocess Popen."""
> +                if not preexec_fn:
> +                    # setpgid to the gump process group inside the child
> +                    pre_exec_function = lambda: savepgid(process_list_filename)
> +                else:
> +                    # The below has a "stupid lambda trick" that makes the lambda
> +                    # evaluate a tuple of functions. This sticks our own function
> +                    # call in there while still supporting the originally provided
> +                    # function
> +                    pre_exec_function = lambda: (preexec_fn(),savepgid(process_list_filename))
> +                
> +                
> +                subprocess.Popen.__init__(self, args, bufsize=bufsize, executable=executable,
> +                         stdin=stdin, stdout=stdout, stderr=stderr,
> +                         # note our custom function in there...
> +                         preexec_fn=pre_exec_function, close_fds=close_fds, shell=shell,
> +                         cwd=cwd, env=env, universal_newlines=universal_newlines,
> +                         startupinfo=startupinfo, creationflags=creationflags)
> +            else:
> +                subprocess.Popen.__init__(self, args, bufsize=bufsize, executable=executable,
> +                         stdin=stdin, stdout=stdout, stderr=stderr,
> +                         # note our custom function is *not* in there...
> +                         preexec_fn=preexec_fn, close_fds=close_fds, shell=shell,
> +                         cwd=cwd, env=env, universal_newlines=universal_newlines,
> +                         startupinfo=startupinfo, creationflags=creationflags)
> +                
>  
>      def clean_up_processes(timeout=300):
>          """This function can be called prior to program exit to attempt to
>          kill all our running children that were created using this module."""
>      
> -        pgrp_list = [_our_process_group]
> +        global process_list_filename
> +        global temp_dir
> +
> +        pgrp_list = []
> +
> +        f = None
> +        try:
> +            f = open(process_list_filename, 'r')
> +            pgrp_list = [int(line) for line in f.read().splitlines()]
> +        except:
> +            if f: 
> +                try: f.close()
> +                except: pass
> +        try:
> +            import shutil
> +            shutil.rmtree(temp_dir)
> +        except:
> +            pass
> +        

---------------------------------------------------------------------
To unsubscribe, e-mail: general-unsubscribe@gump.apache.org
For additional commands, e-mail: general-help@gump.apache.org


Mime
View raw message