LINUX.ORG.RU

Ядро не отдаёт приложению содержимое TCP-пакета, пока не придёт следующий

 , , ,


1

8

Есть приложение, которое общается с сервером по TCP. Протокол представляет собой просто запрос-ответ, плюс ещё клиент и сервер отправляют друг другу пинги.

Клиент в бесконечном цикле дёргает read() из сокета, который переведёт в неблокирующий режим.

Проблема в том, что через некоторое время случается так, что клиент не вычитывает ответ, пока не придёт очередной пинг от сервера. То есть, read() всё это время возвращает -1, errno=EAGAIN.

В tcpdump видно, что ответ пришёл вовремя (сразу после запроса). А также видно, что в тот момент, когда клиент наконец-то вычитал, пришёл следующий пакет (пинг).

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

В какую сторону копать?


В пакете, данные из которого приложение получает только после пинга, посмотри, есть ли флаг PSH. Если его нет, ядро имеет право задерживать данные до тех пор, пока не получит либо достаточно данных в других пакетах для заполнения буфера приложения до конца, либо PSH.

Чтобы такого не было, копай в сторону FIONREAD и пытайся читать ровно столько, сколько доступно.

AEP ★★★★★
()
Ответ на: комментарий от vel

Речь идет о локалке от 100Мбит/с и хотя бы одном коммутаторе между хостами.

Тогда я живу в мире «локалок». Но задержки на ack порядка 1милли не видел ни разу.

Там и mtu 64k и контрольные суммы заголовков не считают и не проверяют.

Господин вообще 52 байта шлёт (если верить его постам) - MTU не при чём. Я бы подозревал кривое / экзотическое железо, и что нибудь в духе неправильно использующегося kernel bypass. Нам явно что-то недоговаривают.

ПыСы: минутные задержки это вообще клиника какая-то. И я не разу не сталкивался с тем чтобы read() не вычитывал того что ядро уже имеет.

bugfixer ★★★★★
()
Ответ на: комментарий от AEP

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

Бред же. На receiving end это роли вообще играть не должно.

Это TCP. Нет там понятия «достаточно данных». Или теперь по байтику вообще слать запрещено?

ПыСы. Все лайкнувшие меня откровенно разочаровали.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Если бы на receiving end это не влияло, на кой бы под него вообще выделяли место в пакете?

A sending TCP is allowed to collect data from the sending user and to send that data in segments at its own convenience, until the push function is signaled, then it must send all unsent data. When a receiving TCP sees the PUSH flag, it must not wait for more data from the sending TCP before passing the data to the receiving process.

Другое дело, что я без понятия, что такого надо в TCP-стеке клиента накрутить, чтобы он минутами не отдавал данные, отправленные без PSH, поэтому не верю, что дело в PSH на пакете с пингом. Но это ТСу легко будет проверить.

t184256 ★★★★★
()
Ответ на: комментарий от t184256

Если бы на receiving end это не влияло, на кой бы под него вообще выделяли место в пакете?

TCP это двусторонний протокол, обе стороны могут передавать данные (и даже делать это одновременно)

annulen ★★★★★
()
Ответ на: комментарий от annulen

Если сделать PSH в другую сторону, не «flush дальше», а «flush в ответ если есть че», то половина трафика в интернете состояла бы из таких «тянущих» PSH штоб побыстрее: https://imgflip.com/i/7eo8n8

t184256 ★★★★★
()
Ответ на: комментарий от t184256

на кой бы под него вообще выделяли место в пакете?

Из очевидного - forwarding, за исключением (возможно) самых примитивных его форм.

When a receiving TCP sees the PUSH flag, it must not wait for more data from the sending TCP before passing the data to the receiving process.

Да, там так написано. Вот только я затрудняюсь представить зачем и почему кто-то даже теоретически может захотеть принудительно / дополнительно буферизировать пакеты в kernel space вместо того чтобы отдавать их в user space «при первой возможности». Да, и я таки поискал подобную логику в линуксовом ядре - не нашёл.

поэтому не верю, что дело в PSH на пакете с пингом.

Согласен.

bugfixer ★★★★★
()
Ответ на: комментарий от vel

Для локалки это единицы милисекунд. Посмотри на rtt в «ss -i»

Вы там TCP_QUICKACK не включаете случаем? Это многое бы объясняло…

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от bugfixer

Нет, не включаю.

Тяжело быть теоретиком застрявшем в криокамере :)

Нет никакого смысла откладывать на 300 мс подтверждение принятых данных при условии пустых очередей и при использовании скоростных интерфейсов (с низким rtt).

В условиях 100Mбит/с, 300 мс - это вечность.

vel ★★★★★
()
Ответ на: комментарий от vel

Нет никакого смысла откладывать на 300 мс подтверждение принятых данных

Вы отдаёте себе отчёт что не имеет никакого смысла каждый пакет подтверждать?

В условиях 100Mбит/с, 300 мс - это вечность.

Это долго, да. Только откуда 100MBps? Я живу в мире от 10Gbit/s…

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от bugfixer

то долго, да. Только откуда 100MBps?

И, кстати, как Вы думаете, какого порядка величины задержки можно добиться от «packet in» до «packet out» померяных «on a wire»? Давайте - ваша ставка?

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Каждый _отдельный_ пакет есть смысл подтвердить, т.к. это будет поддерживать актуальный rtt.

А вот когда идет поток пакетов, то смысла подтверждать каждый пакет нет.

Слишком задерживать аск тоже плохо. Передающая сторона заполнит свою очередь передачи и будет ждать подтверждения или еще хуже, начнёт ретрансмиссии.

PS к проблеме ТС это не имеет отношения.

vel ★★★★★
()
Ответ на: комментарий от vel

А вот когда идет поток пакетов

И здесь мы подходим к самому главному…

то смысла подтверждать каждый пакет нет

Согласен. И так все и делают.

Слишком задерживать аск тоже плохо.

Тоже правда. Но задержки что я пока видел - 250-300 милли, в зависимости от операционки. Никогда ни в единицах.

bugfixer ★★★★★
()