LINUX.ORG.RU

splice+pipe=EAGAIN

 ,


0

1

Добрый день

Делаю сплайс из неблокируемого сокета в пайп.
Псевдокод:


if(pipe(splice_pipe) < 0)
	{
		perror("pipe");
		return -1;
	}

pipe_size = fcntl(splice_pipe[0], F_GETPIPE_SZ))
...................
//При событии поступления данных на сокет
if((read = splice(
			sock_fd,
			NULL,
			splice_pipe[1],
			NULL,
			pipe_size - pipe_bytes_num,
			SPLICE_F_MOVE|SPLICE_F_NONBLOCK
			)) <0)
{
  perror("splice");
  return -1;
}

pipe_bytes_num += read;

Размер пайпа 65536 байт. При попытке заполнить пайп полностью splice выдает EAGAIN.
Ради шалости попробовала заведомо уменьшить на килобайт-другой значение переменной pipe_size чтобы не заполнять подзавязку - не дает результата. А вот если уменьншить pipe_size килобайт на 20, то EAGAIN не выскакивает.
Лишний пустой вызов splice очень напрягает, особенно часто это может быть если медленный читатель из пайпа, а писатель очень быстрый.

Код не могу привести полностью из-за размазанности по проекту.
Откуда берется EAGAIN, если допустить что в коде нет попыток переполнения пайпа и в буфере сокета есть данные?


Попробовала «недоиспользовать» 4096*4+ байт пайпа и при записи в него из сокета splice не выбрасывает EAGAIN вообще.
Ядро 3.2.0-89-generic-pae

nyka
() автор топика

если допустить что в коде нет попыток переполнения пайпа и в буфере сокета есть данные

Я бы начал с того, что проверил обе этих гипотезы. Первая часть легко проверяется через write/send (по вкусу) из dummy-буфера в сокет вместо splice. Вторая часть — recv с MSG_PEEK или вовсе ioctl(fd, FIONREAD, &int_variable).

Ну и докучи проверил бы, что pipe_size - pipe_bytes_num != 0. Косяк с большей вероятностью будет в твоем коде, нежели в ядре.

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

проверки на колво данных в пайпе есть
остальное щас попробую проверить

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

уменьшила размер пайпа с пом fcntl на 4096*4 байт и нет EAGAIN
. сказочно как-то

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

данных в избытке

А pipe в это время пустой по мнению FIONREAD? EAGAIN может означать как опустение sock_fd, так и переполнение pipe_fd. Надо понимать, что если splice вернул 0, то это однозначно EOF на fd_in, а если -1 + EAGAIN, то либо из fd_in нечего читать, либо в fd_out невозможно записать.

Я правильно понимаю, что прогресс передачи вообще останавливается, если пытаться передавать данные в pipe слишком большими блоками? Это звучит очень дико.

Кстати, а при выгребании данных из пайпа ты корректно уменьшаешь pipe_bytes_num?

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

возвращается -1 и EAGAIN в то время когда пайп не пустой. FIONREAD показывает при срабатывании:

try to read from socket to pipe 12753 bytes
splice_read_from_socket: Resource temporarily unavailable
FIONREAD: bytes in socket 45521 | bytes in pipe 52783

кусок лога в котором при попытке записать 12753 байт в пайп в котором уже 52783. fcntl показывает размер пайпа 65536.

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

Процесс передачи не прекращается. Вылетает EAGAIN при попытке заполнить пайп полностью, затем читатель освобождает немного места в пайпе и можно продолжать писать. Думаю что нужно сделать небольшой пример чтобы исследовать проблему. Суть в том что делается порожняковый сплайс возвращающий -1, первоначально именно это меня насторожило

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

а что должно вылетать если вы переполняете пайп ?

anonymous
()

