BSD sockes don't respect non-blocking settings
csunday95 opened this issue · 1 comments
It appears that the current socket implementation doesn't respect non-blocking socket operations. The below program should immediately exit recv with a value of -1 for bytesReceived
, but it seems to block.
#include <coreinit/thread.h>
#include <coreinit/time.h>
#include <coreinit/systeminfo.h>
#include <nn/ac.h>
#include <whb/proc.h>
#include <whb/log.h>
#include <whb/log_console.h>
#include <thread>
#include <chrono>
#include <netinet/in.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>
#include <cstring>
static bool accepting = false;
static int port = 1234;
int test_thread()
{
int acceptSocket = -1, clientSocket = -1;
int haveEvent;
socklen_t clientLen;
sockaddr_in serverAddr{};
sockaddr_in clientAddr{};
char clientIP[64];
pollfd pfds[1];
int bytesReceived = 0;
char data[256];
clientLen = sizeof(clientAddr);
acceptSocket = socket(AF_INET, SOCK_STREAM, 0);
if (acceptSocket == -1)
{
WHBLogPrintf("Unable to create accept socket: %d", errno);
WHBLogConsoleDraw();
return 1;
}
// set socket to non-blocking
int flags = fcntl(acceptSocket, F_GETFL, 0);
if (flags == -1)
{
WHBLogPrintf("unable to get flags from fnctl: %d", errno);
WHBLogConsoleDraw();
return 1;
}
flags |= O_NONBLOCK;
if (fcntl(acceptSocket, F_SETFL, flags) == -1)
{
WHBLogPrintf("unable to set flags with fnctl: %d", errno);
WHBLogConsoleDraw();
return 1;
}
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(port);
if(bind(acceptSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0)
{
WHBLogPrintf("Unable to bind accept socket: %d", errno);
WHBLogConsoleDraw();
return 1;
}
listen(acceptSocket, 5);
pfds[0].fd = acceptSocket;
pfds[0].events = POLLIN;
while(accepting)
{
haveEvent = poll(pfds, 1, 1000);
if (haveEvent == 0) {
WHBLogPrintf("accept poll timed out");
WHBLogConsoleDraw();
continue;
}
if (haveEvent < 0)
{
WHBLogPrintf("Poll encountered error: %d", errno);
WHBLogConsoleDraw();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
continue;
}
if(pfds[0].revents & POLLIN)
{
// client socket should inherit non-blocking status from accepting socket
clientSocket = accept(acceptSocket, (struct sockaddr*)&clientAddr, &clientLen);
if (clientSocket != -1)
{
inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP));
WHBLogPrintf("client FD %d connected from addr %s", clientSocket, clientIP);
WHBLogConsoleDraw();
break;
}
}
}
while(accepting)
{
// add don't wait flag, even though socket should be non-blocking already
bytesReceived = recv(clientSocket, data, sizeof(data) - 1, MSG_DONTWAIT);
if (bytesReceived == 0)
{
WHBLogPrintf("client FD %d disconnected", clientSocket);
WHBLogConsoleDraw();
break;
}
else if (bytesReceived < 0)
{
switch (errno)
{
case EWOULDBLOCK:
std::this_thread::sleep_for(std::chrono::milliseconds(500));
continue;
default:
WHBLogPrintf("Error on recv: %d", errno);
WHBLogConsoleDraw();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
return 1;
}
}
else
{
data[bytesReceived] = '\0';
WHBLogPrintf("Got data: %s", data);
WHBLogConsoleDraw();
}
}
WHBLogPrintf("test thread exiting");
WHBLogConsoleDraw();
return 0;
}
int
main(int argc, char **argv)
{
nn::ac::ConfigIdNum configId;
nn::ac::Initialize();
nn::ac::GetStartupId(&configId);
nn::ac::Connect(configId);
WHBProcInit();
WHBLogConsoleInit();
accepting = true;
std::thread t(test_thread);
while(WHBProcIsRunning()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
accepting = false;
t.join();
WHBLogConsoleFree();
WHBProcShutdown();
nn::ac::Finalize();
return 0;
}
An example python script to test
import socket
import time
WIIU_IP = '192.168.1.163'
sock = socket.create_connection((WIIU_IP, 1234), timeout=1.0)
time.sleep(5.0)
print(sock.send(b"Here's some example data"))
time.sleep(5.0)
Expected Output:
accept poll timed out
...
accept poll timed out
client FD 4 connected from addr X.X.X.X <-- point where test python script begins running
waiting to recv: 11
waiting to recv: 11
...
waiting to recv: 11
waiting to recv: 11
Got data: Here's some example data <-- point where test script calls send()
waiting to recv: 11
waiting to recv: 11
...
waiting to recv: 11
waiting to recv: 11
client FD 4 disconnected <-- point where test script exits
Actual Output
accept poll timed out
...
accept poll timed out
client FD 4 connected from addr X.X.X.X
Got data: Here's some example data
client FD 4 disconnected
This demonstrates that the recv() call is blocking despite the socket having O_NONBLOCK set and MSG_DONTWAIT being passed. Its possible that this simply isn't implemented yet, but I was unable to find that documented anywhere. It's also possible I'm just missing some step of initialization to enable non-blocking operations.
I'm unable to reproduce this.
When testing your example, calling recv
does not block and will set errno to EWOULDBLOCK
, like expected.
Your example seems to be missing the "waiting to recv" log message from your expected output. After adding that at line 126, the output matches the expected output.