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

Hi Monks,

I'm having a problem with a script which opens many filehandles. I'm on linux and my /proc/sys/fs/file-max is set to 340,000 and the system has only 10,000 files open when starting the script. I've cut it down to a small reproducable example:
#!/usr/bin/perl -w use strict; use Socket; use BSD::Resource; setrlimit(RLIMIT_NOFILE, 100000, 100000) or die "Cannot set rlimit - $ +!"; open OLDOUT, ">&STDOUT"; for my $max qw/1000 2000 4000 8000 16000/ { open STDOUT, ">&", \*OLDOUT or die "reset STDOUT - $!"; my @fh; my $count = 0; while ($count < $max){ open my $fh, ">/tmp/temp.$count" or die "open $count - + $!"; push @fh, $fh; $count++; } print "opened $count files\n"; my ($in, $out); my $buf; open STDOUT, ">/dev/null" or die "redirect - $!"; socketpair( $in, $out, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die + $!; open STDOUT, ">&=", fileno($out) or die ">&= fileno(fd) - $!"; open STDOUT, ">&=", $out or die ">&= fd - $!"; open STDOUT, ">&", \*OLDOUT or die "reset2 STDOUT - $!"; print "no problem with $count!\n"; } exit;


It generates the following output for me:

opened 1000 files no problem with 1000! opened 2000 files no problem with 2000! opened 4000 files >&= fd - Illegal seek at lotsoffiles.pl line 23.


