Удаленка для jenkins+selenide+selenoid+allure+docker спецов на 2-3 часа в день. 100% remote! Присоединиться к проекту

Интересная бага с веб-драйвером. Может кто сталкивался. (Java + Selenium + webdriver (chrome))

jenkins
java
webdriver
testng
selenium
Теги: #<Tag:0x00007fedb874ba78> #<Tag:0x00007fedb874b898> #<Tag:0x00007fedb874b6e0> #<Tag:0x00007fedb874b500> #<Tag:0x00007fedb874b370>

(Djuise) #1

Добрый день. Обнаружил интересную штуковину. Есть авто тесты которые работали еще до меня. Тут начал я их поддерживать и решил немного ускорить процесс. Эти тесты очень часто ходят на одну и ту же страницу по многу раз, то есть время перехода по всем ссылкам занимает достаточно времени. Решил я сэкономить время, просто передав параметры ссылке CTR + ENTER (открыть ссылку в новом окне). Тем самым я просто прыгаю между окнами и не трачу время на весь проход по ссылкам. Но после этого начали падать тесты с эксепшином:

org.openqa.selenium.WebDriverException:
java.net.BindException: Address already in use: connect
Command duration or timeout: 0 milliseconds

Немного разобравшись, я так понял, что webdriver обращается к локальному порту, который занят. И тут не могу понять почему так. Логичнее предположить, что это баг системы, но воспроизводиться только когда включаю эти новые тесты, получается это какой-то баг вебдрайвера? (НО падает этот эксепшин после прохождения моих тестов и уже ближе к концу всех тестов (как буд-то порты лочатся ближе к финалу).

Все запускается на дженкинсе на удаленной машине (Windows 7). Когда направлял эти тесты на свою машину, то все было тоже ок.

По факту получается этот баг воспроизводится на одной одной машине с определенными тестами (с остальными все норм). И еще, если после того как эти тесты по падали, запустить совершенно другие тесты, то они тоже будут переодически падать.

Используется Java + TestNG + Selenium + Selenide


(Alexandr D ) #2

Я конечно не уверен, но очень похоже на ошибку SocketException в C#.

В ваших тестах, возможно, есть дыра, которая быстро сжирает свободные порты.

Если драйвер не может открыть новую сессию из-за того, что нет свободных портов - это как раз тот самый случай.

Лечится это следующим образом:
Проверьте, сколько у вас портов может быть использовано.
В cmd команда:

netsh int ipv4 show dynamicport tcp

Должно быть что-то типа (у меня уже изменено):

Протокол tcp Динамический диапазон портов
---------------------------------
Начальный порт      : 1025
Число портов : 64510

Если у вас не так, то делается следующее:

  1. Увеличивается количество портов, которые можно юзать. Подробнее тут: https://support.microsoft.com/ru-ru/help/929851/the-default-dynamic-port-range-for-tcp-ip-has-changed-in-windows-vista

После этого можете проверить. А так же в ходе тестов можно в cmd вводить команду для отслеживания сколько портов сожрано уже: (Она мне помогла вычислить в своё время узкое место)

netstat -a -n | find "TIME_WAIT" /c

Ну и конечно же, можно изменить жизнь порта после его закрытия (т.е. после использования он ещё по умолчанию вроде 3 или 4 минуты в винде живёт со статусом TIME_WAIT). Можно это изменить на 30 сек.
Здесь:

HKEY_LM\System\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay = 30 (decimal) 
  1. Важно - надо найти в коде этих тестов место, которое жрёт свободные порты. Это может быть какой-нибудь цикл без PollingTime, и он может за секунд 5 сожрать все порты.
    Убрать это недоразумение из кода и не допускать больше в будущем.

(Djuise) #3

Спасибо большое. Касательно портов, значения отличаются, но по ссылке, что Вы дали вроде как пишут, что так и должно быть :slight_smile: . По поводу мониторинга портов, не совсем понимаю как это сделать. Тесты идут с большой скоростью и как понять, в какой момент и какой порт был заюзан и из-за чего? Это не совсем понял как мониторить и что это дает, то что они лочатся, наверное да, но как понять из-за чего? Заранее спасибо.


(Alexandr D ) #4

Любой метод, который вы используете в драйвере - отсылает запрос на поднятый селениумом сервер.
Каждый запрос - это использование одного свободного порта.
Если вы напишите бесконечный цикл без каких-либо ожиданий, и будете например опрашивать какой-то элемент, существует ли он - то ваши порты быстро начнут заканчиваться.
С этим и призваны бороться увеличение количества портов и уменьшение времени их жизни после смерти.

Данную команду

netstat -a -n | find "TIME_WAIT" /c

Можно постоянно писать руками в cmd, пока ваши тесты запущены. С периодичностью 2-3 секунды, например.

Вам не важно, какой именно порт был заюзан, вам важно количество этих портов, которые пребывают в состоянии TIME_WAIT.

Либо запускайте только один тест и жмакайте команду в cmd. Когда количество занятых портов резко и быстро начнет расти, значит тест находится в том месте, где дыра.
Ставьте паузу и смотрите по коду, где это место.


(Alexandr D ) #5

Ради интереса я провёл эксперимент.

Вот кусок кода:

do
            {
                try
                {
                    DriverManager.Driver.FindElement(By.XPath("fake"));
                }
                catch (Exception)
                {
                }

            } while (true);

Вот количество умерших, но ещё не воскревших портов до входа в цикл:

U:\>netstat -a -n | find "TIME_WAIT" /c
2

А вот спустя 10 секунд работы цикла:

U:\>netstat -a -n | find "TIME_WAIT" /c
1041

Так что это дело такое… Надо следить за такими утечками)

