Skip to content

Commit 7f2abe4

Browse files
committed
Previously, if an emulated TCP socket is connect()ed and one attempts to send() back to back right after connecting, the send would fail because the socket is not yet actually connected.
It is a bit hard to imagine where that behavior would be useful. So this PR changes this behavior to be identical to how connectionless UDP sockets are emulated: instead, connect() calls are always pretended to succeed (since we cannot synchronously establish if the connect would fail, so presume it'll work), and all send() calls that are issued while the socket is connecting will be queued to be sent after the socket actually connects.
1 parent a0fc53c commit 7f2abe4

File tree

3 files changed

+90
-17
lines changed

3 files changed

+90
-17
lines changed

src/library_sockfs.js

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ addToLibrary({
241241
addr,
242242
port,
243243
socket: ws,
244-
dgram_send_queue: []
244+
msg_send_queue: []
245245
};
246246

247247
SOCKFS.websocket_sock_ops.addPeer(sock, peer);
@@ -254,7 +254,7 @@ addToLibrary({
254254
#if SOCKET_DEBUG
255255
dbg('websocket queuing port message (port ' + sock.sport + ')');
256256
#endif
257-
peer.dgram_send_queue.push(new Uint8Array([
257+
peer.msg_send_queue.push(new Uint8Array([
258258
255, 255, 255, 255,
259259
'p'.charCodeAt(0), 'o'.charCodeAt(0), 'r'.charCodeAt(0), 't'.charCodeAt(0),
260260
((sock.sport & 0xff00) >> 8) , (sock.sport & 0xff)
@@ -283,13 +283,13 @@ addToLibrary({
283283
Module['websocket'].emit('open', sock.stream.fd);
284284

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

496-
// always "fail" in non-blocking mode
497-
throw new FS.ErrnoError({{{ cDefs.EINPROGRESS }}});
496+
// because we cannot synchronously block to wait for the WebSocket
497+
// connection to complete, we return here pretending that the connection
498+
// was a success.
498499
},
499500
listen(sock, backlog) {
500501
if (!ENVIRONMENT_IS_NODE) {
@@ -606,7 +607,9 @@ addToLibrary({
606607
if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) {
607608
throw new FS.ErrnoError({{{ cDefs.ENOTCONN }}});
608609
} else if (dest.socket.readyState === dest.socket.CONNECTING) {
609-
throw new FS.ErrnoError({{{ cDefs.EAGAIN }}});
610+
#if SOCKET_DEBUG
611+
dbg('socket sendmsg called while socket is still connecting.');
612+
#endif
610613
}
611614
}
612615

@@ -631,21 +634,21 @@ addToLibrary({
631634
}
632635
#endif
633636

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

651654
try {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// This test verifies that calling send() back to back right after calling
2+
// connect() succeeds.
3+
4+
#include <sys/socket.h>
5+
#include <netinet/in.h>
6+
#include <arpa/inet.h>
7+
#include <emscripten/html5.h>
8+
#include <string.h>
9+
#include <stdio.h>
10+
11+
int sock;
12+
13+
EM_BOOL wait_for_recv(double, void *) {
14+
// Poll read from the socket to see what we have received
15+
char buf[1024] = {};
16+
recv(sock, buf, sizeof(buf)-1, 0);
17+
if (strlen(buf) > 0)
18+
{
19+
printf("%s\n", buf);
20+
if (!strcmp(buf, "Hello"))
21+
{
22+
printf("Got hello, test finished.\n");
23+
#ifdef REPORT_RESULT
24+
REPORT_RESULT(0);
25+
#endif
26+
}
27+
}
28+
return EM_TRUE;
29+
}
30+
31+
int main() {
32+
// Connect socket to a WebSocket echo server
33+
sockaddr_in addr = {
34+
.sin_family = AF_INET,
35+
.sin_port = htons(8089)
36+
};
37+
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
38+
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
39+
if (sock < 0)
40+
{
41+
printf("socket() failed to error %d\n", sock);
42+
return sock;
43+
}
44+
45+
// Connect to echo server.
46+
int error = connect(sock, (sockaddr*)&addr, sizeof(addr));
47+
if (error)
48+
{
49+
printf("connect() failed to error %d\n", error);
50+
return error;
51+
}
52+
53+
// Immediately send a message back-to-back from connecting to the socket
54+
const char *msg = "Hello";
55+
ssize_t bytes = send(sock, msg, strlen(msg), 0);
56+
if (bytes != strlen(msg))
57+
{
58+
printf("send() failed to send %d bytes. Return value: %d\n", (int)strlen(msg), (int)bytes);
59+
return bytes;
60+
}
61+
62+
// Asynchronously wait until we get the message echoed back
63+
emscripten_set_timeout_loop(wait_for_recv, 0, 0);
64+
return 0;
65+
}

test/test_sockets.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,11 @@ def test_posix_proxy_sockets(self):
350350
# Build and run the TCP echo client program with Emscripten
351351
self.btest_exit('websocket/tcp_echo_client.c', args=['-lwebsocket', '-sPROXY_POSIX_SOCKETS', '-pthread', '-sPROXY_TO_PTHREAD'])
352352

353+
# Test that multiple pthreads calling send() on the same socket produces correct ordering semantics.
354+
def test_sockets_send_immediately_after_connect(self):
355+
with NodeJsWebSocketEchoServerProcess():
356+
self.btest('sockets/send_immediately_after_connect.cpp', args=['-DSOCKET_DEBUG'], expected='0')
357+
353358

354359
class sockets64(sockets):
355360
def setUp(self):

0 commit comments

Comments
 (0)