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

I'm seeing some real weirdness in Perl under Windows.

Apparently select only works with sockets (see 67963), so instead of using open("x |") I'm trying to use socketpair, POSIX::dup2, and exec. It's working fine, but when I dup2 one end of the socketpair to STDOUT in the child process, somehow STDOUT in the parent process gets connected to the same place, after a brief delay during which it works normally.

Here's seltest.pl:

#!perl -w use strict; use IO::Select; use Socket; use POSIX; socketpair(SOCK_READ,SOCK_WRITE,AF_UNIX,SOCK_STREAM,PF_UNSPEC) or die "Couldn't create socket pair: $!\n"; print "STDOUT print 1.\n"; if (!defined(my $f = fork())) { die "Fork error: $!\n"; } elsif (!$f) { # child close(SOCK_READ); warn "CHILD PID: $$\n"; close(STDOUT) or die "Couldn't close STDOUT: $!\n"; POSIX::dup2(fileno(SOCK_WRITE),1); sleep(100) while(1); } # parent close(SOCK_WRITE); warn "Parent is PID $$\n"; # We can't reliably print to STDOUT after this point (at some point, # it will start going to the socket) #sleep(5); print "STDOUT print 2.\n"; my $sel = IO::Select->new; $sel->add(\*SOCK_READ) or die "Couldn't add pipe to IO::Select: $!\n"; print "STDOUT print 3.\n"; while (1) { print "STDOUT print 4.\n"; warn "DEBUG: select on ",$sel->count," handles.\n"; my @ready = $sel->can_read(10); warn "Parent DEBUG: ",scalar(@ready)," handles readable.\n"; print "STDOUT print 5.\n"; foreach my $rh (@ready) { my $line=<$rh>; warn "Parent read $rh: $line"; } sleep(3); }
The output from this on Windows is:
STDOUT print 1. Parent is PID 1996 STDOUT print 2. STDOUT print 3. STDOUT print 4. DEBUG: select on 1 handles. CHILD PID: -1860 Parent DEBUG: 0 handles readable. DEBUG: select on 1 handles. Parent DEBUG: 1 handles readable. Parent read GLOB(0x1b2cee8): STDOUT print 5. DEBUG: select on 1 handles. Parent DEBUG: 1 handles readable. Parent read GLOB(0x1b2cee8): STDOUT print 4.

As you can see, after a short time the parent starts reading its own input on the handle that should be coming from the child. If you uncomment the sleep(5), then all output from the parent goes to the wrong place. On Unix it behaves as I expected, with all output going to the parent's STDOUT and select nevere returning anything.

Anybody have any idea what's going on here? Or is this just what I should expect when I try to program Perl on Windows as if it were Unix?

Replies are listed 'Best First'.
Re: Windows weirdness after fork, dup2
by Thelonius (Priest) on Mar 05, 2004 at 20:01 UTC
    Instead of dup2(), use this
    open STDOUT, ">&SOCK_WRITE" or die "STDOUT: $!\n";
    The "fork" in ActivePerl is not a real fork, it just creates a separate thread. I think by using dup2() you are bypassing the logic that makes them work like separate processes with separate file descriptors.
      Thanks Thelonius, but no luck. If I use:
      open STDOUT, ">&SOCK_WRITE" or die "STDOUT: $!\n"; warn "fileno(STDOUT)=",fileno(STDOUT),"\n";
      I find that it's not using the right file descriptor:
      fileno(STDOUT)=3
      so when I exec a new process it doesn't have the file handles hooked up right. For example, if I change the infinite sleep loop to actually run something:
      exec('perl','-e','$|=1; while(1) { sleep(5); print q:The time is now : +,time,qq:\n:; };') or die "exec error: $!\n";
      the output from the child goes to the console, not the socket. With dup2, it goes to the socket.
        What you can do is set up the child to forward the output of the command to the main parent through the socket:
        # child close(SOCK_READ); open CMD, "ipconfig|" or die "ipconfig: $!\n"; print SOCK_WRITE while <CMD>; exit(0);
Re: Windows weirdness after fork, dup2
by crabbdean (Pilgrim) on Mar 06, 2004 at 13:31 UTC
    Okay, if I'm not mistaken your "if" statement has problems. What is the difference between saying "if fork is not defined to $f" and "if $f is not defined". You fork, and then completely bypasses it. Your outputs show this - 1, then Parent equals PID (which will show because it IS the parent) and then as a normal program its shows 2,3,4. The if statement is just jumped! You don't even test within that fork. Basically you are creating a successful fork and then ignoring it with your "if" statement by having two negations to the fork. Hence why later on its starts reading itself. The parent isn't reading itself, it's just being a parent and outputting to STDOUT like any other program. I bet if you cut out the whole "if" bit you'd get the same result.

    Where you have written your child process is where you parent should be, IF you have the "if" statement correct.

    Hope that helps

    Dean

    Programming these days takes more than a lone avenger with a compiler. - sam
    A Standard for the Transmission of IP Datagrams on Avian Carriers

      The = in that line is the assignment of the return value from fork to the variable $f. fork returns undef if it fails, 0 in the child, and the child's PID in the parent. So if the fork fails, there's only one process, and it die's. Otherwise, the child starts executing inside the elsif (!$f) block, and the parent executes the rest of the program. You can see this by having the different chunks of code print out PIDs.

      Plus, it works under Unix, which indicates that the code is generally correct.