А если тесты бегают параллельно в 10+ потоков - то, сами понимаете, одна такая дыра способна всё сломать.

Именно по этой причине я написал методы расширения, и всегда использую их, в них вшит pollingtime, для избежания таких проблем.
Выглядит вроде вполне нормально и универсально, применяется к любому объекту вообще, а не только к драйверу, ибо оборачивание там где надо голых вызовов методов драйвера всегда чревато допущением ошибок.

DriverManager.Driver.Wait().Until(_ => _.FindElement(By.XPath("fake")));

Упс, сорян, у вас java же, не в ту степь меня понесло. Но там есть функциональные интерфейсы, тоже вполне годится для подобных дел. Ну или велком в Selenide :slight_smile:


(Djuise) #6

Порты растут, но не сильно отличается от других тестов. А в случае, если тест не найдет вебэлемент, он же должен свалиться (циклы не использую в тесте). По этому не совсем понимаю где искать( в том плане, что если что-то не нашло, то должно свалиться.


(Alexandr D ) #7

Где искать это я вам не подскажу.

Но факт, что эта та самая ошибка - 99%.


(Djuise) #8

А можно еще раз, для глупых, получается, что что-то ест порты очень быстро?
Просто сейчас првоел эксперемент, запустил только этот тест и мониторил порты

netstat -a -n | find “TIME_WAIT” /c

Больше 1500 не переваливало, раз упало при 1200, раз на 1500 не падало.
Запустил, паралельно несколько тестов, 15 минут оно гоняло их и никаких ошибок не было (при том, что кол-во портов в ожидании переваливало за 5000). Как-то не логично, раз упало при 1200, а при паралельном, где было 5к портов умерших оно все норм его прогняло. Почему так то?

Циклов ну вот вообще нет. Я лезу на сайт, открываю нужную ссылку, меняю значения в нужных местах, переключаюсь между вкладками и жму на кнопки. Никаких трай кетчей с циклом не использую). Может я что-то не так понял или что-то не то ищу?

И еще, эта ошибка валится в разных местах, не обязательно на новых тестах, бывет и на старых. А вот если отключить новые, то на старых падать не будет. И это происходит ближе к концу выполнения (а новые тесты проганяются ± в начале). В общем нет никакой прямой зависимости, чтоб можно было сказать с уверенностью, что вот тут он сейчас упадет. Он бывает вообще не падает, бывает постоянно падает и это дизориентирует сильно)


(Djuise) #9

