Rewrite Windows accept()

This change should fix the Windows issues Qt Creator has been having, by
ensuring accept() and accept4() work in O_NONBLOCK mode. I switched away
from AcceptEx() which is buggy, back to using WSAAccept(). This requires
making a tradeoff where we have to accept a busy loop. However it is low
latency in nature, just like our new and improved Windows poll() code. I
was furthermore able to eliminate a bunch of Windows-related test todos.
This commit is contained in:
Justine Tunney 2024-09-12 01:18:14 -07:00
parent 6f868fe1de
commit acd6c32184
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
20 changed files with 622 additions and 209 deletions

View file

@ -37,9 +37,6 @@
#include "libc/thread/thread.h"
TEST(O_NONBLOCK, canBeSetBySocket_toMakeListenNonBlocking) {
// TODO(jart): this doesn't make any sense on windows
if (IsWindows())
return;
char buf[16] = {0};
uint32_t addrsize = sizeof(struct sockaddr_in);
struct sockaddr_in addr = {

View file

@ -33,8 +33,6 @@
// two clients send a udp packet containing their local address
// server verifies content of packet matches the peer's address
TEST(recvfrom, test) {
if (!IsWindows())
return;
uint32_t addrsize = sizeof(struct sockaddr_in);
struct sockaddr_in server = {
.sin_family = AF_INET,

View file

@ -41,9 +41,9 @@
void SetUpOnce(void) {
if (IsNetbsd())
exit(0);
exit(0); // no sendfile support
if (IsOpenbsd())
exit(0);
exit(0); // no sendfile support
testlib_enable_tmp_setup_teardown();
ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath proc inet", 0));
}
@ -102,9 +102,6 @@ TEST(sendfile, testSeeking) {
}
TEST(sendfile, testPositioning) {
// TODO(jart): fix test regression on windows
if (IsWindows())
return;
char buf[1024];
uint32_t addrsize = sizeof(struct sockaddr_in);
struct sockaddr_in addr = {
@ -130,9 +127,8 @@ TEST(sendfile, testPositioning) {
ASSERT_TRUE(errno == EINVAL || errno == EPIPE);
errno = 0;
// XXX: WSL1 clobbers file offset on failure!
if (!__iswsl1()) {
if (!__iswsl1())
ASSERT_EQ(12, GetFileOffset(5));
}
_Exit(0);
}
ASSERT_SYS(0, 0, close(3));

View file

@ -32,7 +32,9 @@ TEST_POSIX_DIRECTDEPS = \
LIBC_INTRIN \
LIBC_MEM \
LIBC_PROC \
LIBC_LOG \
LIBC_RUNTIME \
LIBC_SOCK \
LIBC_STDIO \
LIBC_STR \
LIBC_SYSV \

View file

@ -0,0 +1,71 @@
#include <arpa/inet.h>
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
// Create server socket
int server_fd;
struct sockaddr_in address;
int addrlen = sizeof(address);
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return 1;
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
address.sin_port = 0; // let os assign random port
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)))
return 2;
if (getsockname(server_fd, (struct sockaddr *)&address,
(socklen_t *)&addrlen))
return 3;
if (listen(server_fd, SOMAXCONN))
return 4;
{
// poll server
struct pollfd fds[2] = {
{server_fd, POLLIN | POLLOUT},
};
int ret = poll(fds, 1, 0);
if (ret != 0)
return 5;
}
// create client socket
int client_fd;
if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return 6;
if (connect(client_fd, (struct sockaddr *)&address, sizeof(address)))
return 7;
// accept client
int server_client_fd;
if ((server_client_fd = accept4(server_fd, 0, 0, SOCK_NONBLOCK)) == -1)
return 8;
// check that it's non-blocking
char buf[1];
if (read(server_client_fd, buf, 1) != -1)
return 9;
if (errno != EAGAIN && errno != EWOULDBLOCK)
return 10;
// Clean up
if (close(server_client_fd))
return 12;
if (close(client_fd))
return 13;
if (close(server_fd))
return 14;
CheckForMemoryLeaks();
}

View file

@ -0,0 +1,85 @@
#include <arpa/inet.h>
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
void on_signal(int sig) {
}
int main() {
// Create server socket
int server_fd;
struct sockaddr_in address;
int addrlen = sizeof(address);
if ((server_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1)
return 1;
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
address.sin_port = 0; // let os assign random port
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)))
return 2;
if (getsockname(server_fd, (struct sockaddr *)&address,
(socklen_t *)&addrlen))
return 3;
if (listen(server_fd, SOMAXCONN))
return 4;
{
// poll server
struct pollfd fds[] = {{server_fd, POLLIN | POLLOUT}};
int ret = poll(fds, 1, 0);
if (ret != 0)
return 5;
}
// verify server socket is non-blocking
if (accept(server_fd, 0, 0) != -1)
return 20;
if (errno != EAGAIN && errno != EWOULDBLOCK)
return 21;
// create client socket
int client_fd;
if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return 6;
if (connect(client_fd, (struct sockaddr *)&address, sizeof(address)))
return 7;
// prevent race condition
// impacts platforms like openbsd
fcntl(server_fd, F_SETFL, fcntl(server_fd, F_GETFL) & ~O_NONBLOCK);
// accept client
int server_client_fd;
if ((server_client_fd = accept(server_fd, 0, 0)) == -1)
return 8;
// check that non-blocking wasn't inherited from listener
char buf[1];
sigaction(SIGALRM, &(struct sigaction){.sa_handler = on_signal}, 0);
ualarm(100000, 0);
if (read(server_client_fd, buf, 1) != -1)
return 9;
if (errno != EINTR)
return 10;
// Clean up
if (close(server_client_fd))
return 12;
if (close(client_fd))
return 13;
if (close(server_fd))
return 14;
CheckForMemoryLeaks();
}