read = splice(

pipe_size - pipe_bytes_num,

pipe_bytes_num += read

Тебе не кажется, что pipe_size - pipe_bytes_num в конце концов станет отрицательным?

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

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

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

Выше привела логи в котор FIONREAD показывает сколько данных в пайпе, сколько в сокете и сколько пыталась записать.

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

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

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

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

По результатам отпишусь тут, надеюсь что я где-то накосячила

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

приложение однопоточное, возможно, я ошиблась. 1 читатель и 1 писатель.

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

Извиняюсь, поправила тип данных

#include <iostream>
#include <stdio.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/ioctl.h>

using namespace std;

int setnonblock(int sock_fd)
{

	  int opts;

	  opts = fcntl(sock_fd,F_GETFL);
	  if (opts < 0)
	   return -1;

	  opts = (opts | O_NONBLOCK);
	  if (fcntl(sock_fd, F_SETFL, opts) < 0)
	   return -1;

return 0;
}


int main() {

	uint16_t port = 5555;

	int read_pipe[2];
	if(pipe(read_pipe))
	{
		perror("pipe creation");
		return 1;
	}

	uint32_t pipe_size;
	if((pipe_size = fcntl(read_pipe[0], F_GETPIPE_SZ)) < 0)
	{
		perror("get pipe size");
		return 1;
	}

	cout<<"\npipe_size="<<pipe_size<<endl;

	int sock_fd;
    if( (sock_fd = socket( AF_INET, SOCK_STREAM, 0 )) < 0)
    {
        perror( "Socket creation error" );
        return 1;
    }


	struct sockaddr_in server;

    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = INADDR_ANY;

    int ss_n=1;
    setsockopt(sock_fd , SOL_SOCKET, SO_REUSEADDR, (char *)&ss_n, sizeof(ss_n));

    if ( bind(sock_fd, ( const sockaddr* )&server , sizeof( server ) ) == -1 )
	{
		perror("Binding error");
		return 1;
	}

    if( listen( sock_fd, 1000 ) <0 )
    {
    	perror("Listen error");
        return 1;
    }

    int sock_fd_accepted = accept(sock_fd, 0, 0);
    cout<<"\naccepted"<<endl;

    if(setnonblock(sock_fd) < 0)
    {
    	perror("setnonblock");
    	return 1;
    }

    //
    int epoll_fd = epoll_create(1);
    int events_num;

    struct epoll_event events[1];

	struct epoll_event ev;
	ev.data.fd = sock_fd_accepted;
	ev.events = EPOLLIN;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd_accepted, &ev);

    int readed=0;
    uint32_t pipe_bytes_num=0;

    while(1)
    {
    	events_num = epoll_wait(epoll_fd, events, 1, -1);

    	for(int i = 0 ; i< events_num ; i++)
    	{

    		//Данные пришли
    		if(events[i].events & EPOLLIN)
    		{
    			cout<<"\n\nEPOLLIN============================="<<endl;
    			cout<<"\nПробуем записать в пайп "<<pipe_size - pipe_bytes_num<<" байт"<<endl;

    			//Делаем сплайс
    			if((readed = splice(
    					sock_fd_accepted,
    					NULL,
    					read_pipe[1],
    					NULL,
    					pipe_size - pipe_bytes_num,
    					SPLICE_F_MOVE|SPLICE_F_NONBLOCK
    					)) <0){

    				if(errno != EAGAIN)
    				{
    					cout<<"\nЧто-то пошло не так..."<<endl;
    					return 1;
    				}

    				//А тут уже EAGAIN и проблема с заполнением пайпа плностью
    				cout<<"\nА это EAGAIN..."<<endl;

    				int socket_bytes_num;
    				int pipe_bytes_num;

    				ioctl(sock_fd_accepted, FIONREAD, &socket_bytes_num);
    				ioctl(read_pipe[1], FIONREAD, &pipe_bytes_num);

    				cout<<"\nОсталось: байт в сокете "<<socket_bytes_num<<"|"<<" байт в пайпе "<<pipe_bytes_num<<endl;

    				return 1;
    			}

    			pipe_bytes_num += readed;
    			cout<<"\nЗаписали в пайп за текущую попытку "<<readed<<" байт"<<endl;
    			cout<<"\nВсего Записали в пайп  "<<pipe_bytes_num<<" байт"<<endl;


    			//Этот участок кода у меня не отрабатывает
    			if(pipe_size == pipe_bytes_num)
    			{
    				cout<<"\nВсе отлично, пайп заполнен полностью"<<endl;
    				return 1;
    			}
    		}
    	}

    }

	return 0;
}

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

