Вызов ioctl () с FIONREAD приводит к странным побочным эффектам в явном состоянии гонки,

Я пишу симулятор параллельной нейронной сети, и недавно я столкнулся с проблемой в моем коде, которая полностью меня смущает (если я только программист среднего уровня C ++, так что, может быть, я упускаю что-то очевидное?), … Мой В коде задействован «сервер» и множество клиентов (работников), которые берут работу и возвращают результаты на сервер. Вот серверная часть:

#include <iostream>
#include <fstream>

#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>

#include <sys/ioctl.h>

void advanceToNextInputValue(std::ifstream &trainingData, char &nextCharacter)
{

nextCharacter = trainingData.peek();
while(nextCharacter != EOF && !isdigit(nextCharacter))
{
sleep(1);
trainingData.get();
sleep(1);
nextCharacter = trainingData.peek();
}
}

int main()
{
// Create a socket,...
int listenerSocketNum = socket(AF_INET, SOCK_STREAM, 0);

// Name the socket,...
sockaddr_in socketAddress;
socklen_t socketAddressLength = sizeof(socketAddress);

inet_pton(AF_INET, "127.0.0.1", &(socketAddress.sin_addr));
socketAddress.sin_port = htons(9988);
bind(listenerSocketNum, reinterpret_cast<sockaddr*>(&socketAddress), socketAddressLength);

// Create a connection queue for worker processes waiting to connect to this server,...
listen(listenerSocketNum, SOMAXCONN);int epollInstance = epoll_create(3); // Expected # of file descriptors to monitor

// Allocate a buffer to store epoll events returned from the network layer
epoll_event* networkEvents = new epoll_event[3];

// Add the server listener socket to the list of file descriptors monitored by epoll,...
networkEvents[0].data.fd = -1; // A marker returned with the event for easy identification of which worker process event belongs to
networkEvents[0].events = EPOLLIN | EPOLLET; // epoll-IN- since we only expect incoming data on this socket (ie: connection requests from workers),...
// epoll-ET- indicates an Edge Triggered watch
epoll_ctl(epollInstance, EPOLL_CTL_ADD, listenerSocketNum, &networkEvents[0]);char nextCharacter = 'A';
std::ifstream trainingData;

// General multi-purpose/multi-use variables,...
long double v;
signed char w;
int x = 0;
int y;

while(1)
{
y = epoll_wait(epollInstance, networkEvents, 3, -1); // the -1 tells epoll_wait to block indefinitely

while(y > 0)
{
if(networkEvents[y-1].data.fd == -1) // We have a notification on the listener socket indicating a request for a new connection (and we expect only one for this testcase),...
{
x = accept(listenerSocketNum,reinterpret_cast<sockaddr*>(&socketAddress), &socketAddressLength);

networkEvents[y-1].data.fd = x; // Here we are just being lazy and re-using networkEvents[y-1] temporarily,...
networkEvents[y-1].events = EPOLLIN | EPOLLET;

// Add the socket for the new worker to the list of file descriptors monitored,...
epoll_ctl(epollInstance, EPOLL_CTL_ADD, x, &networkEvents[y-1]);

trainingData.open("/tmp/trainingData.txt");
}
else if(networkEvents[y-1].data.fd == x) // Worker is waiting to receive datapoints for calibration,...
{
std::cout << "nextCharacter before call to ioctl: " << nextCharacter << std::endl;
ioctl(networkEvents[y-1].data.fd, FIONREAD, &w);
std::cout << "nextCharacter after call to ioctl: " << nextCharacter << std::endl;

recv(networkEvents[y-1].data.fd, &v, sizeof(v), MSG_DONTWAIT); // Retrieve and discard the 'tickle' from worker

if(nextCharacter != EOF)
{
trainingData >> v;

send(networkEvents[y-1].data.fd, &v, sizeof(v), MSG_DONTWAIT);
advanceToNextInputValue(trainingData, nextCharacter);
}
}

y--;
}
}

close(epollInstance);
return 0;
}

А вот и клиентская часть:

#include <arpa/inet.h>

int main()
{
int workerSocket = socket(AF_INET, SOCK_STREAM, 0);

// Name the socket as agreed with the server:
sockaddr_in serverSocketAddress;
serverSocketAddress.sin_family = AF_INET;
serverSocketAddress.sin_port = htons(9988);
inet_pton(AF_INET, "127.0.0.1", &(serverSocketAddress.sin_addr));

// Connect your socket to the server's socket:
connect(workerSocket, reinterpret_cast<sockaddr*>(&serverSocketAddress), sizeof(serverSocketAddress));

long double z;
while(1)
{
send(workerSocket, &z, sizeof(z), MSG_DONTWAIT); // Send a dummy result/tickle to server,...
recv(workerSocket, &z, sizeof(z), MSG_WAITALL);
}
}