I can't see any difference in the relevant snippet from strace perl lotsoffiles.pl 2>&1 |perl -ne 'print if /\((1|2),|dup/', which says:
write(1, "opened 2000 files\n", 18opened 2000 files dup2(2004, 1) = 1 fcntl(1, F_SETFD, 0) = 0 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff2b62da20) = -1 ENOTTY (I +nappropriate ioctl for device) lseek(1, 0, SEEK_CUR) = 0 dup2(2005, 1) = 1 dup(2005) = 2006 dup2(2006, 2005) = 2005 fcntl(1, F_SETFD, 0) = 0 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff2b62da20) = -1 EINVAL (I +nvalid argument) lseek(1, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) dup2(2005, 1) = 1 fcntl(1, F_SETFD, 0) = 0 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff2b62da20) = -1 EINVAL (I +nvalid argument) lseek(1, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) dup(3) = 2006 dup2(2006, 1) = 1 fcntl(1, F_SETFD, 0) = 0 dup(3) = 4 write(1, "no problem with 2000!\n", 22no problem with 2000! dup2(4, 1) = 1 fcntl(1, F_SETFD, 0) = 0 fstat(4004, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0 write(1, "opened 4000 files\n", 18opened 4000 files dup2(4004, 1) = 1 fcntl(1, F_SETFD, 0) = 0 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff2b62da20) = -1 ENOTTY (I +nappropriate ioctl for device) lseek(1, 0, SEEK_CUR) = 0 dup2(4005, 1) = 1 dup(4005) = 4006 dup2(4006, 4005) = 4005 fcntl(1, F_SETFD, 0) = 0 ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff2b62da20) = -1 EINVAL (I +nvalid argument) lseek(1, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) write(2, ">&= fileno(fd) - Illegal seek at"..., 57>&= fileno(fd) - Ill +egal seek at lotsoffiles.pl line 23.

Is this a bug?? This is perl, v5.8.8 built for x86_64-linux-gnu-thread-multi on Debian etch

Thanks!!

Replies are listed 'Best First'.
Re: perl bug? illegal seek with dup and many filehandles
by Utilitarian (Vicar) on Oct 30, 2008 at 15:07 UTC
    while you fd limit may be sufficient you may have a ulimit in the shell that the script is called from, run
    ulimit -n
    and if it's too low run
    ulimit -n $bigNumber
      ulimit -n 100000 has no effect - it changes the same thing as the setrlimit() call
Re: perl bug? illegal seek with dup and many filehandles
by Illuminatus (Curate) on Oct 30, 2008 at 16:49 UTC
    It is not an fd-limit issue. Using '>&=' reuses the file descriptor, so it is not increasing the number. You should probably show the whole strace output after the print of 'opened 4000 files'. Your parsing of strace may not be showing the system call actually triggering the die. Also, can you run:
    #!/usr/bin/perl -w use strict; use Socket; open OLDOUT, ">&STDOUT"; for (my $i=0;$i<3;$i++) { open STDOUT, ">&", \*OLDOUT or die "reset STDOUT - $!"; my ($in, $out); my $buf; open STDOUT, ">/dev/null" or die "redirect - $!"; socketpair( $in, $out, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die + $!; printf STDERR "sock is $out file %d\n", fileno ($out); open STDOUT, ">&=", fileno($out) or die ">&= fileno(fd) - $!"; print "test1\n"; open STDOUT, ">&=", $out or die ">&= fd - $!"; print "test2\n"; open STDOUT, ">&", \*OLDOUT or die "reset2 STDOUT - $!"; }
    It works OK for me, but I am using a 32-bit 5.8.6.
      Your test works for me on 32 and 64 bit (I originally found the problem using socketpair() :-). The original script also fails for me using perl, v5.8.8 built for i486-linux-gnu-thread-multi. 32bit strace working with 2000 files:

      and the same process failing with 4000 files:
Re: perl bug? illegal seek with dup and many filehandles (PERLIO_MAX_REFCOUNTABLE_FD)
by almut (Canon) on Oct 30, 2008 at 19:48 UTC

    I can replicate the issue with 5.8.x, but not with 5.10.0. The exact limit seems to be 2048 (try something like for my $max (2035..2050) {...} to verify yourself).  A bit of digging in the 5.8.8 sources suggests this might have to do with a constant in perlio.c:

    #define PERLIO_MAX_REFCOUNTABLE_FD 2048

    which would make PerlIOUnix_dup() fail if fd >= PERLIO_MAX_REFCOUNTABLE_FD:

    PerlIO * PerlIOUnix_dup(pTHX_ PerlIO *f, PerlIO *o, CLONE_PARAMS *param, int fl +ags) { PerlIOUnix *os = PerlIOSelf(o, PerlIOUnix); int fd = os->fd; if (flags & PERLIO_DUP_FD) { fd = PerlLIO_dup(fd); } if (fd >= 0 && fd < PERLIO_MAX_REFCOUNTABLE_FD) { f = PerlIOBase_dup(aTHX_ f, o, param, flags); if (f) { /* If all went well overwrite fd in dup'ed lay with the du +p()'ed fd */ PerlIOUnix_setfd(aTHX_ f, fd, os->oflags); return f; } } return NULL; }

    In Perl 5.10.0, the respective number of refcountable file descriptors is dynamically resized as needed...

    (In case you really want to know, just modify the value, recompile perl, and try again to test the hypothesis :)

Re: perl bug? illegal seek with dup and many filehandles
by ig (Vicar) on Oct 30, 2008 at 21:11 UTC

    I have found two ways around the limit (with thanks to almut):

    1. Run perl with environment variable PERLIO set to :stdio
    2. Recompile perl with PERLIO_MAX_REFCOUNTABLE_FD in perlio.c set to a suitably large number

    Running perl with PERLIO set to :stdio makes perl use standard IO libraries instead of its own. This avoids the hard coded limit on file descriptor reference counts in perl's IO routines but note the warning in perlio.c:

    /* This file contains the functions needed to implement PerlIO, which * is Perl's private replacement for the C stdio library. This is used * by default unless you compile with -Uuseperlio or run with * PERLIO=:stdio (but don't do this unless you know what you're doing) */

    I recompiled perl with PERLIO_MAX_REFCOUNTABLE_FD set to 32768 and your test script ran to successful completion. Tests on the recompiled perl ran ok.

      Thanks for this solution -- I ran into it & didn't waste much time, thanks to you.
Re: perl bug? illegal seek with dup and many filehandles
by oko1 (Deacon) on Oct 30, 2008 at 17:30 UTC
    ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff2b62da20) = -1 EINVAL (Invalid argument)

    I suspect that the ioctl() you're calling is invalid for the x86_64 architecture. The completely malaprop file descriptor (SNDCTL_TMR_TIMEBASE doesn't have much to do with duping filehandles) certainly implies that this is the case; so does the reported success on the 32-bit machine.


    --
    "Language shapes the way we think, and determines what we can think about."
    -- B. L. Whorf
Re: perl bug? illegal seek with dup and many filehandles
by ig (Vicar) on Oct 30, 2008 at 19:47 UTC

    In my tests with perl 5.8.8 on CentOS 5.2, open with ">&=" is failing as soon as or whenever the file descriptor being opened is 2048 or greater. It appears to be failing in perl because strace does not show failed system calls.