Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TCP socket send() immediately back to back after connect() #22630

Merged
Merged
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
37 changes: 20 additions & 17 deletions src/library_sockfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ addToLibrary({
addr,
port,
socket: ws,
dgram_send_queue: []
msg_send_queue: []
};

SOCKFS.websocket_sock_ops.addPeer(sock, peer);
Expand All @@ -254,7 +254,7 @@ addToLibrary({
#if SOCKET_DEBUG
dbg('websocket: queuing port message (port ' + sock.sport + ')');
#endif
peer.dgram_send_queue.push(new Uint8Array([
peer.msg_send_queue.push(new Uint8Array([
255, 255, 255, 255,
'p'.charCodeAt(0), 'o'.charCodeAt(0), 'r'.charCodeAt(0), 't'.charCodeAt(0),
((sock.sport & 0xff00) >> 8) , (sock.sport & 0xff)
Expand Down Expand Up @@ -283,13 +283,13 @@ addToLibrary({
Module['websocket'].emit('open', sock.stream.fd);

try {
var queued = peer.dgram_send_queue.shift();
var queued = peer.msg_send_queue.shift();
while (queued) {
#if SOCKET_DEBUG
dbg('websocket: sending queued data (' + queued.byteLength + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(queued))]);
#endif
peer.socket.send(queued);
queued = peer.dgram_send_queue.shift();
queued = peer.msg_send_queue.shift();
}
} catch (e) {
// not much we can do here in the way of proper error handling as we've already
Expand Down Expand Up @@ -493,8 +493,9 @@ addToLibrary({
sock.daddr = peer.addr;
sock.dport = peer.port;

// always "fail" in non-blocking mode
throw new FS.ErrnoError({{{ cDefs.EINPROGRESS }}});
// because we cannot synchronously block to wait for the WebSocket
// connection to complete, we return here pretending that the connection
// was a success.
},
listen(sock, backlog) {
if (!ENVIRONMENT_IS_NODE) {
Expand Down Expand Up @@ -605,8 +606,10 @@ addToLibrary({
if (sock.type === {{{ cDefs.SOCK_STREAM }}}) {
if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) {
throw new FS.ErrnoError({{{ cDefs.ENOTCONN }}});
#if SOCKET_DEBUG
} else if (dest.socket.readyState === dest.socket.CONNECTING) {
throw new FS.ErrnoError({{{ cDefs.EAGAIN }}});
dbg('socket sendmsg called while socket is still connecting.');
#endif
}
}

Expand All @@ -631,21 +634,21 @@ addToLibrary({
}
#endif

// if we're emulating a connection-less dgram socket and don't have
// a cached connection, queue the buffer to send upon connect and
// lie, saying the data was sent now.
if (sock.type === {{{ cDefs.SOCK_DGRAM }}}) {
if (!dest || dest.socket.readyState !== dest.socket.OPEN) {
// if we're not connected, open a new connection
// if we don't have a cached connectionless UDP datagram connection, or
// the TCP socket is still connecting, queue the message to be sent upon
// connect, and lie, saying the data was sent now.
if (!dest || dest.socket.readyState !== dest.socket.OPEN) {
// if we're not connected, open a new connection
if (sock.type === {{{ cDefs.SOCK_DGRAM }}}) {
if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) {
dest = SOCKFS.websocket_sock_ops.createPeer(sock, addr, port);
}
}
#if SOCKET_DEBUG
dbg('websocket: queuing (' + length + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(data))]);
dbg('websocket: queuing (' + length + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(data))]);
#endif
dest.dgram_send_queue.push(data);
return length;
}
dest.msg_send_queue.push(data);
return length;
}

try {
Expand Down
60 changes: 60 additions & 0 deletions test/sockets/test_sockets_send_while_connecting.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// This test verifies that calling send() back to back right after calling
// connect() succeeds.

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <emscripten/html5.h>
#include <string.h>
#include <stdio.h>

int sock;

EM_BOOL wait_for_recv(double d, void *u) {
// Poll read from the socket to see what we have received
char buf[1024] = {};
recv(sock, buf, sizeof(buf)-1, 0);
if (strlen(buf) > 0) {
printf("%s\n", buf);
if (!strcmp(buf, "Hello")) {
printf("Got hello, test finished.\n");
#ifdef REPORT_RESULT
REPORT_RESULT(0);
#endif
}
}
return EM_TRUE;
}

int main() {
// Connect socket to a WebSocket echo server
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8089)
};
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
printf("socket() failed to error %d\n", sock);
return sock;
}

// Connect to echo server.
int error = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
if (error) {
printf("connect() failed to error %d\n", error);
return error;
}

// Immediately send a message back-to-back from connecting to the socket
const char *msg = "Hello";
ssize_t bytes = send(sock, msg, strlen(msg), 0);
if (bytes != strlen(msg)) {
printf("send() failed to send %d bytes. Return value: %d\n", (int)strlen(msg), (int)bytes);
return bytes;
}

// Asynchronously wait until we get the message echoed back
emscripten_set_timeout_loop(wait_for_recv, 0, 0);
return 0;
}
5 changes: 5 additions & 0 deletions test/test_sockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,11 @@ def test_posix_proxy_sockets(self):
# Build and run the TCP echo client program with Emscripten
self.btest_exit('websocket/tcp_echo_client.c', args=['-lwebsocket', '-sPROXY_POSIX_SOCKETS', '-pthread', '-sPROXY_TO_PTHREAD'])

# Test that calling send() right after a socket connect() works.
def test_sockets_send_while_connecting(self):
with NodeJsWebSocketEchoServerProcess():
self.btest('sockets/test_sockets_send_while_connecting.c', args=['-DSOCKET_DEBUG'], expected='0')


class sockets64(sockets):
def setUp(self):
Expand Down
Loading