У меня есть набор программ клиент/сервер TCP, который без проблем работает уже более 10 лет. Задание «head» порождает тело дочерних процессов (я работаю на платформе AS400, которая не поддерживает fork()/exec()). Головное задание настраивается как TCP-сервер, и как только приходит соединение, оно немедленно отдает соединение одному из дочерних заданий через sendmsg(). Используется обычный шаблон socketpair() и т. д. Признаюсь, когда я собирал все это по кусочкам 12 лет назад, я не понимал всего, что происходит. Код, который я нашел для передачи открытого соединения, приведен ниже. Таким образом, главная работа ожидает accept() и вызывает sendfd(), когда accept() возвращается.
int sendfd(int s, int fd)
{
char buf[1];
struct iovec iov;
struct msghdr msg;
struct cmsghdr *cmsg;
int n;
char cms[CMSG_SPACE(sizeof(int))];
buf[0] = 0;
iov.iov_base = buf;
iov.iov_len = 1;
memset(&msg, 0, sizeof msg);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = (caddr_t)cms;
msg.msg_controllen = CMSG_LEN(sizeof(int));
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
memmove(CMSG_DATA(cmsg), &fd, sizeof(int));
if ((n = sendmsg(s, &msg, 0)) != iov.iov_len) {
return -1;
}
return 0;
}
Таким образом, дочерние задания запускают процедуру для получения соединения. Это ниже. Пул дочерних процессов эффективно запускает эту процедуру, например "recvfd(0);" Наш отдел контроля качества запустит автоматизированный тест, и с прошлой недели все дочерние процессы будут умирать, по-видимому, случайным образом, вплоть до секунды, с EWOULDBLOCK. Меня смущает эта ошибка. recvmsg() всегда блокируется, ожидая сообщения. Относится ли ошибка к принимаемому сокету или к «0», для которого он используется? Ничто в моем коде не меняет качества файлового дескриптора 0. Я действительно озадачен тем, почему умирает все тело дочерних процессов. Что-то подсказывает мне, что файловый дескриптор 0 изменяется, но в коде ни одной из программ нет ничего, что могло бы выполнять подобные манипуляции.
int recvfd(int s)
{
int ret, fd;
char buf[1];
struct iovec iov;
struct msghdr msg;
struct cmsghdr *cmsg;
char cms[CMSG_SPACE(sizeof(int))];
iov.iov_base = buf;
iov.iov_len = 1;
memset(&msg, 0, sizeof msg);
msg.msg_name = 0;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = (caddr_t)cms;
msg.msg_controllen = sizeof cms;
if ((ret = recvmsg(s, &msg, 0)) < 0) {
return -1;
}
if (ret == 0) {
return -1;
}
cmsg = CMSG_FIRSTHDR(&msg);
memmove(&fd, CMSG_DATA(cmsg), sizeof(int));
return fd;
}
Я могу опубликовать больше кода, если требуется больше деталей. Я должен найти эту ошибку прямо сейчас, так как это основа нашего бизнеса. Заранее спасибо!
Обновлено: Вот фрагмент дочернего кода. Обратите внимание, что 'worker_sd' явно установлен на 0. Проблема, которую мне нужно исправить, заключается в том, что выход из цикла означает выход из программы, но я не знаю, есть ли способ справиться с этой ошибкой.
Сообщение об ошибке в итоге будет таким: Дочерняя транзакция завершается с new_sock=-1 и error=Operation привела бы к приостановке процесса.
// worker_sd is explicitly set to 0 because the parent set the child's 0 file descriptor (the usual one
// reserved for standard input) to be the socket over which open descriptors come.
int worker_sd = 0;
jobSwitches.get();
logging_on = jobSwitches[QQJobSwitches::LOGGING_ON] == '1';
debug_on = jobSwitches[QQJobSwitches::DEBUG_ON] == '1';
wait_on_child_process = jobSwitches[QQJobSwitches::WAIT_ON_CHILD] == '1';
if (debug_on) {
CERR << "START: Waiting for a message..." << endl;
}
while ((new_sock = recvfd(worker_sd)) >= 0) {
stringstream sstr;
string ip_str, port_str;
bool success;
MCVE, пожалуйста. Откуда взялся EWOULDBLOCK? Для начала я бы улучшил обработку ошибок в этом куске кода, чтобы было понятнее, откуда берется ошибка.
@Chimera, сработало без проблем. Никакой реальный код не меняется в последнее время. Конечно, я рассматриваю возможности, связанные с системой, но поскольку это начало происходить, мы ничего не нашли (пока). В данный момент я добавляю больше кода регистрации. Я также провожу некоторые исследования с getockopt(), которые могут рассказать мне лучшую историю.
@СергейА. Я уверен, что проблема связана с recvmsg() в подпрограмме recvfd(). Я добавляю больше кода регистрации на данный момент. Спасибо за ваш ответ.
recvmsg() может сообщить EWOULDBLOCK только в том случае, если 1) s является неблокирующим сокетом и нет данных, ожидающих чтения при вызове recvmsg(), или 2) s является блокирующим сокетом с назначенным тайм-аутом приема и recvmsg() раз до получения каких-либо данных. Какую логику вы используете, чтобы определить, когда следует вызывать recvfd()? Вы вообще используете select() или (e)poll()? Вы уверены, что s на самом деле относится к правильному сокету, из которого вы ожидаете читать?
Это может быть полезно? ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzab6/rzab6.pdf
Я повторяю комментарий @RemyLebeau. Вы используете select() или poll(), чтобы определить, есть ли в сокете данные для чтения?
@Chimera По общему признанию, это не так. Это никогда не требовалось раньше, и вы можете прокручивать это весь день, и это не произойдет позже или может произойти сразу. что-то изменился - пока не уверен, что именно. Спасибо.
@KellyBeard - вы должны использовать select по крайней мере, чтобы определить, действительно ли в сокете есть данные, готовые для чтения, чтобы избежать проблемы EWOULDBLOCK.
@Chimera Понятно, но теоретически мне это не нужно, потому что fd 0 является блокирующим сокетом. Я добавляю дополнительные операторы регистрации и вызов fcntl(), чтобы увидеть, изменился ли каким-либо образом статус блокировки. Однако я добавлю select().





Итак, это приложение работало более 10 лет, и теперь вы видите новое поведение? Код изменился? Какие-либо обновления ОС, библиотек, системных настроек и т. д., о которых вы знаете, могут изменить поведение ваших кодов?