develooper Front page | perl.perl5.porters | Postings from March 2000

Build Report for Perl v5.6.0-RC3 on i386.openbsd-thread v2.6

Thread Next
From:
Tom Christiansen
Date:
March 21, 2000 16:02
Subject:
Build Report for Perl v5.6.0-RC3 on i386.openbsd-thread v2.6
Message ID:
19350.953683330@chthon
Well, here's my research project for the day.

The first thing you need to do to make OpenBSD build a pthreaded
Perl is get enough compiler flags right.  This is not in the hints
files.  You need -pthread all over the place:

    % grep pthread config.sh
    ccflags='-fno-strict-aliasing -I/usr/local/include -pthread'
    cppflags='-fno-strict-aliasing -I/usr/local/include -pthread'
    d_old_pthread_create_joinable='define'
    d_oldpthreads='undef'
    d_pthread_yield='define'
    i_pthread='define'
    ldflags='-pthread -L/usr/local/lib'
    libswanted='sfio socket bind inet nsl nm ndbm gdbm dbm db malloc dl dld ld sun m c cposix posix ndir dir sec ucb bsd BSD PW x iconv pthread'
    old_pthread_create_joinable=''

Now, doing that, there were three test failures.  I'll explain them all.

    0) threads
    1) openpid
    2) fork

====================================================================

The threads failure is test 19, which fails because Perl regex 
library is not thread safe.  I cannot fix this.  All I can say
is don't play $1 games diddingly between duelling threads.  Of course,
this is indeterminable, so life is imperfect.  But we knew that.

====================================================================

The next problem, the openpid failure, you can fix trivially by,
when compiling under threads, using fork not instead of BSD's normal
vfork.  Without threads, however, vfork is fine.  Note that the
openbsd hints say this:

    # Currently, vfork(2) is not a real win over fork(2) but this will
    # change starting with OpenBSD 2.7.
    usevfork='true'

But with pthreads, for some strange reason, you can't do that and
all work.  I don't know why.  If you read
/usr/src/lib/libc_r/uthread/uthread_vfork.c, you'll see that it
just does this:

    int
    vfork(void)
    {
	    return (fork());
    }

Anyway, that's easy to fix.

====================================================================

Now the hard one.  This is tests 10 and 11 of fork.t, which is
really testing waitpid.  That's where we lose.

Here's the failing test.  I added full paths just in case.  The other
is like this but without the exec.  Same issue.

    $| = 1;
    $\ = "\n";
    my $echo = '/bin/echo';
    if ($pid = fork) {
	waitpid($pid, 0);
	print "parent got $?"
    }
    else {
	exec($echo, "foo");
    }

Now, if you run without threads, it succeeds.  If you run with
threads, it fails.  And now I know why.  If you adjust the print,
you'll see it clearly:

    print "parent got $? $!"

That now prints

    parent got -1 Interrupted system call

Bingo.  The short story is that under threads and there alone, the
wait4 you get is being EINTRd by the SIGCHLD.  This does not normally
happen.

The test is easily fixable this way:

    $| = 1;
    $\ = "\n";
    my $echo = '/bin/echo';
    if ($pid = fork) {
	while (waitpid($pid,0) == -1 && $! =~ /Interrupted system call/) { }
	print "parent got $?"
    }
    else {
	exec($echo, "foo");
    }

And now it works.  But one should *not* have to do that.  We are not
being sigrestarted.  

Here's more detail.  First, here's the kernel trace without threading:

  2923 perl     CALL  sigprocmask(0x1,0)
  2923 perl     RET   sigprocmask 0
  2923 perl     CALL  fork
  2923 perl     RET   fork 93/0x5d

    93 perl     RET   fork 0
    93 perl     CALL  getpid
    93 perl     RET   getpid 93/0x5d
    93 perl     CALL  execve(0xa8db0,0xa8dc0,0xdfbfda1c)
    93 perl     NAMI  "/bin/echo"
    93 echo     EMUL  "native"
    93 echo     RET   execve 0
    ....
    93 echo     CALL  write(0x1,0x8000,0x4)
    93 echo     GIO   fd 1 wrote 4 bytes
       "foo
       "
    93 echo     RET   write 4
    93 echo     CALL  exit(0)
  2923 perl     CALL  wait4(0x5d,0xdfbfd908,0,0)
  2923 perl     RET   wait4 93/0x5d

And here it is with the threading:

  1465 perl     CALL  fork
  1465 perl     RET   fork 19545/0x4c59
  1465 perl     CALL  wait4(0x4c59,0xdfbfd8c4,0,0)
 19545 perl     RET   fork 0
 19545 perl     CALL  getpid
 19545 perl     RET   getpid 19545/0x4c59
 19545 perl     CALL  execve(0xb6e50,0xb6e60,0xdfbfda18)
 19545 perl     NAMI  "/bin/echo"
 19545 echo     CALL  write(0x1,0x8000,0x4)
 19545 echo     GIO   fd 1 wrote 4 bytes
       "foo
       "
 19545 echo     RET   write 4
 19545 echo     CALL  exit(0)
  1465 perl     PSIG  SIGCHLD caught handler=0x401d1ad4 mask=0x0 code=0x0
  1465 perl     RET   wait4 -1 errno 4 Interrupted system call
  1465 perl     CALL  sigreturn(0xdfbfd74c)
  1465 perl     RET   sigreturn JUSTRETURN

See what happens?  Apparently, in the thread version, it's losing
the SA_RESTART flag in our sigaction, so waitpid returns EINTR when
the SIGCHLD hits it.  Perl's built-in system() spins on that, but
Perl's waitpid() does not (which makes sense; you want complete
system accessibility).  I suspect a bug in lurking in the icky
/usr/src/lib/libc_r/uthread/uthread_wait4.c but am unclear.

Here's the only sigaction in the non-threaded version:

  2923 perl     CALL  sigaction(0x14,0,0xdfbfd8b0)
  2923 perl     RET   sigaction 0

I cann't trace down the flags, because that last one is a pointer:

     sigaction(int sig, const struct sigaction *act, struct sigaction *oact);

where all the good stuff is, but Perl is doing this there:

    Sighandler_t
    Perl_rsignal(pTHX_ int signo, Sighandler_t handler)
    {
	struct sigaction act, oact;

	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
    #ifdef SA_RESTART
	act.sa_flags |= SA_RESTART; /* SVR4, 4.3+BSD */
    #endif
    #ifdef SA_NOCLDWAIT
	if (signo == SIGCHLD && handler == (Sighandler_t)SIG_IGN)
	    act.sa_flags |= SA_NOCLDWAIT;
    #endif
	if (sigaction(signo, &act, &oact) == -1)
	    return SIG_ERR;
	else
	    return oact.sa_handler;
    }

Meanwhile, the threaded version has *THIRTY-THREE* calls to sigaction.
I can't tell you why.  Well, yes I can, I can see lots of stuff in
uthread_wait4.c, but that doesn't explain the following.  

Here's the very curious thing.  If in the parent, you add a call
to Perl system()

    $| = 1;
    $\ = "\n";
    my $echo = '/bin/echo';
    if ($pid = fork) {
	system("true");  # or false, doesn't matter
	waitpid($pid, 0);
	print "parent got $?"
    }
    else {
	exec($echo, "foo");
    }

Then something manages to fix your signal state.  Perl's system
does this:

	rsignal_save(SIGINT, SIG_IGN, &ihand);
        rsignal_save(SIGQUIT, SIG_IGN, &qhand);
        do {
            result = wait4pid(childpid, &status, 0);
        } while (result == -1 && errno == EINTR);
        (void)rsignal_restore(SIGINT, &ihand);
        (void)rsignal_restore(SIGQUIT, &qhand);

which of course dodges the problem.  Since it also fixes 
the EINTR problem we otherwise get later, I think that calling our
own sigaction (which rsignal does), fixes it.

That's all I know.

--tom

Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About