Допустим перед нами задача по ipc взимодействовать с одним единственным дочерним процессом. Взаимодействие в стиле запрос-родителя-ответ-ребенка, не более.
Но ребенок может вообще ничего не ответить или ответить слишком много, намного превышающим лимит PIPE_BUF (man 7 pipe), поэтому выходом будет процесс чтения защитить таймаутом. Не блокирующее или асинхронное чтение для решения данной проблемы не нужно - достаточно select-а.
Самый простой вариант видится таким:
// k_systemDependetLimitation == PIPE_BUF
struct timeval tv;
tv.tv_sec = k_ipcWaitDataDelay;
tv.tv_usec = 0;
std::string ret;
int returnCode;
while (true) {
if ((returnCode = select(m_istance(client).m_readFromChildProccess[0] + 1,
&m_istance(client).m_readFromChildWait, nullptr, nullptr, &tv)) > 0) {
char rawBuffer[k_bufferLength] = {0};
ssize_t readLegth;
if ((readLegth = read(m_istance(client).m_readFromChildProccess[0], rawBuffer, k_systemDependetLimitation)) > 0) {
ret.append(rawBuffer, rawBuffer + readLegth);
if (readLegth == k_systemDependetLimitation) {
continue;
}
break;
} else {
releaseIpcAndswitchToErrorState(client);
break;
}
} else if (!returnCode) {
FD_ZERO(&m_istance(client).m_readFromChildWait); // reinit for select (see man select)
FD_SET(m_istance(client).m_readFromChildProccess[0], &m_istance(client).m_readFromChildWait);
break;
} else {
releaseIpcAndswitchToErrorState(client);
break;
}
}
По скольку чтение блокирующее мы не можем быть уверены что после readLegth == k_systemDependetLimitation что-то есть или чего-то нет
поэтому тут неизбежно нужно запрашивать select, если что-то есть select сразу же вернёт управление и read опять начнёт читать, если ничего нет - повисит за зря (а что делать?) и вернет управления по таймайту else if (!returnCode).
Если прочли меньше, значит конец чтения.
Но я хотел бы читать больше чем 4096 за раз. Мотивация: не хочу лишние переключения контекста. И вообще хотел бы например использовать select только изначально, и дальше с помощью каких-то техник определять стоит ли запускать read еще или нет, но похоже это не возможно?
Дело в том что читать >4096 сложно. Даже при условии что пишет в канал только один процесс и даже с гарантией что он пишет ровно тогда когда буффер пайпа полностью свободен (родитель все ранее записанное, от прошлого запроса, прочитал).
Казалось бы можно просто указать читать не 4096 а больше, и всё. Но в редких случаях ядро остановит чтение на границе 4096 Т.е. как раз на границе атомарной передачи по каналу.
Это можно попытаться отловить:
Например:
//k_bufferLength == 9000
...
if ((readLegth = read(m_istance(client).m_readFromChildProccess[0], rawBuffer, k_bufferLength)) > 0) {
ret.append(rawBuffer, rawBuffer + readLegth);
if (readLegth == k_bufferLength) {
// здесь мы по прежнему ничего не можем сказать о том что больше нечего читать
continue;
}
// если чтение прервано не на границе PIPE_BUF то оно конечно
if (readLegth % k_systemDependetLimitation) {
break;
}
} else {
releaseIpcAndswitchToErrorState(client);
break;
}
}
...
На одних и тех же тестовых запусках над одними и теми же тестовыми данными данный код в подавляющем большинстве случаяв работал, я расчитваю
что может быть прочтено только:
или ==9000 (т.е. 2 полных атомарных цикла записи в пайп и один не атомарный)
или <9000, но не кратный границе 4096 тогда типа явно всё прочитано, потому что типа ядро не прервёт такое, потому что граница атомарности (судя по размеру прочитанного уже пройдена - а значит то что не лежит на такой границе - ядро не прерывает)
или кратный_атомарному - значит тут есть возможность того что ядро первало.
Но такие выводы, оказались не верны, крайне редко но бывает:
что сначала прочитали 9000, потом вместо например 4132 (что бывает в большинстве случаев и тогда ок)
мы прочитали скажем 3800 - и все - тогда код прерывает чтение, а по факту еще осталось читать (4132-3800 байт).
И так есть ли какой-то красивый способ без дополнтельного вызова select пусть и с минимальными миллисекунндыми таймаутами - определять что ядро все передало что пишет дочерний.
Или таких способов нет, и едиственным способом (чтобы передавать много с минимальным дроблением на итерации (ну т.е. читать не по 4096 а по много)) был бы в начале передчи передавать размер передаваемого данного (чтобы размер укладывался в первые байты передачи) или что еще лучше - просто ввести маркер начала передачи и конца, и пока в принятой последовательности нет маркера конца - читать ещё.
но вот фишка в том что то что отправлят дочерний я менять не могу и там таких маркеров нет :)