И еще такой вопрос, может ли это быть связано с тем, что я открываю дополнительную вкладку? (Все тесты до этого работают прямолинейно, то есть если нужно что-то изменять и возвращаться обратно, то весь путь будет проходится в этом окне, там нет дополнительных табок). Или это вряд-ли?


(Alexandr D ) #10

Не, вкладки тут не причем. Это связано с портами, даже гугл об этом говорит (https://stackoverflow.com/questions/4708649/java-net-bindexception-address-already-in-use-when-trying-to-do-rapid-socket)

На машине, где вы запускаете тесты, что возвращает команда?

netsh int ipv4 show dynamicport tcp

(Djuise) #11

Start Port : 49152
Number of Ports : 16384

Но на моей, локальной машине, значения точно такие же. Все, кроме времени жизни

HKEY_LM\System\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay = 30

На моей машине, такой параметр вообще отстутствует. Может это является причиной того, что локально не репродюсится?

А касательно портов, как уже писал, что бывало падало, когда портов в Тайм_Вейт было 1200, а сейчас, например, больше 5 тысяч (так как запустил паралельные тесты), но ничего не валится. Это странно.


(Alexandr D ) #12

Попробуйте увеличить количество портов до 50000 хотя бы.
И посмотрите, будет воспроизводиться или нет.
Если будет, значит надо копать в другом направлении. Но количество портов всё равно на машине для прогонов я бы держал увеличенным и с 30 секундым ожиданием после закрытия.

Конечно. Только должно быть наоборот. Не должно воспроизводиться там, где этот параметр указан и равен 30. :slight_smile:


(Djuise) #13

Ну тогда я в тупике). Если я не ошибаюсь, то это является максимальным количеством портов. И все равно, не понимаю, почему при 1200 валится, а при 5000 нет (порты в ТАЙМ_ВЕЙТ)


(Djuise) #14

А может быть такое, что система дает порт дважды. Ну типа 1й вкладке его выдает и 2й по ошибке, просто все делается так быстро, что идет какой-то рассинхрон? (хотя опять же, падает все не обязательно в этом тесте)


(Alexandr D ) #15

У вас используется 16384 порта всего.

У меня используется

Число портов : 64510

Сравните разницу… :slight_smile:

Нет, такого не бывает. А вот то что она не может дать - это да.


(Djuise) #16

В общем, проблема решилась увеличением кол-ва портов. И да, тесты с 2мя вкладками никак не влияли. Причину так и не нашел. Может ли быть такое, что просто машине просто их не хватало? И вытекает еще 1 вопрос. Вдруг настанет момент, когда будут открыты все порты, но ошибка будет валиться, или такое вряд-ли возможно в принципе?

П.С. Огромное спасибо за помощь!


(Alexandr D ) #17

Если у вас на машине ничего кроме автотестов не крутится и вы предоставили порядка 65000 портов для использования, и уменьшили в реестре время пребывания портов в состоянии TIME_WAIT до 30 секунд - то шансов, что такое произойдёт почти 0, только если у вас будет 100-200 потоков одновременно тесты гонять, и-то далеко не факт, надо проверять.

Я же вам с самого первого сообщения написал, в чём причина. Как вы её могли не найти, если она в портах? :slight_smile:


(Djuise) #18

В портах то да) но хотелось понять причину ж :slight_smile:
А можно TIME_WAIT уменьшить, например, до 10 секунд?


(Alexandr D ) #19

Нет, 30 секунд это минимум.
Вы можете так же уменьшить ещё одно время, так называемые полузакрытые соединения.
Тоже до 30 сек.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpFinWait2Delay

(Alexandr D ) #20

Понимаете, мы с вами смотрели на порты, находящиеся только в состоянии TIME_WAIT.
Но помимо этого состояния, куча портов находится в других состояниях.

Возможно, в вашем случае, нужно смотреть на все занятые порты, если вы так уж хотите докопаться до истины.
Попробуйте в ходе тестов использовать такую команду:

netstat -a -n | find "TCP" /c

Она, правда, включает в себя порты LISTENING, но их не очень обычно много, можно пренебречь :slight_smile: