Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions libc-bottom-half/sources/udp.c
Original file line number Diff line number Diff line change
Expand Up @@ -799,8 +799,8 @@ static ssize_t udp_recvfrom(void *data, void *buffer, size_t length, int flags,
return -1;
}

streams->recv_subtask = 0;
streams->flags |= UDP_STREAMS_RECV_READY;
// Same completion path as nonblocking I/O: drop then clear.
wasip3_recv_end(streams, &event);
} else {
wasip3_event_t event;
__wasilibc_poll_waitable(streams->recv_subtask, &event);
Expand Down Expand Up @@ -933,7 +933,7 @@ static int wasip3_send_resolve(udp_socket_t *socket, bool should_block) {
errno = EWOULDBLOCK;
return -1;
}
streams->send_subtask = 0;
// Leave send_subtask set so the shared drop below runs.
} else {
wasip3_event_t event;
__wasilibc_poll_waitable(streams->send_subtask, &event);
Expand Down
36 changes: 31 additions & 5 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,7 @@ if (NOT (WASI STREQUAL "p1"))
add_sockets_test_executable(sockets-client-hangup-while-receiving.c)
add_sockets_test_executable(sockets-client-hangup-while-sending.c)
add_sockets_test_executable(sockets-client-udp-blocking.c)
add_sockets_test_executable(sockets-client-udp-blocking-roundtrips.c)
add_sockets_test_executable(sockets-multiple-client.c)
add_sockets_test_executable(sockets-server.c)
add_sockets_test_executable(sockets-server-handle-hangups.c)
Expand All @@ -745,29 +746,54 @@ if (NOT (WASI STREQUAL "p1"))
add_sockets_test_executable(sockets-server-hangup-during-recv.c)
add_sockets_test_executable(sockets-server-hangup-during-send.c)
add_sockets_test_executable(sockets-server-udp-blocking.c)
add_sockets_test_executable(sockets-server-udp-blocking-roundtrips.c)
add_sockets_test_executable(sockets-multiple-server.c)

function(sockets_test test_name client server)
set(options)
set(oneValueArgs NCLIENTS)
set(oneValueArgs NCLIENTS MAX_RESOURCES TIMEOUT_S)
set(multiValueArgs)
cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${oneValueArgs}" "${multiValueArgs}")

set(socket_test_args
-DENGINE=${ENGINE}
-DSERVER=$<TARGET_FILE:${server}>
-DCLIENT=$<TARGET_FILE:${client}>
-DNCLIENTS=${arg_NCLIENTS}
)
if (arg_MAX_RESOURCES)
list(APPEND socket_test_args -DMAX_RESOURCES=${arg_MAX_RESOURCES})
endif()
if (arg_TIMEOUT_S)
list(APPEND socket_test_args -DTIMEOUT_S=${arg_TIMEOUT_S})
endif()

add_test(
NAME "${test_name}"
COMMAND
${CMAKE_COMMAND}
-DENGINE=${ENGINE}
-DSERVER=$<TARGET_FILE:${server}>
-DCLIENT=$<TARGET_FILE:${client}>
-DNCLIENTS=${arg_NCLIENTS}
${socket_test_args}
-P ${CMAKE_CURRENT_SOURCE_DIR}/socket-test.cmake
)
endfunction()

sockets_test(sockets sockets-client.wasm sockets-server.wasm)
sockets_test(sockets-udp-blocking sockets-client-udp-blocking.wasm
sockets-server-udp-blocking.wasm)
# wasip3: cap concurrent resources so a missing subtask drop fails the suite
# (wasmtime: "resource table has no free keys") while proper drop stays green.
# wasip2 has no async subtasks for this path; leave unlimited.
if (WASI STREQUAL "p3")
sockets_test(sockets-udp-blocking-roundtrips
sockets-client-udp-blocking-roundtrips.wasm
sockets-server-udp-blocking-roundtrips.wasm
MAX_RESOURCES 128
TIMEOUT_S 15)
else()
sockets_test(sockets-udp-blocking-roundtrips
sockets-client-udp-blocking-roundtrips.wasm
sockets-server-udp-blocking-roundtrips.wasm)
endif()
sockets_test(sockets-multiple sockets-multiple-client.wasm sockets-multiple-server.wasm
NCLIENTS 10)

Expand Down
28 changes: 25 additions & 3 deletions test/socket-test.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ if (NOT NCLIENTS)
set(NCLIENTS 1)
endif()

# Base engine flags for wasip3 dual-process socket tests.
set(ENGINE_FLAGS
-Wcomponent-model-async
-Wcomponent-model-threading
-Sp3,inherit-network
)

# Optional cap on wasmtime resource tables. When set, undropped wasip3 subtask
# HostTask entries accumulate in the concurrent ResourceTable and eventually
# trap with "resource table has no free keys". Used to make blocking UDP
# subtask-drop regressions fail before the fix and pass after it.
if (MAX_RESOURCES)
list(APPEND ENGINE_FLAGS -Smax-resources=${MAX_RESOURCES})
endif()

# Serialize flags for embedding inside bash -c strings below.
string(JOIN " " ENGINE_FLAGS_STR ${ENGINE_FLAGS})

if (NOT TIMEOUT_S)
set(TIMEOUT_S 5)
endif()

foreach(i RANGE 1 ${NCLIENTS})
# This is a bit tricky to setup, but the flow is:
#
Expand Down Expand Up @@ -36,15 +58,15 @@ read port # read the first line of stdin from the previous process
echo $port # forward this line to the next process, or out to cmake itself
exec 2>&1 # close our stdout and replace it with stderr
cat <&0 & # forward the rest of stdin to stderr so it shows up in cmake
exec ${ENGINE} -Wcomponent-model-async -Wcomponent-model-threading -Sp3,inherit-network ${CLIENT} \"$port\"
exec ${ENGINE} ${ENGINE_FLAGS_STR} ${CLIENT} \"$port\"
")
list(APPEND CLIENTS COMMAND bash -c ${client_script})
endforeach()

execute_process(
COMMAND ${ENGINE} -Wcomponent-model-async -Wcomponent-model-threading -Sp3,inherit-network ${SERVER}
COMMAND ${ENGINE} ${ENGINE_FLAGS} ${SERVER}
${CLIENTS}
TIMEOUT 5
TIMEOUT ${TIMEOUT_S}
COMMAND_ERROR_IS_FATAL ANY
COMMAND_ECHO STDOUT
)
66 changes: 66 additions & 0 deletions test/src/sockets-client-udp-blocking-roundtrips.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "test.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define TEST(c) \
do { \
errno = 0; \
if (!(c)) \
t_error("%s failed (errno = %d)\n", #c, errno); \
} while (0)

// Pair with sockets-server-udp-blocking-roundtrips.c. Many blocking send/recv
// cycles exercise wasip3 subtask completion on both directions. On wasip3 CI
// this runs under -Smax-resources=128 so missing subtask drops fail the
// resource table while correct drops stay green (see server file comment).
#define NROUNDS 200
#define BUFSIZE 256

void test_udp_client(int server_port) {
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
TEST(socket_fd != -1);
if (socket_fd == -1)
return;

struct sockaddr_in sockaddr_in;
sockaddr_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
sockaddr_in.sin_family = AF_INET;
sockaddr_in.sin_port = htons(server_port);

TEST(connect(socket_fd, (struct sockaddr *)&sockaddr_in,
sizeof(sockaddr_in)) != -1);

for (int i = 0; i < NROUNDS; i++) {
char message[64];
int len = snprintf(message, sizeof(message), "roundtrip-%d", i);
TEST(len > 0 && len < (int)sizeof(message));

char client_buffer[BUFSIZE];
TEST(send(socket_fd, message, len, 0) == len);
int bytes_received = recv(socket_fd, client_buffer, BUFSIZE, 0);
TEST(bytes_received == len);
if (bytes_received == len)
TEST(memcmp(message, client_buffer, len) == 0);
}

close(socket_fd);
}

int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <server_port>\n", argv[0]);
return 1;
}
int port;
if (sscanf(argv[1], "%d", &port) != 1) {
fprintf(stderr, "Invalid port number: %s\n", argv[1]);
return 1;
}
test_udp_client(port);
return t_status;
}
68 changes: 68 additions & 0 deletions test/src/sockets-server-udp-blocking-roundtrips.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "test.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define TEST(c) \
do { \
errno = 0; \
if (!(c)) \
t_error("%s failed (errno = %d)\n", #c, errno); \
} while (0)

// Many blocking UDP recv/send cycles. On wasip3, each blocking wait that
// completes a pending subtask must drop that subtask handle. Clearing the
// handle without drop leaves HostTask entries in wasmtime's concurrent
// resource table. CI runs this pair with -Smax-resources=128 so a leak
// fails with "resource table has no free keys" while a correct drop stays
// under the cap. The server blocks in recvfrom before each client send so
// the receive subtask is often still outstanding when the wait completes.
#define NROUNDS 200
#define BUFSIZE 256

void run_udp_server(void) {
int server_socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
TEST(server_socket_fd != -1);
if (server_socket_fd == -1)
return;

struct sockaddr_in server_address;
socklen_t server_address_len = sizeof(server_address);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = 0;
TEST(bind(server_socket_fd, (struct sockaddr *)&server_address,
sizeof(server_address)) != -1);
if (t_status != 0)
return;

TEST(getsockname(server_socket_fd, (struct sockaddr *)&server_address,
&server_address_len) != -1);

printf("%d\n", ntohs(server_address.sin_port));
fflush(stdout);

for (int i = 0; i < NROUNDS; i++) {
struct sockaddr_in client_address;
socklen_t address_len = sizeof(client_address);
char buffer[BUFSIZE];
int bytes_read = recvfrom(server_socket_fd, buffer, BUFSIZE, 0,
(struct sockaddr *)&client_address, &address_len);
TEST(bytes_read > 0);
if (bytes_read <= 0)
break;
TEST(sendto(server_socket_fd, buffer, bytes_read, 0,
(struct sockaddr *)&client_address, address_len) == bytes_read);
}

close(server_socket_fd);
}

int main(void) {
run_udp_server();
return t_status;
}