Ошибка 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 ответ
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) очередь для входящих соединений работает немного по-другому, см. Вышеупомянутую статью.