собираем, запускаем и на порт 5555 шлем данные количеством больше размера пайпа (65536) чем-нибудь типа

curl -F 'data=@test.file' 127.0.0.1:5555

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

и никаких проблем

user@ubuntu64:~$ ./a.out

pipe_size=65536







accepted


EPOLLIN=============================

Пробуем записать в пайп 65536 байт

Записали в пайп за текущую попытку 211 байт

Всего Записали в пайп  211 байт


EPOLLIN=============================

Пробуем записать в пайп 65325 байт

Записали в пайп за текущую попытку 146 байт

Всего Записали в пайп  357 байт


EPOLLIN=============================

Пробуем записать в пайп 65179 байт

Записали в пайп за текущую попытку 16384 байт

Всего Записали в пайп  16741 байт


EPOLLIN=============================

Пробуем записать в пайп 48795 байт

Записали в пайп за текущую попытку 16384 байт

Всего Записали в пайп  33125 байт


EPOLLIN=============================

Пробуем записать в пайп 32411 байт

Записали в пайп за текущую попытку 16384 байт

Всего Записали в пайп  49509 байт


EPOLLIN=============================

Пробуем записать в пайп 16027 байт

Записали в пайп за текущую попытку 16027 байт

Всего Записали в пайп  65536 байт

Все отлично, пайп заполнен полностью
user@ubuntu64:~$

и да пука, кончай со своей макосью, затрахало форматировать текст после нее

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

хоть убей, на 2-х машинах такого не могла достичь. никакой макоси в глаза не видела
за пуку - респект, порадовало))

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

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

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

Linux 3.2.0-89-generic-pae #127-Ubuntu SMP Tue Jul 28 09:52:21 UTC 2015 i686 i686 i386 GNU/Linux
другая машина-дебиан восьмерка, тоже 32х

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

не, это эклипс. к виму не приспособлена

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

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

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

думаю банально пофиксили баг в новых ядрах всего то, есть еще бубунта 12 x32, хз какое там ядро, ну точно постарее чем на 17 бубунте

anonymous
()
Ответ на: комментарий от nyka
./a.out 

pipe_size=65536

accepted


EPOLLIN=============================

Пробуем записать в пайп 65536 байт

Записали в пайп за текущую попытку 211 байт

Всего Записали в пайп  211 байт


EPOLLIN=============================

Пробуем записать в пайп 65325 байт

Записали в пайп за текущую попытку 169 байт

Всего Записали в пайп  380 байт


EPOLLIN=============================

Пробуем записать в пайп 65156 байт

Записали в пайп за текущую попытку 32768 байт

Всего Записали в пайп  33148 байт


EPOLLIN=============================

Пробуем записать в пайп 32388 байт

Записали в пайп за текущую попытку 32388 байт

Всего Записали в пайп  65536 байт

Все отлично, пайп заполнен полностью

Debian 9, 4.9.88-1+deb9u1

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

uname -a

Linux 3.2.0-89-generic-pae #127-Ubuntu SMP Tue Jul 28 09:52:21 UTC 2015 i686 i686 i386 GNU/Linux
Вывод:
pipe_size=65536

accepted


EPOLLIN=============================

Пробуем записать в пайп 65536 байт

Записали в пайп за текущую попытку 296 байт

Всего Записали в пайп  296 байт


EPOLLIN=============================

Пробуем записать в пайп 65240 байт

Записали в пайп за текущую попытку 154 байт

Всего Записали в пайп  450 байт


EPOLLIN=============================

Пробуем записать в пайп 65086 байт

Записали в пайп за текущую попытку 16384 байт

Всего Записали в пайп  16834 байт


EPOLLIN=============================

Пробуем записать в пайп 48702 байт

Записали в пайп за текущую попытку 16384 байт

Всего Записали в пайп  33218 байт


EPOLLIN=============================

Пробуем записать в пайп 32318 байт

Записали в пайп за текущую попытку 15934 байт