View file

@ -0,0 +1,94 @@
#include <arpa/inet.h>
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
// Create server socket
int server_fd;
struct sockaddr_in address;
int addrlen = sizeof(address);
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return 1;
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
address.sin_port = 0; // let os assign random port
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)))
return 2;
if (getsockname(server_fd, (struct sockaddr *)&address,
(socklen_t *)&addrlen))
return 3;
if (listen(server_fd, SOMAXCONN))
return 4;
{
// poll server
struct pollfd fds[2] = {
{server_fd, POLLIN | POLLOUT},
};
int ret = poll(fds, 1, 0);
if (ret != 0)
return 5;
}
// create client socket
int client_fd;
if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return 6;
if (connect(client_fd, (struct sockaddr *)&address, sizeof(address)))
return 7;
{
// poll server
struct pollfd fds[] = {{server_fd, POLLIN | POLLOUT}};
int ret = poll(fds, 1, -1u);
if (ret != 1)
return 8;
if (!(fds[0].revents & POLLIN))
return 9;
if (fds[0].revents & POLLOUT)
return 10;
if (fds[0].revents & POLLHUP)
return 11;
if (fds[0].revents & POLLERR)
return 12;
}
{
// poll server with invalid thing
struct pollfd fds[] = {
{server_fd, POLLIN | POLLOUT},
{666, POLLIN | POLLOUT},
};
int ret = poll(fds, 2, -1u);
if (ret != 2)
return 18;
if (!(fds[0].revents & POLLIN))
return 19;
if (fds[0].revents & POLLOUT)
return 20;
if (fds[1].revents & POLLIN)
return 21;
if (fds[1].revents & POLLOUT)
return 22;
if (!(fds[1].revents & POLLNVAL))
return 23;
}
// Clean up
if (close(client_fd))
return 13;
if (close(server_fd))
return 14;
CheckForMemoryLeaks();
}

View file

