Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

exec sometimes changes pid

by doom (Deacon)
on Aug 02, 2009 at 21:54 UTC ( [id://785284]=perlquestion: print w/replies, xml ) Need Help??

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

I sometimes use the idiom where you fork off a child process, and then have the child exec to run some other code, and then later have the parent kill the child's pid to get rid of it. I've seen some odd behavior on one of my machines, though, where the "exec" changes the pid of the child, so the return from fork is no longer the pid that I need to kill. The following code runs differently on my workstation and my laptop, despite the fact that they're both AMD64 machines running ubuntu jaunty, using perl 5.10.0:

use warnings; use strict; $|=1; my $cmd = q{ perl -e 'print "child: id after exec: $$\n"; print "child: sees processes...\n " , grep { m/perl/ } `ps ax` , "\n"; ' }; print "parent: pre-fork id $$\n"; if ( my $pid = fork ) { # this is parent code print "parent: post-fork id $$\n"; print "parent: return from fork is: $pid\n"; sleep 3; print "parent: still here, with id $$\n"; print "parent: sees processes...\n " , grep { m/perl/ } `ps ax` , "\n"; } else { # this is child code die "cannot fork: $!" unless defined $pid; print "child believes it is: $$\n"; exec( $cmd ) # replaces child with a new perl: pid shouldn't change, + right? }

On the laptop, the child pid stays the same after the exec, but on the workstation it increases by one. There's very little difference between the two machines... the laptop is a single-processor (Turion) and the workstation is a dual (a dual-Opteron).

They are running slightly different kernel versions, though, the workstation is "2.6.24-16-generic" and the laptop is "2.6.27-9-generic".

Any ideas what this might be about? If you've got a minute, could you please run the code I posted above and report back on how it behaves for you?

On my workstation (the dual-Opteron) the results are something like this:

parent: pre-fork id 27516 child believes it is: 27517 child: id after exec: 27518 parent: post-fork id 27516 parent: return from fork is: 27517 child: sees processes... 27516 pts/1 S+ 0:00 /usr/bin/perl /home/doom/bin/testes-fork +itude 27517 pts/1 S+ 0:00 sh -c perl -e 'print "child: id after ex +ec: $$\n";? print "child: se 27518 pts/1 R+ 0:00 perl -e print "child: id after exec: $$\ +n";? print "child: sees proc parent: still here, with id 27516 parent: sees processes... 27516 pts/1 R+ 0:00 /usr/bin/perl /home/doom/bin/testes-fork +itude
On my laptop (the Turion) the reults are like so:
parent: pre-fork id 7253 child believes it is: 7254 parent: post-fork id 7253 parent: return from fork is: 7254 child: id after exec: 7254 child: sees processes... 7253 pts/3 S+ 0:00 /usr/bin/perl /home/doom/bin/testes-forki +tude 7254 pts/3 R+ 0:00 perl -e print "child: id after exec: $$\n +";? print " parent: still here, with id 7253 parent: sees processes... 7253 pts/3 S+ 0:00 /usr/bin/perl /home/doom/bin/testes-forki +tude 7254 pts/3 Z+ 0:00 [perl] <defunct>

Replies are listed 'Best First'.
Re: exec sometimes changes pid
by moritz (Cardinal) on Aug 02, 2009 at 22:16 UTC
    It might related to the fact that you don't pass a simple command to exec, but rather a string with shell meta characters.

    So when you call exec it overrides the current (child) program with the shell from /bin/sh which interprets the string for you, which in turns starts another perl script which prints a PID (which is not the same as the one you forked off originally).

    Maybe your other system is smart enough to detect that it doesn't really need to spawn a shell, but can execvp directly the new perl process. I'd guess that you'll get the same result on both systems if you use the LIST form of exec instead.

      Some preliminary trials show you're probably right (though I still don't see what could be different on these two systems... maybe a different version of some IPC::* module?). In any case, I was experimenting with the list form of exec for other reasons, and I finally made it through the gotchas:

      my @cmd = ('perl', '-e print "child: id after exec: $$\n"; print "child: sees processes...\n " , grep { m/perl/ } `ps ax` , "\n"; ', ); print "parent: pre-fork id $$\n"; if ( my $pid = fork ) { # this is parent code print "parent: post-fork id $$\n"; print "parent: return from fork is: $pid\n"; sleep 3; print "parent: still here, with id $$\n"; print "parent: sees processes...\n " , grep { m/perl/ } `ps ax` , "\n"; } else { # this is child code die "cannot fork: $!" unless defined $pid; print "child believes it is: $$\n"; exec {$cmd[0]} @cmd; }

      The gotchas:

      • You use the command name first ("indirect object" syntax, ala printing to a file handle), *and* have the command name again inside the list. The second one is what appears in the process listing, the first is the program that's actually run.
      • From the shell, you typically do a perl -e 'some code', but if you're going around the shell, those single quotes on the string fed to "-e" just get in the way: @cmd = ('perl', q{-e some code }).

Re: exec sometimes changes pid
by ssandv (Hermit) on Aug 03, 2009 at 01:02 UTC
    I think the explanation moritz gives above is right. Should you need a workaround, you can probably send the entire process group a signal (other than KILL, obviously) after putting a handler in to ignore that signal in the parent.

    Warning: drylabbed code ahead. Not tested, though I do have a little experience using signals in Perl.
    my $pgrp_id=getpgrp(0) # gets the process group id of # the current process { # scoping is important! Otherwise # that signal will always be ignored # by the parent, which is probably bad. # I picked SIGINT, you might prefer something # else. local $SIG{'INT'}='IGNORE'; kill 2, -$pgrp_id; # - means it's a group id. }
    But, of course, this assumes you don't have other stuff running in the process group that you need to leave alive, besides the parent.

    This does raise the question of why you need to kill it via a signal, of course.

      I've done this sort of thing for a number of different reasons, usually involving driving emacs code from perl. For example, I've written test code in perl that spins off an emacs window, and captures an image of it. The parent perl process then kills the emacs, and examines the image to see if it looks okay (has the right number of colors, for example).

      The work around that I had in mind was to run the child with a munged program name, then do a "ps ax" listing, and grep for the munged name. That would get me the actual pid of the child to kill. Your idea looks a bit simpler (if a little less surgical).

      Assuming that the pid returned from fork is the shell's, then why not do the opposite? Set SIGHUP to 'DEFAULT' then kill the shell. Assuming that the shell command is not doing something like 'nohup', then that should kill the shell's child, without needing `ps`.

        If killing the pid for the intermediate shell Just Worked, then I wouldn't be asking this question. I must say I don't understand why it doesn't work...

        It didn't occur to me to explicitly set SIGHUP to 'DEFAULT', maybe that would've made a difference.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://785284]
Approved by moritz
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (5)
As of 2024-03-29 12:33 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found