Часть кода, с которой у меня возникают проблемы, выглядит следующим образом (с сервера):

std::cout << "nextCharacter before call to ioctl: " << nextCharacter << std::endl;
ioctl(networkEvents[y-1].data.fd, FIONREAD, &w);
std::cout << "nextCharacter after call to ioctl: " << nextCharacter << std::endl;

Здесь (по крайней мере, в моей системе) при определенных обстоятельствах вызов ioctl в основном уничтожает значение ‘nextCharacter’, и я не могу понять, как и почему!

Вот результаты, которые я ожидаю получить:

$ ./server.exe
nextCharacter before call to ioctl: A
nextCharacter after call to ioctl: A
nextCharacter before call to ioctl: 1
nextCharacter after call to ioctl: 1
nextCharacter before call to ioctl: 9
nextCharacter after call to ioctl: 9
nextCharacter before call to ioctl: 2
nextCharacter after call to ioctl: 2
nextCharacter before call to ioctl: 1
nextCharacter after call to ioctl: 1
nextCharacter before call to ioctl: 1
nextCharacter after call to ioctl: 1
nextCharacter before call to ioctl: 1
nextCharacter after call to ioctl: 1
nextCharacter before call to ioctl: 2
nextCharacter after call to ioctl: 2
nextCharacter before call to ioctl: ÿ
nextCharacter after call to ioctl: ÿ

(«Y» в нижнем регистре с умлаутом является символом EOF в конце файла)

И вот результаты, которые я получаю (обратите внимание, что мы в конечном итоге в бесконечном цикле, потому что условие остановки зависит от значения nextCharacter, и оно стирается, поэтому оно никогда не останавливается):

$ ./server.exe
nextCharacter before call to ioctl: A
nextCharacter after call to ioctl:
nextCharacter before call to ioctl: 1
nextCharacter after call to ioctl:
nextCharacter before call to ioctl: 9
nextCharacter after call to ioctl:
nextCharacter before call to ioctl: 2
nextCharacter after call to ioctl:
nextCharacter before call to ioctl: 1
nextCharacter after call to ioctl:
nextCharacter before call to ioctl: 1
nextCharacter after call to ioctl:
nextCharacter before call to ioctl: 1
nextCharacter after call to ioctl:
nextCharacter before call to ioctl: 2
nextCharacter after call to ioctl:
nextCharacter before call to ioctl: ÿ
nextCharacter after call to ioctl:
nextCharacter before call to ioctl: ÿ
nextCharacter after call to ioctl:
nextCharacter before call to ioctl: ÿ
nextCharacter after call to ioctl:
.
.
.

Если я закомментирую какие-либо операторы сна в этом разделе (на сервере):

void advanceToNextInputValue(std::ifstream &trainingData, char &nextCharacter)
{

nextCharacter = trainingData.peek();
while(nextCharacter != EOF && !isdigit(nextCharacter))
{
sleep(1);
trainingData.get();
sleep(1);
nextCharacter = trainingData.peek();
}
}

Затем я получаю результаты, которые ожидаю получить …

Это make-файл, который я использую:

$ cat Makefile
all: server client

server: server.cpp
g++ server.cpp -o server.exe -ansi -fno-elide-constructors -O3 -pedantic-errors -Wall -Wextra -Winit-self -Wold-style-cast -Woverloaded-virtual -Wuninitialized -Winit-self

client: client.cpp
g++ client.cpp -o client.exe -ansi -fno-elide-constructors -O3 -pedantic-errors -Wall -Wextra -Winit-self -Wold-style-cast -Woverloaded-virtual -Wuninitialized -Winit-self

С файлом trainingData.txt, который выглядит следующим образом:

$ cat trainingData.txt
15616.16993666375,15616.16993666375,9.28693983312753E20,24.99528974548316,16.91935342923897,16.91935342923897,1.386594632397968E6,2.567209162871251

Так я обнаружил новую ошибку или я просто глуп? 🙂 Честно говоря, я не понимаю, почему вызов ioctl с помощью FIONREAD, который должен сообщить мне, сколько байтов у меня есть в сокете, ожидающем чтения, должен каким-либо образом влиять на значение переменной nextCharacter, .. ,

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

Терри

2

Решение

От man ioctl_list:

FIONREAD int *

То есть, FIONREAD ожидает указатель на целое число, но вы передаете указатель на signed char,

Решение: измените свое:

signed char w;

в

int w;

иначе ты будешь страдать от Неопределенное поведение.

Объяснение того, что вы видите, состоит в том, что, вероятно, компилятор помещает w и nextCharacter Переменные вместе в памяти и переполнение первого перезаписывает значение последнего.

0

Другие решения


По вопросам рекламы [email protected]