2

我在 mod_perl 下运行 perl 代码,它使用 Net::LDAP 模块连接到 openldap 服务器 slapd。

我正在尝试像这样设置连接超时:

my $ldap = Net::LDAP->new($server, timeout => 120); 

但是当 slapd 负载很重时,我会在大约 20 秒后连接尝试超时。

Net::LDAP 使用 IO::Socket 和 IO::Select 来实现其连接处理,特别是 IO::Socket 中的这段代码(注意我添加了一些额外的调试代码):

sub connect {
    @_ == 2 or croak 'usage: $sock->connect(NAME)';
    my $sock = shift;
    my $addr = shift;
    my $timeout = ${*$sock}{'io_socket_timeout'};
    my $err;
    my $blocking;

    my $start = scalar localtime;
    $blocking = $sock->blocking(0) if $timeout;
    if (!connect($sock, $addr)) {
    if (defined $timeout && ($!{EINPROGRESS} || $!{EWOULDBLOCK})) {
        require IO::Select;

        my $sel = new IO::Select $sock;

        undef $!;
        if (!$sel->can_write($timeout)) {
        $err = $! || (exists &Errno::ETIMEDOUT ? &Errno::ETIMEDOUT : 1);
        $@ = "connect: timeout";
        }
        elsif (!connect($sock,$addr) &&
                not ($!{EISCONN} || ($! == 10022 && $^O eq 'MSWin32'))
            ) {
        # Some systems refuse to re-connect() to
        # an already open socket and set errno to EISCONN.
        # Windows sets errno to WSAEINVAL (10022)
                my $now = scalar localtime;
        $err = $!;
        $@ = "connect: (1) $! : start = [$start], now = [$now], timeout = [$timeout] : " . Dumper(\%!);
        }
    }
        elsif ($blocking || !($!{EINPROGRESS} || $!{EWOULDBLOCK}))  {
        $err = $!;
        $@ = "connect: (2) $!";
    }
    }

    $sock->blocking(1) if $blocking;

    $! = $err if $err;

    $err ? undef : $sock;
}

我看到这样的日志输出:

connect: (1) Connection timed out : start = [Tue Jun 19 14:57:44 2012], now = [Tue Jun 19 14:58:05 2012], timeout = [120] : $VAR1 = {
          'EBADR' => 0,
          'ENOMSG' => 0,
<snipped>
          'ESOCKTNOSUPPORT' => 0,
          'ETIMEDOUT' => 110,
          'ENXIO' => 0,
          'ETXTBSY' => 0,
          'ENODEV' => 0,
          'EMLINK' => 0,
          'ECHILD' => 0,
          'EHOSTUNREACH' => 0,
          'EREMCHG' => 0,
          'ENOTEMPTY' => 0
        };
 : Started attempt at Tue Jun 19 14:57:44 2012

20 秒连接超时从何而来?

编辑:我现在找到了罪魁祸首:/proc/sys/net/ipv4/tcp_syn_retries,默认设置为 5,重试 5 次大约需要 20 秒。 http://www.sekuda.com/overriding_the_default_linux_kernel_20_second_tcp_socket_connect_timeout

4

1 回答 1

2

更新:有些内核是这样的

简短的回答是,一些 Linux 内核对 connect() 施加了 20 秒的超时。 这是一个错误

请注意,链接的sekuda显然是模棱两可的:默认值tcp_syn_retries(5) 和重试退避将给出远大于 20 秒的超时。上面链接的错误讨论中给出了缺失的细微差别。

原始答案

尝试升级。

connectIO::Socket 版本 1.34(例如 perl 5.16)中的sub,select()用于写入错误的 s 套接字。getsockopt()然后使用/SO_ERROR检查错误的套接字是否存在真正的错误情况。

我怀疑您遇到了TCP '软错误'(例如,一个 ICMP 主机不时无法访问)。但是,您的 IO::Socket 版本错过了要点,因为它从不查看 SO_ERROR。

如果升级不能解决问题,那么正确的解决方法是在 IO::Socket::connect 内部确定逻辑以执行 Linux connect(2) 手册页建议的操作,即在非阻塞、connect()ing 套接字之后检查 SO_ERROR select()s可写

廉价的解决方法

与此同时,类似...

# untested!
use Errno;

...

my $relative_to = 120; 
my $absolute_to = time() + $relative_to;

TRYCONN: {
  $ldap = Net::LDAP->new($server, timeout => $relative_to);
  if (! $ldap and $!{ETIMEDOUT}) {
    $rel_to = $absolute_to - time();
    redo TRYCONN if $relative_to > 0;
  }
}

die "Aaaaargh" unless $ldap;

...或类似的东西应该可以解决问题。

于 2012-06-19T19:05:45.433 回答