Ошибка CLOSED при параллельном установлении большого количества соединений с gen_tcp (ошибка?)

При попытке установить большое количество TCP-соединений параллельно я наблюдаю странное поведение, которое я считаю потенциальной ошибкой в gen_tcp,

Сценарий заключается в том, что сервер прослушивает порт с несколькими одновременными получателями. С клиента я устанавливаю соединение, звоня gen_tcp:connect/3 После этого я отправляю сообщение "Ping" на сервер и жду в пассивном режиме ответа "Pong". При выполнении вызовов get_tcp:connect/3 последовательно все работает нормально, в том числе для большого количества соединений (я тестировал до ~ 28000).

Проблема возникает при попытке установить множество параллельных соединений (в зависимости от машины от ~75 до нескольких сотен). В то время как большинство соединений все еще устанавливаются, некоторые соединения терпят неудачу с closed ошибка в gen_tcp:recv/3, Странно то, что эти соединения не прервались раньше, звонки gen_tcp:connect/3 а также gen_tcp:send/2 оба были успешными (т.е. вернулись ok). На стороне сервера я не вижу подходящего соединения для этих "странных" соединений, т.е. gen_tcp:accept/1, Насколько я понимаю, успешное 'get_tcp:connect/3' должно привести к совпадению принятых соединений на стороне сервера.

Я уже подал отчет об ошибке, там вы можете найти более подробное описание и минимальный пример кода, чтобы продемонстрировать проблему. Мне удалось воспроизвести проблему на Linux и Mac OS X и с разными версиями Erlang.

Мои вопросы здесь:

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

1 ответ

Решение

TCP 3-х стороннее рукопожатие Client Server

  connect()│──┐          │listen()
           │  └──┐       │
           │      SYN    │
           │        └──┐ │
           │           └▶│   STATE
           │          ┌──│SYN-RECEIVED
           │       ┌──┘  │
           │   SYN-ACK   │
           │ ┌──┘        │
   STATE   │◀┘           │
ESTABLISHED│──┐          │
           │  └──┐       │
           │     └ACK    │
           │        └──┐ │   STATE
           │           └▶│ESTABLISHED
           ▽             ▽

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

В Linux фактически есть две очереди для входящих соединений. Когда сервер получает запрос на соединение (SYN пакет) и переходы в состояние SYN-RECEIVEDэто соединение находится в SYN очередь. Если соответствующий ACK Получено, соединения помещаются в очередь принятия для использования приложением. {backlog, N} (по умолчанию: 5) опция gen_tcp:listen/2 определяет длину очереди доступа.

Когда сервер получает ACK в то время как очередь приема заполнена ACK в основном игнорируется и нет RST отправлено клиенту. Существует тайм-аут, связанный с SYN-RECEIVED состояние: если нет ACK будет получено (или проигнорировано, как в данном случае), сервер повторно отправит SYN-ACK, Затем клиент отправляет ACK, Если приложение использует запись из очереди приема до максимального количества SYN-ACK повторные попытки достигнуты, сервер в конечном итоге обработает один из дубликатов ACKs и переход в состояние ESTABLISHED, Если достигнуто максимальное количество попыток, сервер отправит RST клиенту для сброса соединения.

Возвращаясь к поведению, наблюдаемому при запуске множества соединений параллельно. Объяснение состоит в том, что очередь приема на сервере заполняется быстрее, чем наше приложение потребляет принятые соединения. gen_tcp:connect/3 звонки на стороне клиента возвращаются успешно, как только получит первый SYN-ACK, Соединения не сбрасываются немедленно, потому что сервер повторяет попытку SYN-ACK, Сервер не сообщает об этих соединениях как об успешных, потому что они все еще находятся в состоянии SYN-RECEIVED,

В производной системе BSD (включая Mac OS X) очередь для входящих соединений работает немного по-другому, см. Вышеупомянутую статью.

Другие вопросы по тегам