[svn:ponie] rev 301 - trunk/perl
[email protected] 27 Jun 2005 07:30:26 -0000
| Newsgroups | perl.ponie.changes |
|---|---|
| Message-ID | <[email protected]> |
Author: nicholas
Date: Mon Jun 27 00:30:26 2005
New Revision: 301
Modified:
trunk/perl/perl.c
Log:
Merge 24984 and 14986 from blead (avoid hangs with
DEBUG_LEAKING_SCALARS_FORK_DUMP by closing all file descriptors other than
the contol socket, and pass the debugging output descriptor across as needed)
Modified: trunk/perl/perl.c
==============================================================================
--- trunk/perl/perl.c (original)
+++ trunk/perl/perl.c Mon Jun 27 00:30:26 2005
@@ -98,6 +98,15 @@ char *nw_get_sitelib(const char *pl);
# ifdef I_SYS_WAIT
# include <sys/wait.h>
# endif
+# ifdef I_SYSUIO
+# include <sys/uio.h>
+# endif
+
+union control_un {
+ struct cmsghdr cm;
+ char control[CMSG_SPACE(sizeof(int))];
+};
+
#endif
#ifdef __BEOS__
@@ -458,39 +467,95 @@ void
Perl_dump_sv_child(pTHX_ SV *sv)
{
ssize_t got;
- int sock = PL_dumper_fd;
- SV *target;
+ const int sock = PL_dumper_fd;
+ const int debug_fd = PerlIO_fileno(Perl_debug_log);
+ union control_un control;
+ struct msghdr msg;
+ struct iovec vec[2];
+ struct cmsghdr *cmptr;
+ int returned_errno;
+ unsigned char buffer[256];
- if(sock == -1)
+ if(sock == -1 || debug_fd == -1)
return;
PerlIO_flush(Perl_debug_log);
- got = write(sock, &sv, sizeof(sv));
+ /* All these shenanigans are to pass a file descriptor over to our child for
+ it to dump out to. We can't let it hold open the file descriptor when it
+ forks, as the file descriptor it will dump to can turn out to be one end
+ of pipe that some other process will wait on for EOF. (So as it would
+ be open, the wait would be forever. */
+
+ msg.msg_control = control.control;
+ msg.msg_controllen = sizeof(control.control);
+ /* We're a connected socket so we don't need a destination */
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = vec;
+ msg.msg_iovlen = 1;
+
+ cmptr = CMSG_FIRSTHDR(&msg);
+ cmptr->cmsg_len = CMSG_LEN(sizeof(int));
+ cmptr->cmsg_level = SOL_SOCKET;
+ cmptr->cmsg_type = SCM_RIGHTS;
+ *((int *)CMSG_DATA(cmptr)) = 1;
+
+ vec[0].iov_base = (void*)&sv;
+ vec[0].iov_len = sizeof(sv);
+ got = sendmsg(sock, &msg, 0);
if(got < 0) {
- perror("Debug leaking scalars parent write failed");
+ perror("Debug leaking scalars parent sendmsg failed");
abort();
}
- if(got < sizeof(target)) {
- perror("Debug leaking scalars parent short write");
+ if(got < sizeof(sv)) {
+ perror("Debug leaking scalars parent short sendmsg");
abort();
}
- got = read(sock, &target, sizeof(target));
+ /* Return protocol is
+ int: errno value
+ unsigned char: length of location string (0 for empty)
+ unsigned char*: string (not terminated)
+ */
+ vec[0].iov_base = (void*)&returned_errno;
+ vec[0].iov_len = sizeof(returned_errno);
+ vec[1].iov_base = buffer;
+ vec[1].iov_len = 1;
+
+ got = readv(sock, vec, 2);
if(got < 0) {
perror("Debug leaking scalars parent read failed");
+ PerlIO_flush(PerlIO_stderr());
abort();
}
- if(got < sizeof(target)) {
+ if(got < sizeof(returned_errno) + 1) {
perror("Debug leaking scalars parent short read");
+ PerlIO_flush(PerlIO_stderr());
abort();
}
- if (target != sv) {
- perror("Debug leaking scalars parent target != sv");
- abort();
+ if (*buffer) {
+ got = read(sock, buffer + 1, *buffer);
+ if(got < 0) {
+ perror("Debug leaking scalars parent read 2 failed");
+ PerlIO_flush(PerlIO_stderr());
+ abort();
+ }
+
+ if(got < *buffer) {
+ perror("Debug leaking scalars parent short read 2");
+ PerlIO_flush(PerlIO_stderr());
+ abort();
+ }
+ }
+
+ if (returned_errno || *buffer) {
+ Perl_warn(aTHX_ "Debug leaking scalars child failed%s%.*s with errno"
+ " %d: %s", (*buffer ? " at " : ""), (int) *buffer, buffer + 1,
+ returned_errno, strerror(returned_errno));
}
}
#endif
@@ -569,10 +634,12 @@ perl_destruct(pTHXx)
}
if (!child) {
/* We are the child */
-
const int sock = fd[1];
const int debug_fd = PerlIO_fileno(Perl_debug_log);
int f;
+ const char *where;
+ /* Our success message is an integer 0, and a char 0 */
+ static const char success[sizeof(int) + 1];
close(fd[0]);
@@ -580,51 +647,120 @@ perl_destruct(pTHXx)
with interesting hangs, where the parent closes its end of a
pipe, and sits waiting for (another) child to terminate. Only
that child never terminates, because it never gets EOF, because
- we also have the far end of the pipe open. */
+ we also have the far end of the pipe open. We even need to
+ close the debugging fd, because sometimes it happens to be one
+ end of a pipe, and a process is waiting on the other end for
+ EOF. Normally it would be closed at some point earlier in
+ destruction, but if we happen to cause the pipe to remain open,
+ EOF never occurs, and we get an infinite hang. Hence all the
+ games to pass in a file descriptor if it's actually needed. */
f = sysconf(_SC_OPEN_MAX);
if(f < 0) {
- perror("Debug leaking scalars sysconf failed");
- abort();
+ where = "sysconf failed";
+ goto abort;
}
while (f--) {
if (f == sock)
continue;
- if (f == debug_fd)
- continue;
close(f);
}
while (1) {
SV *target;
- ssize_t got = read(sock, &target, sizeof(target));
+ union control_un control;
+ struct msghdr msg;
+ struct iovec vec[1];
+ struct cmsghdr *cmptr;
+ ssize_t got;
+ int got_fd;
+
+ msg.msg_control = control.control;
+ msg.msg_controllen = sizeof(control.control);
+ /* We're a connected socket so we don't need a source */
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = vec;
+ msg.msg_iovlen = sizeof(vec)/sizeof(vec[0]);
+
+ vec[0].iov_base = (void*)⌖
+ vec[0].iov_len = sizeof(target);
+
+ got = recvmsg(sock, &msg, 0);
if(got == 0)
break;
if(got < 0) {
- perror("Debug leaking scalars child read failed");
- abort();
+ where = "recv failed";
+ goto abort;
}
if(got < sizeof(target)) {
- perror("Debug leaking scalars child short read");
- abort();
+ where = "short recv";
+ goto abort;
+ }
+
+ if(!(cmptr = CMSG_FIRSTHDR(&msg))) {
+ where = "no cmsg";
+ goto abort;
+ }
+ if(cmptr->cmsg_len != CMSG_LEN(sizeof(int))) {
+ where = "wrong cmsg_len";
+ goto abort;
+ }
+ if(cmptr->cmsg_level != SOL_SOCKET) {
+ where = "wrong cmsg_level";
+ goto abort;
+ }
+ if(cmptr->cmsg_type != SCM_RIGHTS) {
+ where = "wrong cmsg_type";
+ goto abort;
+ }
+
+ got_fd = *(int*)CMSG_DATA(cmptr);
+ /* For our last little bit of trickery, put the file descriptor
+ back into Perl_debug_log, as if we never actually closed it
+ */
+ if(got_fd != debug_fd) {
+ if (dup2(got_fd, debug_fd) == -1) {
+ where = "dup2";
+ goto abort;
+ }
}
sv_dump(target);
+
PerlIO_flush(Perl_debug_log);
- /* Write something back as synchronisation. */
- got = write(sock, &target, sizeof(target));
+ got = write(sock, &success, sizeof(success));
if(got < 0) {
- perror("Debug leaking scalars child write failed");
- abort();
+ where = "write failed";
+ goto abort;
}
- if(got < sizeof(target)) {
- perror("Debug leaking scalars child short write");
- abort();
+ if(got < sizeof(success)) {
+ where = "short write";
+ goto abort;
}
}
_exit(0);
+ abort:
+ {
+ int send_errno = errno;
+ unsigned char length = (unsigned char) strlen(where);
+ struct iovec failure[3] = {
+ {(void*)&send_errno, sizeof(send_errno)},
+ {&length, 1},
+ {(void*)where, length}
+ };
+ int got = writev(sock, failure, 3);
+ /* Bad news travels fast. Faster than data. We'll get a SIGPIPE
+ in the parent if we try to read from the socketpair after the
+ child has exited, even if there was data to read.
+ So sleep a bit to give the parent a fighting chance of
+ reading the data. */
+ sleep(2);
+ _exit((got == -1) ? errno : 0);
+ }
+ /* End of child. */
}
PL_dumper_fd = fd[0];
close(fd[1]);