Всего Записали в пайп  49152 байт


EPOLLIN=============================

Пробуем записать в пайп 16384 байт

А это EAGAIN...

Осталось: байт в сокете 16834| байт в пайпе 49152

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

а я пробовала писать меньше на несколько байт в рабочем варианте кода и история повторяется. Гарантированно не выскакивает EAGAIN если уменьшаю пайп до 65536-4096*4 с пом fcntl (я про тесты на своих машинах)

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

ну что пука...

pipe_size=65536

accepted


EPOLLIN=============================

Пробуем записать в пайп 65536 байт

Записали в пайп за текущую попытку 211 байт

Всего Записали в пайп  211 байт


EPOLLIN=============================

Пробуем записать в пайп 65325 байт

Записали в пайп за текущую попытку 149 байт

Всего Записали в пайп  360 байт


EPOLLIN=============================

Пробуем записать в пайп 65176 байт

Записали в пайп за текущую попытку 16384 байт

Всего Записали в пайп  16744 байт


EPOLLIN=============================

Пробуем записать в пайп 48792 байт

Записали в пайп за текущую попытку 16384 байт

Всего Записали в пайп  33128 байт


EPOLLIN=============================

Пробуем записать в пайп 32408 байт

Записали в пайп за текущую попытку 16384 байт

Всего Записали в пайп  49512 байт


EPOLLIN=============================

Пробуем записать в пайп 16024 байт

Записали в пайп за текущую попытку 16024 байт

Всего Записали в пайп  65536 байт

Все отлично, пайп заполнен полностью
root@local:~#

root@local:~# uname -a Linux local 3.11.0-26-generic #45-Ubuntu SMP Tue Jul 15 04:04:15 UTC 2014 i686 i686 i686 GNU/Linux

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

ниже 3.11 версий под рукой больше нет

anonymous
()
Ответ на: комментарий от nyka

дифни сорсы на splice, думаю была бага, а еще лучше обнови ядро, какой смысл на старье сидеть?

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

про баг уже понятно. просто был академический интерес
тема закрыта

nyka
() автор топика
Ответ на: комментарий от nyka
$ ls -s test.file 
131204 test.file
$ curl -F 'data=@test.file' 192.168.7.12:5555
curl: (56) Recv failure: Соединение разорвано другой стороной


pipe_size=65536

accepted


EPOLLIN=============================

Пробуем записать в пайп 65536 байт

Записали в пайп за текущую попытку 217 байт

Всего Записали в пайп  217 байт


EPOLLIN=============================

Пробуем записать в пайп 65319 байт

Записали в пайп за текущую попытку 1448 байт

Всего Записали в пайп  1665 байт


EPOLLIN=============================

Пробуем записать в пайп 63871 байт

Записали в пайп за текущую попытку 4344 байт

Всего Записали в пайп  6009 байт


EPOLLIN=============================

Пробуем записать в пайп 59527 байт

Записали в пайп за текущую попытку 4344 байт

Всего Записали в пайп  10353 байт


EPOLLIN=============================

Пробуем записать в пайп 55183 байт

Записали в пайп за текущую попытку 5792 байт

Всего Записали в пайп  16145 байт


EPOLLIN=============================

Пробуем записать в пайп 49391 байт

Записали в пайп за текущую попытку 4344 байт

Всего Записали в пайп  20489 байт


EPOLLIN=============================

Пробуем записать в пайп 45047 байт

Записали в пайп за текущую попытку 1448 байт

Всего Записали в пайп  21937 байт


EPOLLIN=============================

Пробуем записать в пайп 43599 байт

А это EAGAIN...

Осталось: байт в сокете 2896| байт в пайпе 21937

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

в примере выше конечно понятно, там данных в буфере сокета было меньше чем пробовали сплайснуть в пайп

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

инетесная картина выходит. по окончании предпоследней итерации в пайпе находится 21937 байт.
На последней итерации пробуем записать 43599 байт и даже те 2896 байт в буфере сокета он не записал в пайп.
Закономерный вопрос: сфига ли он не сделал сплайс данных которые были в буфере сокета?

nyka
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.