我有在 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
最佳答案
更新:有些内核是这样的
简短的回答是,某些 Linux 内核对 connect() 施加 20 秒的超时。 This is a bug .
请注意链接的 sekuda显然是不明确的:tcp_syn_retries
的默认值 (5) 和重试退避将给出远大于 20 秒的超时。上面链接的错误讨论中给出了缺少的细微差别。
原始答案
尝试升级。
IO::Socket 版本 1.34 中的 connect
子项(例如,在 perl 5.16 中),select()
用于写入和 对于错误。然后使用 getsockopt()
/SO_ERROR 检查出错的套接字是否存在真正的错误情况。
我怀疑你得到的是 TCP 'soft error' (例如,ICMP 主机时常无法访问)。但是,您的 IO::Socket 版本错过了要点,因为它从不查看 SO_ERROR。
如果升级不能解决问题,那么正确的解决方法是将 IO::Socket::connect 内部的逻辑钉入 do what the Linux connect(2) man page suggests ,这是在非阻塞的 connect()
ing 套接字 select()
s 可写之后检查 SO_ERROR。
廉价的解决方法
与此同时,类似...
# 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;
...或类似的应该可以解决问题。
关于perl - 设置 Net::LDAP 连接超时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11102713/