Re: GUI-safe polling loop




On Feb 1, 2007, at 8:10 PM, Matthew Braid wrote:

Just wanted to know if what I'm doing makes sense.

I'm trying to poll for a child process being finished in a way that will not freeze the GUI up, but also won't let the surrounding code continue until the child process is finished.

GLib provides GChildWatch, which is a GSource that fires when a given pid exits. The Right Way to do this is along the lines of

    my $loop = Glib::MainLoop->new;
    $id = Glib::ChildWatch->add ($child_pid, sub { $loop->exit; 0 });
    $loop->run;

However, on looking through GMainLoop.xs just now, it appears that g_child_watch_add() is not bound to perl.

:-P


Another thought would be to create your own GSource in perl, and hook that into the main loop. Of course, the utilities you need to do that are (you guessed it) also not bound to perl.

Dammit.



Assuming $kid is the process id of the child, is the following code
reasonable or am I missing something basic:

use POSIX qw/WNOHANG/;
require Glib;
#...
while (1) {
  last if waitpid($kid, WNOHANG); # Check if we're done
  my $ml = Glib::MainLoop->new;
  Glib::Timeout->add(2000, sub { $ml->quit; return 0 });
  $ml->run;
}

This gives you a timer granularity, which means that you won't acknowledge the child's termination in a timely manner. To fix that, you shorten the timeout, which increases the load.

The real solution is to use something that fires from SIGCHLD to wake up a source being polled by the main loop. (This is what GChildWatch does...) So, a pure-perl reimplementation of GChildWatch might look like this:


#!/usr/bin/perl -w

package Mup::ChildWatch;

use strict;

use Glib;

#
# A little private mapping of PIDs to callbacks and their data.
#
my %callback;

#
# One of the very few things that are safe to do from async signal handlers # is to write to a pipe. It just so happens that pipes are one of the things # we can easily monitor in the main loop. So, here, we'll create a pipe to # which we'll write in the SIGCHLD handler, and then monitor in the main loop.
#
pipe CHLDEXITREAD, CHLDEXITWRITE;

#
# Install our CHLD handler. Of course, it's usually very bad form for library
# code to install signal handlers, so i don't think of this as a perfect
# solution... but in here, when we get a CHLD, we'll tickle the pipe with a
# byte so as to wake up the event source.
#
$SIG{CHLD} = sub { syswrite CHLDEXITWRITE, "1"; };

#
# Now, install a watch on the other end of the pipe. This will wake up in the # main loop when the signal handler has written to it. That's our signal to # reap all the processes we can, invoking the appropriate callbacks if any
# are registered.
#
Glib::IO->add_watch (fileno CHLDEXITREAD, 'in', sub {
        my $dummy;
        sysread CHLDEXITREAD, $dummy, 1;

        while (-1 != (my $pid = wait)) {
                print "reaped pid $pid\n";
                use Data::Dumper;
                print Dumper(\%callback);
                if ($callback{$pid}) {
                        print "invoking callback ... \n";
                        $callback{$pid}{callback}->($callback{$pid}{data});
                        __PACKAGE__->remove ($pid);
                }
        }
        1;
});

#
# And some public interface that walks and talks like Glib::ChildWatch would.
#
sub add {
        my ($class, $pid, $callback, $data) = @_;
        $callback{$pid} = { callback => $callback, data => $data };

        return $pid;
}

sub remove {
        my ($class, $pid) = @_;
        delete $callback{$pid};
}


package main;


my $pid = fork;
die "couldn't fork: $!\n" unless defined $pid;

if (!$pid) {
        sleep 1 + rand(2);
        exit 0;
}

my $loop = Glib::MainLoop->new;

Mup::ChildWatch->add ($pid, sub { print "whee\n"; $loop->quit });

$loop->run;

__END__


--
Doing a good job around here is like wetting your pants in a dark suit; you get a warm feeling, but no one notices.
  -- unknown





[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]