Skip to content

Commit b14dddc

Browse files
committed
Emulate Linux socket timeout signaling on Windows
1 parent 65e425f commit b14dddc

File tree

8 files changed

+246
-29
lines changed

8 files changed

+246
-29
lines changed

libc/sock/getsockopt-nt.c

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,40 +48,34 @@ textwindows int sys_getsockopt_nt(struct Fd *fd, int level, int optname,
4848
}
4949

5050
if (level == SOL_SOCKET && optname == SO_ERROR) {
51-
if (in_optlen >= sizeof(int)) {
52-
int err;
53-
uint32_t len = sizeof(err);
54-
if (__imp_getsockopt(fd->handle, SOL_SOCKET, SO_ERROR, &err, &len) == -1)
55-
return __winsockerr();
56-
*(int *)out_opt_optval = __dos2errno(err);
57-
*inout_optlen = sizeof(int);
58-
} else {
51+
if (in_optlen < sizeof(int))
5952
return einval();
60-
}
53+
int err;
54+
uint32_t len = sizeof(err);
55+
if (__imp_getsockopt(fd->handle, SOL_SOCKET, SO_ERROR, &err, &len) == -1)
56+
return __winsockerr();
57+
*(int *)out_opt_optval = __dos2errno(err);
58+
*inout_optlen = sizeof(int);
6159
}
6260

6361
if (level == SOL_SOCKET &&
6462
(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
65-
if (in_optlen >= sizeof(struct timeval)) {
66-
if (optname == SO_RCVTIMEO) {
67-
ms = fd->rcvtimeo;
68-
} else {
69-
ms = fd->sndtimeo;
70-
}
71-
((struct timeval *)out_opt_optval)->tv_sec = ms / 1000;
72-
((struct timeval *)out_opt_optval)->tv_usec = ms % 1000 * 1000;
73-
*inout_optlen = sizeof(struct timeval);
74-
return 0;
75-
} else {
63+
if (in_optlen < sizeof(struct timeval))
7664
return einval();
65+
if (optname == SO_RCVTIMEO) {
66+
ms = fd->rcvtimeo;
67+
} else {
68+
ms = fd->sndtimeo;
7769
}
70+
*(struct timeval *)out_opt_optval = timeval_frommillis(ms);
71+
*inout_optlen = sizeof(struct timeval);
72+
return 0;
7873
}
7974

8075
// TODO(jart): Use WSAIoctl?
8176
if (__imp_getsockopt(fd->handle, level, optname, out_opt_optval,
82-
inout_optlen) == -1) {
77+
inout_optlen) == -1)
8378
return __winsockerr();
84-
}
8579