@ -0,0 +1,75 @@
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int pipefd[2];
char buf[PIPE_BUF];
char buf2[PIPE_BUF];
ssize_t bytes_read;
ssize_t bytes_written;
// Create a pipe
if (pipe2(pipefd, O_NONBLOCK) == -1)
exit(1);
// Test 1: Reading from an empty pipe should fail with EAGAIN
bytes_read = read(pipefd[0], buf, PIPE_BUF);
if (bytes_read != -1 || errno != EAGAIN)
exit(4);
// Test 2: Writing to the pipe
bytes_written = write(pipefd[1], buf, PIPE_BUF);
if (bytes_written != PIPE_BUF)
exit(5);
// Test 3: Reading from the pipe after writing
bytes_read = read(pipefd[0], buf2, PIPE_BUF);
if (bytes_read != PIPE_BUF || memcmp(buf, buf2, PIPE_BUF))
exit(6);
// Test 4: Fill the pipe buffer
int ch = 10;
size_t total_written = 0;
for (;;) {
memset(buf, ch, PIPE_BUF);
bytes_written = write(pipefd[1], buf, PIPE_BUF);
if (bytes_written == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break; // Pipe is full
} else {
exit(7); // Unexpected error
}
}
total_written += bytes_written;
}
// Test 5: Verify that we can read all the data we wrote
ch = 10;
size_t total_read = 0;
while (total_read < total_written) {
bytes_read = read(pipefd[0], buf2, PIPE_BUF);
if (bytes_read == -1)
exit(8);
memset(buf, ch, PIPE_BUF);
if (memcmp(buf, buf2, PIPE_BUF))
exit(9);
total_read += bytes_read;
}
if (total_read != total_written)
exit(10);
// Clean up
if (close(pipefd[0]))
exit(11);
if (close(pipefd[1]))
exit(12);
CheckForMemoryLeaks();
}

View file

@ -0,0 +1,84 @@
#include <cosmo.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int pipefd[2];
char buf[PIPE_BUF];
char buf2[PIPE_BUF];
ssize_t bytes_read;
ssize_t bytes_written;
// Create a pipe
if (pipe(pipefd) == -1)
exit(1);
// Set O_NONBLOCK flag on the pipe
for (int i = 0; i < 2; ++i) {
int flags;
if ((flags = fcntl(pipefd[i], F_GETFL, 0)) == -1)
exit(2);
if (fcntl(pipefd[i], F_SETFL, flags | O_NONBLOCK) == -1)
exit(3);
}
// Test 1: Reading from an empty pipe should fail with EAGAIN
bytes_read = read(pipefd[0], buf, PIPE_BUF);
if (bytes_read != -1 || errno != EAGAIN)
exit(4);
// Test 2: Writing to the pipe
bytes_written = write(pipefd[1], buf, PIPE_BUF);
if (bytes_written != PIPE_BUF)
exit(5);
// Test 3: Reading from the pipe after writing
bytes_read = read(pipefd[0], buf2, PIPE_BUF);
if (bytes_read != PIPE_BUF || memcmp(buf, buf2, PIPE_BUF))
exit(6);
// Test 4: Fill the pipe buffer
int ch = 10;
size_t total_written = 0;
for (;;) {
memset(buf, ch, PIPE_BUF);
bytes_written = write(pipefd[1], buf, PIPE_BUF);
if (bytes_written == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break; // Pipe is full
} else {
exit(7); // Unexpected error
}
}
total_written += bytes_written;
}
// Test 5: Verify that we can read all the data we wrote
ch = 10;
size_t total_read = 0;
while (total_read < total_written) {
bytes_read = read(pipefd[0], buf2, PIPE_BUF);
if (bytes_read == -1)
exit(8);
memset(buf, ch, PIPE_BUF);
if (memcmp(buf, buf2, PIPE_BUF))
exit(9);
total_read += bytes_read;
}
if (total_read != total_written)
exit(10);
// Clean up
if (close(pipefd[0]))
exit(11);
if (close(pipefd[1]))
exit(12);
CheckForMemoryLeaks();
}