2008/7/19 Robert Tate <[EMAIL PROTECTED]>:
> Neat project!
>
> Attached is a patch for your consideration.  It adds an optional
> argument to the "xpra server" sub-command to run a specified command
> (presumably an X client application); when this application exits, the
> server is terminated (unless --no-automatic-stop was specified).
>
> Sample usages:  (Note:  a -- is required before the command if it
> contains any options starting with a -.)
>     xpra start :20 -- xterm -fn 10x20
>     xpra --no-automatic-stop start :20 xterm

Neat patch!  Rather than overload the meaning of positional arguments,
I think I like a UI like:
  xpra start :20 --with-child="xterm -fn 10x20"

This also allows for people to specify multiple programs if they want
that for some reason.

Comments for future reference:

> +            pid,status=posix.waitpid(-1,posix.WNOHANG)
^^ pleasetrytousespacesitmakesthingsmorereadable

> +                                 + "\t%prog start DISPLAY [ 
> [--no-automatic-stop] -- command]\n"
^^ we don't normally put options explicitly into the synopsis like
this, since putting them all in would take too much space, and
duplicate the automatically generated --help besides.

> +                      help="Don't terminate server when client on start 
> command exits")
^^ Probably better to avoid referring to these as "clients", since
that term becomes overloaded ('xpra attach' is also a client, but a
very different sort).  My version uses "child" instead; maybe there's
a better term yet?

> import posix
^^ Why use posix explicitly, instead of just os?  (On posix systems
posix is an alias for os.)

I also rewrote the process spawning loop to use the subprocess module
instead of fork()/exec() directly, and factored out the (now more
complicated by the addition of multiple-child support) child reaping
functionality into an object, instead of having it refer to global
state.

Revised patch attached, also committed as
0490573b05ba466a373ee6380c24c266423dffd6 on branch
org.vorpus.parti.spawn-children.  Now I just need to test it...

-- Nathaniel
#
# old_revision [0490573b05ba466a373ee6380c24c266423dffd6]
#
# patch "xpra/scripts/main.py"
#  from [ab87ff74958e2549a9e817957a7fe6ed8463a558]
#    to [0a2ece3c5989579ab35b56487882b887b8e4eae2]
# 
# patch "xpra/scripts/server.py"
#  from [e8923cc866750d4196572ea0648f089c02ae69c9]
#    to [c22cd72c24012c339e245f96cecc7e920624b1c5]
#
============================================================
--- xpra/scripts/main.py	ab87ff74958e2549a9e817957a7fe6ed8463a558
+++ xpra/scripts/main.py	0a2ece3c5989579ab35b56487882b887b8e4eae2
@@ -27,12 +27,21 @@ def main(script_file, cmdline):
     parser.add_option("--no-daemon", action="store_false",
                       dest="daemon", default=True,
                       help="Don't daemonize when running as a server")
+    parser.add_option("--no-automatic-stop", action="store_false",
+                      dest="automatic_stop", default=True,
+                      help="Don't terminate server when client on start command exits")
     parser.add_option("--remote-xpra", action="store",
                       dest="remote_xpra", default=None, metavar="CMD",
                       help="How to run 'xpra' on the remote host")
     parser.add_option("-d", "--debug", action="store",
                       dest="debug", default=None, metavar="FILTER1,FILTER2,...",
                       help="List of categories to enable debugging for (or \"all\")")
+    parser.add_option("--start-child", action="append",
+                      dest="children",
+                      help="program to spawn in new server (may be repeated)")
+    parser.add_option("--survive-children", action="store_true",
+                      dest="survive_children", default=False,
+                      help="Don't terminate server when --start-child command(s) exit")
     (options, args) = parser.parse_args(cmdline[1:])
 
     if not args:
============================================================
--- xpra/scripts/server.py	e8923cc866750d4196572ea0648f089c02ae69c9
+++ xpra/scripts/server.py	c22cd72c24012c339e245f96cecc7e920624b1c5
@@ -31,6 +31,37 @@ def deadly_signal(signum, frame):
     #kill(os.getpid(), signum)
     os._exit(128 + signum)
 
+# Note that this class has async subtleties -- e.g., it is possible for a
+# child to exit and us to receive the SIGCHLD before our fork() returns (and
+# thus before we even know the pid of the child).  So be careful:
+class ChildReaper(object):
+    def __init__(self, survive_children):
+        self._children_pids = None
+        self._dead_pids = set()
+        self._survive_children = survive_children
+
+    def set_children_pids(self, children_pids):
+        assert self._children_pids is None
+        self._children_pids = children_pids
+        self.check()
+
+    def check(self):
+        if (self._children_pids
+            and self._children_pids.issubset(self._dead_pids)):
+            print "all children have exited and --survive-children was not specified, exiting"
+            os.exit(0)
+
+    def __call__(self, signum, frame):
+        while 1:
+            try:
+                pid, status = os.waitpid(-1, os.WNOHANG)
+            except OSError:
+                break
+            if pid == 0:
+                break
+            self._dead_pids.add(pid)
+            self.check()
+
 def save_pid(pid):
     prop_set(gtk.gdk.get_default_root_window(),
              "_XPRA_SERVER_PID", "u32", pid)
@@ -101,7 +132,7 @@ def run_server(parser, opts, mode, xpra_
 def run_server(parser, opts, mode, xpra_file, extra_args):
     if len(extra_args) != 1:
         parser.error("need exactly 1 extra argument")
-    display_name = extra_args[0]
+    display_name = extra_args.pop(0)
 
     atexit.register(run_cleanups)
     signal.signal(signal.SIGINT, deadly_signal)
@@ -211,6 +242,16 @@ def run_server(parser, opts, mode, xpra_
     if xvfb_pid is not None:
         save_pid(xvfb_pid)
 
+    child_reaper = ChildReaper(opts.survive_children)
+    # Always register the child reaper, because even if survive_children is
+    # true, we still need to reap them somehow to avoid zombies:
+    signal.signal(signal.SIGCHLD, child_reaper)
+    if opts.children:
+        children_pids = set()
+        for child_cmd in opts.children:
+            children_pids.add(subprocess.Popen(child_cmd, shell=True).pid)
+        child_reaper.set_children_pids(children_pids)
+
     app = XpraServer(sockpath, upgrading)
     def cleanup_socket(self):
         print "removing socket"
_______________________________________________
Parti-discuss mailing list
[email protected]
http://lists.partiwm.org/cgi-bin/mailman/listinfo/parti-discuss

Reply via email to