Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I would like to execute a child process and determine which line number that it is currently executing to mark its progress. I would expect it to work something like this:
if($pid = fork) { #PARENT while(waitpid($pid, &WNOHANG) != $pid) { #Somehow retrieve line number of child process while running print "Retrieved line number\n"; $mainform -> update; } } else { #CHILD exec(bin/tools/example.pl); # Execute child script print "DECLINED, fatal error spawning child script"; exit; }
Please let me know if you have any ideas on how make something like this work correctly. Thanks

2003-04-28 edit ybiC: <code> tags, indents

  • Comment on Is there any way of determining the current line number of a child process while it is running?
  • Download Code

Replies are listed 'Best First'.
•Re: Is there any way of determining the current line number of a child process while it is running?
by merlyn (Sage) on Apr 28, 2003 at 20:36 UTC
    On architectures that support it, you can assign to $0 to change the message visible in a ps. Coupling that with one of the process modules to extract the info, and maybe with a debugging hook in the child to update the value on statement boundaries, you might have a complete system there. But that's a bunch of "ifs". :)

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

Re: Is there any way of determining the current line number of a child process while it is running?
by BrowserUk (Patriarch) on Apr 28, 2003 at 21:55 UTC

    If your using *nix, instead of using fork, use my $pid = open( KID, '-|') or die $!; to kick of the child process. The child can then print __LINE__, "\n"; at strategic points (or every line if you wish) and the parent reads from <STDIN> (Updated: Thanks runrig) <KID> to get the line numbers.

    See the "Safe pipe opens" section of perlipc for examples.

    I couldn't get this to work under Win32 and tried pipe as an alternative, which works except I cannot work out how to get the parent process to detect that the child has closed the pipe? Maybe someone else knows the trick for this?

    #! perl -sw use strict; use IO::Handle; pipe(FROM_CHILD, TO_PARENT) or die $!; TO_PARENT->autoflush(1); if( my $pid = fork ) { #parent while( <FROM_CHILD> ) { chomp; print "\rChild is processing line:$_ "; } print 'Child appears to be finished.'; ## This never reached? close FROM_CHILD; } else { for (1..10) { print TO_PARENT __LINE__, $/; select undef, undef, undef, 0.0 +5; print TO_PARENT __LINE__, $/; select undef, undef, undef, 0.0 +5; print TO_PARENT __LINE__, $/; select undef, undef, undef, 0.0 +5; print TO_PARENT __LINE__, $/; select undef, undef, undef, 0.0 +5; } close TO_PARENT; }

    Examine what is said, not who speaks.
    1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
    2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
    3) Any sufficiently advanced technology is indistinguishable from magic.
    Arthur C. Clarke.
      Thanks guys. I appreciate your help with this. I've been playing around with your idea of piping the line number through a filehandle, but I don't see anyway of doing it without modifying the child script to print __LINE__ every single time. I suppose that I could append that to each and every line of the child script via a filehandle and then execute it. Thanks,

        I can see how adding print __LINE__ to the end of each line of your scripts would be a time consuming, boring and repetative task... Hang on, that sounds like an ideal application for a computer:)

        Install Filter::Simple, put a copy of the following snippet into .../site/lib/Filter/LineTrace.pm

        package Filter::LineTrace; use Filter::Simple; FILTER_ONLY code => sub { s[;\s*$][;print STDERR __LINE__;]mg; }, ; # Replace the next line with 1; print STDERR 'Filter::LineTrace loaded';

        And add use Filter::LineTrace; to the top of your scripts and each line that ends in a /;\s*\n/ will now print its line number to STDERR.

        As-is, the line numbers don't always match the lines exactly, but they are only usually out by 1 if at all, and not every line ends in a semicolon, so they don't all get traced, but it may be enough for your purpose.

        If not, you could add addition substitutions to catch those that end in { or } etc. as required.


        Examine what is said, not who speaks.
        1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
        2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
        3) Any sufficiently advanced technology is indistinguishable from magic.
        Arthur C. Clarke.
Re: Is there any way of determining the current line number of a child process while it is running?
by runrig (Abbot) on Apr 28, 2003 at 20:57 UTC
Re: Is there any way of determining the current line number of a child process while it is running?
by perlplexer (Hermit) on Apr 28, 2003 at 21:01 UTC
    You could do this in a variety of ways, depending on how ugly you want it to get. ;)

    - Open a pipe to the child process and make child report its status via the pipe
    - Create a file and make the child process write its status to this file (probably the most portable)
    - Use shared memory; e.g., IPC::Shareable::*
    - Sockets

    --perlplexer

    Update: fixed a typo
Re: Is there any way of determining the current line number of a child process while it is running?
by hv (Prior) on May 02, 2003 at 04:47 UTC

    Getting a bit uglier, here's a hack that takes a rather different approach: it uses 'gdb' to attach to the process with the given pid, and queries perl's internal structures.

    Use with caution. :)

    #!/usr/bin/perl -w my $perl = $^X; my $pid; my $tmpfile = "/tmp/attachperl-$$"; if (@ARGV == 1) { ($pid) = @ARGV; } elsif (@ARGV == 2) { ($perl, $pid) = @ARGV; } else { die "Usage: $0 [ /path/to/perl ] <pid>\n"; } # construct the gdb script open F, ">$tmpfile" or die "$tmpfile: $!\n"; print F <<GDB; p ((COP*)PL_curcop)->cop_line p ((XPVGV*)((COP*)PL_curcop)->cop_filegv->sv_any)->xgv_name quit GDB close F; my $call = "gdb -q -x $tmpfile $perl $pid 2>/dev/null"; my @data = map /^\$\d+ = (.*)$/ ? $1 : (), `$call`; my $line = $data[0] =~ /^\d+$/ ? $data[0] : 'unknown'; my $file = $data[1] =~ /^0x\w+ "_<(.*)"$/ ? $1 : 'unknown'; print "pid $pid: line $line of file '$file'\n"; unlink $tmpfile;

    A brief explanation: PL_curcop is a variable used in perl to hold details about the current execution state, including the last seen line number and a reference to the glob for the file the code came from. Note that occasionally PL_curcop may be null, in which case this'll just print "unknown". Also, when the execution is inside code evalled from a string, it'll show something like:

    pid 9256: line 1 of file '(eval 1)'
    .. which may not be supremely useful if your code spends a lot of time in such things.

    I should stress that this is a quick hack - doing it properly would at the very least involve using a safer approach for temporary files. I'm also not sure how recent a version of gdb is required for this, though it works ok for me with "GNU gdb 5.0rh-5 Red Hat Linux 7.1".

    Hope it helps,

    Hugo