установка времени ожидания подключения для Net::LDAP

У меня есть код perl, работающий под mod_perl, который подключается к серверу openldap slapd с помощью модуля Net::LDAP.

Я пытаюсь установить тайм-аут подключения следующим образом:

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


person Chris Card    schedule 19.06.2012    source источник
comment
Является ли предполагаемый тайм-аут всегда около 20 секунд, или это может быть разным? На какой ОС и версии работает клиент?   -  person pilcrow    schedule 19.06.2012
comment
+1 кстати. Хорошо устранил неполадки, прежде чем приехать сюда.   -  person pilcrow    schedule 19.06.2012
comment
Тайм-аут всегда составляет около 20 секунд (и я видел это при непосредственном использовании IO::Socket). Это на Centos 6, 64 бит, версия IO::Socket 1.31 исходит из perl-5.10.1-115.el6.x86_64. Возможно обновление возможно, но это решение не зависит от меня.   -  person Chris Card    schedule 20.06.2012
comment
Я только что нашел это: sekuda.com/   -  person Chris Card    schedule 20.06.2012
comment
Я обновил свой ответ ссылкой на более глубокое обсуждение этого поведения.   -  person pilcrow    schedule 21.06.2012


Ответы (1)


Обновление: некоторые ядра такие

Короткий ответ заключается в том, что некоторые ядра Linux налагают 20-секундный тайм-аут на функцию connect(). Это ошибка.

Обратите внимание, что связанная секуда явно неоднозначна: значение по умолчанию tcp_syn_retries (5) и отсрочка повторной попытки даст тайм-аут намного больше 20 секунд. Отсутствующий нюанс указан в обсуждении ошибок, указанном выше.

Оригинальный ответ

Попробуйте обновить.

Подпрограмма connect в IO::Socket версии 1.34 (например, в perl 5.16), сокеты select()s для записи и для ошибок. Затем сокеты с ошибками проверяются с помощью getsockopt()/SO_ERROR на наличие истинной ошибки.

Я подозреваю, что вы получаете программную ошибку TCP (например, сообщение ICMP хост недоступен время от времени). Однако ваша версия IO::Socket упускает суть, потому что она никогда не смотрит на SO_ERROR.

Если обновление не решает проблему, правильным решением будет добавить логику внутри IO::Socket::connect к делать то, что предлагает справочная страница Linux connect(2), то есть проверять SO_ERROR после неблокирующего, connect()ing сокета 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;

... или подобное должно помочь.

person pilcrow    schedule 19.06.2012
comment
Кстати, я не вижу IO::Socket 1.34 на CPAN. - person Chris Card; 20.06.2012
comment
Я пробовал с IO::Socket 1.34 сейчас, но я вижу то же самое поведение. Вызов IO::Select::select() в этой версии завершается успешно, но второй вызов connect() по-прежнему терпит неудачу с тайм-аутом соединения, и тайм-аут по-прежнему составляет около 20 секунд. - person Chris Card; 20.06.2012