8680
if (level == SOL_SOCKET) {
8781
if (optname == SO_LINGER && in_optlen == sizeof(struct linger)) {

libc/sock/recv.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
3939
* @cancelationpoint
4040
* @asyncsignalsafe
41-
* @restartable (unless SO_RCVTIMEO)
41+
* @restartable (unless SO_RCVTIMEO on Linux or Windows)
4242
*/
4343
ssize_t recv(int fd, void *buf, size_t size, int flags) {
4444
ssize_t rc;

libc/sock/recvfrom.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
5050
* @cancelationpoint
5151
* @asyncsignalsafe
52-
* @restartable (unless SO_RCVTIMEO)
52+
* @restartable (unless SO_RCVTIMEO on Linux or Windows)
5353
*/
5454
ssize_t recvfrom(int fd, void *buf, size_t size, int flags,
5555
struct sockaddr *opt_out_srcaddr,

libc/sock/send.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
5252
* @cancelationpoint
5353
* @asyncsignalsafe
54-
* @restartable (unless SO_RCVTIMEO)
54+
* @restartable (unless SO_SNDTIMEO on Linux or Windows)
5555
*/
5656
ssize_t send(int fd, const void *buf, size_t size, int flags) {
5757
ssize_t rc;

libc/sock/sendto.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
5252
* @cancelationpoint
5353
* @asyncsignalsafe
54-
* @restartable (unless SO_RCVTIMEO)
54+
* @restartable (unless SO_SNDTIMEO on Linux or Windows)
5555
*/
5656
ssize_t sendto(int fd, const void *buf, size_t size, int flags,
5757
const struct sockaddr *opt_addr, uint32_t addrsize) {

libc/sock/setsockopt-nt.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,25 @@ textwindows int sys_setsockopt_nt(struct Fd *fd, int level, int optname,
3636
const void *optval, uint32_t optlen) {
3737

3838
// socket read/write timeouts
39+
// timeout of zero means wait forever (default)
3940
if (level == SOL_SOCKET &&
4041
(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
41-
if (!(optval && optlen == sizeof(struct timeval)))
42+
if (!optval)
43+
return einval();
44+
if (optlen < sizeof(struct timeval))
4245
return einval();
4346
const struct timeval *tv = optval;
4447
int64_t ms = timeval_tomillis(*tv);
4548
if (ms > -1u)
46-
ms = 0; // wait forever (default) yes zero actually means this
49+
ms = -1u;
4750
if (optname == SO_RCVTIMEO)
4851
fd->rcvtimeo = ms;
4952
if (optname == SO_SNDTIMEO)
5053
fd->sndtimeo = ms;
5154
return 0; // we want to handle this on our own
5255
}
5356

54-
// how to make close() a blocking i/o call
57+
// how to make close() a blocking i/o call lool
5558
union {
5659
uint32_t millis;
5760
struct linger_nt linger;

libc/sock/winsockblock.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ __winsock_block(int64_t handle, uint32_t flags, int nonblock,
191191
// check if signal handler without SA_RESTART was called
192192
if (handler_was_called & SIG_HANDLED_NO_RESTART)
193193
return eintr();
194+
195+
// emulates linux behavior of having timeouts @norestart
196+
if (handler_was_called & SIG_HANDLED_SA_RESTART)
197+
if (srwtimeout)
198+
return eintr();
194199
}
195200

196201
// otherwise try the i/o operation again
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#include <arpa/inet.h>
2+
#include <cosmo.h>
3+
#include <errno.h>
4+
#include <fcntl.h>
5+
#include <netinet/in.h>
6+
#include <pthread.h>
7+
#include <signal.h>
8+
#include <stdatomic.h>
9+
#include <stdio.h>
10+
#include <stdlib.h>
11+
#include <string.h>
12+
#include <sys/socket.h>
13+
#include <sys/time.h>
14+
#include <sys/types.h>
15+
#include <unistd.h>
16+
#include "libc/limits.h"
17+
18+
/**
19+
* @fileoverview SO_RCVTIMEO + SA_RESTART interaction test
20+
*
21+
* This code tests that setting a read timeout on a socket will cause
22+
* read() to change its signal handling behavior from @restartable to
23+
* @norestart. This is currently the case on GNU/Systemd and Windows.
24+
*/
25+
26+
struct sockaddr_in serv_addr;
27+
atomic_bool g_ready_for_conn;
28+
atomic_bool g_ready_for_data;
29+
atomic_bool g_ready_for_more;
30+
atomic_bool g_ready_for_exit;
31+
atomic_bool got_sigusr1;
32+
atomic_bool got_sigusr2;
33+
34+
void on_sigusr1(int sig) {
35+
got_sigusr1 = true;
36+
}
37+
38+
void on_sigusr2(int sig) {
39+
got_sigusr2 = true;
40+
}
41+
42+
void *server_thread(void *arg) {
43+
int server, client;
44+
struct timeval timeout;
45+
socklen_t len;
46+
struct sockaddr_in cli_addr;
47+
48+
// create listening socket
49+
server = socket(AF_INET, SOCK_STREAM, 0);
50+
if (server == -1) {
51+
perror("socket");
52+
exit(31);
53+
}
54+
55+
// initialize server address
56+
memset(&serv_addr, 0, sizeof(serv_addr));
57+
serv_addr.sin_family = AF_INET;
58+
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
59+
serv_addr.sin_port = htons(0);
60+
61+
// bind socket
62+
if (bind(server, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
63+
perror("bind");
64+
exit(32);
65+
}
66+
67+
// get assigned port
68+
len = sizeof(serv_addr);
69+
if (getsockname(server, (struct sockaddr *)&serv_addr, &len)) {
70+
perror("getsockname");
71+
exit(30);
72+
}
73+
74+
// listen on the socket
75+
if (listen(server, SOMAXCONN)) {
76+
perror("listen");
77+
exit(33);
78+
}
79+
80+
// wake main thread
81+
g_ready_for_conn = true;
82+
83+
// accept connection
84+
len = sizeof(cli_addr);
85+
client = accept(server, (struct sockaddr *)&cli_addr, &len);
86+
if (client == -1) {
87+
perror("accept");
88+
exit(35);
89+
}
90+
91+
// wake main thread
92+
g_ready_for_data = true;
93+
94+
// check read() has @restartable behavior
95+
char buf[1];
96+
int rc = read(client, buf, 1);
97+
if (rc != -1)
98+
exit(35);
99+
if (errno != EINTR)
100+
exit(36);
101+
if (!got_sigusr1)
102+
exit(37);
103+
if (!got_sigusr2)
104+
exit(38);
105+
got_sigusr1 = false;
106+
got_sigusr2 = false;
107+
108+
// install a socket receive timeout
109+
timeout.tv_sec = 5000000;
110+
timeout.tv_usec = 0;
111+
if (setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &timeout,
112+
sizeof(timeout) + !IsNetbsd())) {
113+
perror("setsockopt");
114+
exit(34);
115+
}
116+
117+
// wake main thread
118+
g_ready_for_more = true;
119+
120+
// check read() has @norestart behavior
121+
rc = read(client, buf, 1);
122+
if (rc != -1)
123+
exit(35);
124+
if (errno != EINTR)
125+
exit(36);
126+
if (!got_sigusr1)
127+
exit(37);
128+
129+
// here's the whammy
130+
if (IsLinux() || IsWindows()) {
131+
if (got_sigusr2)
132+
exit(38);
133+
} else {
134+
if (!got_sigusr2)
135+
exit(38);
136+
}
137+
138+
// wait for main thread
139+
for (;;)
140+
if (g_ready_for_exit)
141+
break;
142+
143+
// close listening socket
144+
if (close(server))
145+
exit(40);
146+
if (close(client))
147+
exit(39);
148+
return 0;
149+
}
150+
151+
int main() {
152+
153+
// handle signals
154+
struct sigaction sa = {0};
155+
sa.sa_handler = on_sigusr1;
156+
sa.sa_flags = SA_RESTART;
157+
sigaction(SIGUSR1, &sa, 0);
158+
sa.sa_handler = on_sigusr2;
159+
sa.sa_flags = 0;
160+
sigaction(SIGUSR2, &sa, 0);
161+
162+
// create server thread
163+
pthread_t th;
164+
if (pthread_create(&th, 0, server_thread, 0))
165+
return 1;
166+
167+
// wait for thread
168+
for (;;)
169+
if (g_ready_for_conn)
170+
break;
171+
172+
// create socket
173+
int client = socket(AF_INET, SOCK_STREAM, 0);
174+
if (client == -1) {
175+
perror("socket");
176+
return 2;
177+
}
178+
179+
// connect to server
180+
if (connect(client, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
181+
perror("connect");
182+
return 3;
183+
}
184+
185+
// wait for thread
186+
for (;;)
187+
if (g_ready_for_data)
188+
break;
189+
190+
usleep(100e3);
191+
if (pthread_kill(th, SIGUSR1))
192+
return 4;
193+
194+
usleep(100e3);
195+
if (pthread_kill(th, SIGUSR2))
196+
return 5;
197+
198+
// wait for thread
199+
for (;;)
200+
if (g_ready_for_more)
201+
break;
202+
203+
usleep(100e3);
204+
if (pthread_kill(th, SIGUSR1))
205+
return 4;
206+
207+
usleep(400e3);
208+
if (pthread_kill(th, SIGUSR2))
209+
return 5;
210+
211+
g_ready_for_exit = true;
212+
213+
if (pthread_join(th, 0))
214+
return 20;
215+
}

0 commit comments

Comments
 (0)