LINUX.ORG.RU

O_NONBLOCK + write(сокет). Почему EWOULDBLOCK?

 ,


0

4

Я не слабо разбираюсь во внутреннем устройстве ОС, системному программированию не обучен, по этому адресую свой вопрос грамотным людям.

Допустим у меня сетевой сокет с флагом O_NONBLOCK. Для более широкого охвата примера возьмём два разных случая, пусть в одном из них записываемые в сокет данные отправляются получателю в виде датаграмм, а в другом в виде гарантированных TCP пакетов.

Собственно вопрос, что должно произойти под капотом write() на этом сокете там, на стороне операционной системы и/или библиотеки, что бы он вернул мне EWOULDBLOCK? Меня интересует конкретные вещи, не абстрактное типа «что-нибудь, что не позволит запись», а конкретно что там происходит и в каком виде это может явиться причиной EWOULDBLOCK.

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

Спасибо.

★★★

Достаточно конкретно?

what I’m unclear on is under what circumstances send() would throw an EWOULDBLOCK

When the sending-buffer (typically held by the OS, but, anyway, somewhere in the TCP/IP stack) is full and the counterpart hasn’t acknowledged any of the bits sent to it from the buffer yet (so the stack must retain everything in the buffer in case a resend is necessary).

Конечно же сразу будет вопрос про send и write, поэтому вот:

The Linux man page for write(2) states that EWOULDBLOCK would only be returned for a file descriptor that refers to a socket.

EAGAIN or EWOULDBLOCK The file descriptor fd refers to a socket and has been marked nonblocking (O_NONBLOCK), and the write would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities.

Отсюда.

alex0x08 ★★★
()

точно EWOULDBLOCK ты можешь словить на не BSD-шних стеках IP (в QNX или Solaris и кто там ещё http://www.openss7.org/streams.html)

в классическом streams - твои данные обрамятся в пакет и отправятся на уровень ниже. А тот уровень может отбрыкнуть от например проблем с памятью или от настроения бабушки. И в POSIX вызовах получишь EAGAIN или EWOULDBLOCK в зависимости от кто отбрыкнул - непосредственно следующий или кто-то за ним. Streams все давно вдоль и поперёк напеределаны, но современное поведение должно оставаться таким-же.

MKuznetsov ★★★★★
()

У сокета есть сэнд буфер. Его размер программист может задать с помощью опции сокета SO_SNDBUF. Системный вызов send добавляет данные в сэнд буфер. Ядро удаляет данные из сэнд буфера, когда получает от пира подтверждение о приёме. Если в буфере нет свободного места, send возвращает EWOULDBLOCK. Получить событие о появлении места в буфере можно с помощью poll POLLOUT. https://man7.org/linux/man-pages/man7/socket.7.html.

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

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

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

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

нет, нельзя. Ты указал место на стеке, и побежал дальше исполняться, и когда ядро возьмет данные из твоего стека, там будут кишки твоего дальнейшего исполнения.

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

«мне остаток? Или произойдёт что-то другое»

я конечно не настоящий сварщик, но думаю, что первый вызов заберёт часть твоего буфера и вернёт размер.

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

Хорошо, есть там буффер (правда не совсем понятно зачем

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

write() бесконечно будет возвращать мне EWOULDBLOCK

он возвращает сколько реально получилось записать.

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

На самом деле ссылка довольно неудачная - там обсуждают EWOULDBLOCK при записи в файл.

@normann, по поводу TCP Вам уже разжевали. С UDP всё немножко сложнее. Нужно понимать что в большинстве операционок EWOULDBLOCK и EAGAIN - это одно и тоже (та же numeric constant). Вот здесь обсуждают как так получилось. Как по мне - логически EAGAIN немножко более широкое понятие (временная ошибка in general), и решение уравнивать его с EWOULDBLOCK можно оспаривать. Но с точки зрения как это обрабатывать - ничего не меняется, так что в чём-то они правы. В реальности EAGAIN при записи в UDP socket Вы увидите только при наличии сигналов. По крайней мере я не вижу как это ещё может произойти. Надеюсь - разберётесь.

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

он возвращает сколько реально получилось записать.

Т.е. Вы имеете в виду, что это не будет ошибкой и он возьмется записать часть данных в пределах размера его буфера?

normann ★★★
() автор топика

Я не слабо разбираюсь во внутреннем устройстве ОС

Вообще не понятна мысль ТС. По идее тут очевидный ответ, нет места в буфере на отправку и при этом запрещена блокировка процесса, то возвращаем EAGAIN (он же EWOULDBLOCK) и выходим из системного вызова.

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

Т.е. Вы имеете в виду, что это не будет ошибкой и он возьмется записать часть данных в пределах размера его буфера?

Допустим мы хотим записать в файловый дескриптор 10 байт, а в буфере на отправку есть место только для 7 байт. Закидываем 7 байт в буфер и завершаем успешно системный вызов с информацией в коде возврата, что записали 7 байт. Если после этого сразу ещё раз вызвать write() чтобы дозаписать оставшиеся 3 байта, то получим EAGAIN(EWOULDBLOCK).

Для TCP сокетов всегда надо исходить из того, что write() вернет меньше байт, чем указал на запись.

Вот если бы это был файловый дескриптор regular файла, вот тогда можно было бы расчитывать на другое поведение.

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

Как по мне - логически EAGAIN немножко более широкое понятие (временная ошибка in general), и решение уравнивать его с EWOULDBLOCK можно оспаривать.

Все остальные временные ошибки имеют свои другие коды (ENOMEM ENOBUFS и всякие network unreachable). Для EAGAIN (при read/write) других причин кроме «подождите пока мы доделаем i/o» быть не может.

firkax ★★★★★
()

По литературе – можно почитать «UNIX Network Programming Volume 1» Стивенса. Только с поправкой на то, что это довольно старый текст и не про Linux. Но общие идеи там хорошо описаны.

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

Все остальные временные ошибки имеют свои другие коды (ENOMEM ENOBUFS и всякие network unreachable). Для EAGAIN (при read/write) других причин кроме «подождите пока мы доделаем i/o» быть не может.

Есть глубокая разница между (например) ENOMEM и EAGAIN - первое как правило так быстро не восстанавливается как второе, почти никогда.

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

Для TCP сокетов всегда надо исходить из того, что write() вернет меньше байт, чем указал на запись.

Совершенно согласен, но иногда намеренно делают иначе.

Один вызов write гарантированно запишет в сокет данные, переданные в этом write, без «перемешивания» с другими вызовами write из иных потоков.

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

(это пример из High Frequency Trading, это не рекомендуемый подход)

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

Обработка ENOMEM и EAGAIN - конечно разная, ведь у них разные коды. Я как раз и писал что всё «разное» - и так вынесено в другие коды, а в EAGAIN, кроме ожидания завершения операции, ничего нет.

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

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

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

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

Удачная запись в сокет ни в одном глазу не означает что peer получил данные.

но и предыдущая из-за закрытия сокета до того, как она полностью отправится из буфера

Всё правильно. Гуглите “graceful TCP socket shutdown”.

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

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

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

Из личного практического. Речь идет больше о rates (цены), но в случае ордеров ситуация похожа (если задержишься с отправкой, получишь Don’t know trade, поэтому можно считать, что и не отправлял).

HFT большой и разный, у меня такие подходы проходят. У кого-то это может быть неприемлемо.

blex ★★★
()