Skip to content

Commit dfb73a7

Browse files
committed
windows: Reimplement poll() with select().
WSAPoll doesn't exist before Vista, and it's unreliable until later versions of Windows 10. Using select(), however, works everywhere. Due to the way select() works on Winsock, we take some efforts to work around the built-in FD_SETSIZE limit of 64 by dynamically building our fd_sets to order. This is a trick that only works on Windows, because it doesn't use bitsets, but rather arrays of sockets. With this work in place, we just call poll() as needed, and the rest of SDL_net is blistfully unaware of our select-based implementation. Fixes #146.
1 parent 9e36857 commit dfb73a7

File tree

1 file changed

+110
-1
lines changed

1 file changed

+110
-1
lines changed

src/SDL_net.c

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,115 @@ static int read(SOCKET s, char *buf, size_t count) {
5757
}
5858
return (int)count_received;
5959
}
60-
#define poll WSAPoll
60+
61+
// WSAPoll doesn't exist on Windows before Vista, and isn't reliable before some version of Windows 10,
62+
// so for now we just fake it with select().
63+
static int WindowsPoll(struct pollfd *fds, unsigned int nfds, int timeout)
64+
{
65+
SDL_assert(fds != NULL);
66+
SDL_assert(nfds > 0);
67+
68+
int nreadfds = 0, nwritefds = 0, nexceptfds = nfds;
69+
for (unsigned int i = 0; i < nfds; i++) {
70+
fds[i].revents = 0;
71+
if (fds[i].events & POLLIN) {
72+
nreadfds++;
73+
}
74+
if (fds[i].events & POLLOUT) {
75+
nwritefds++;
76+
}
77+
}
78+
79+
// FD_SETSIZE is 64 on Windows, but they don't use bitsets, so you can
80+
// just supply your own struct that uses any length.
81+
//
82+
// https://devblogs.microsoft.com/oldnewthing/20221102-00/?p=107343
83+
typedef struct WindowsPoll_fd_set {
84+
Uint32 count;
85+
SOCKET sockets[]; // variable-length array
86+
} WindowsPoll_fd_set;
87+
88+
Uint8 stackbuf_read[256]; // in case if we can do this without malloc.
89+
Uint8 stackbuf_write[256]; // in case if we can do this without malloc.
90+
Uint8 stackbuf_except[256]; // in case if we can do this without malloc.
91+
WindowsPoll_fd_set *readfds = NULL;
92+
WindowsPoll_fd_set *writefds = NULL;
93+
WindowsPoll_fd_set *exceptfds = NULL;
94+
95+
bool failed = false;
96+
#define ALLOC_FDSET(typ) { \
97+
if (!failed && (n##typ##fds > 0)) { \
98+
const int len = sizeof (Uint32) + (n##typ##fds * sizeof (SOCKET)); \
99+
if (len < sizeof (stackbuf_##typ)) { \
100+
typ##fds = (WindowsPoll_fd_set *) stackbuf_##typ; \
101+
} else { \
102+
typ##fds = (WindowsPoll_fd_set *) SDL_malloc(len); \
103+
if (!typ##fds) { \
104+
failed = true; \
105+
} \
106+
} \
107+
if (typ##fds) { \
108+
SDL_memset(typ##fds, '\0', len); \
109+
} \
110+
} \
111+
}
112+
ALLOC_FDSET(read);
113+
ALLOC_FDSET(write);
114+
ALLOC_FDSET(except);
115+
#undef ALLOC_FDSET
116+
117+
int retval = -1;
118+
if (!failed) {
119+
for (unsigned int i = 0; i < nfds; i++) {
120+
exceptfds->sockets[exceptfds->count++] = fds[i].fd;
121+
if (fds[i].events & POLLIN) {
122+
readfds->sockets[readfds->count++] = fds[i].fd;
123+
}
124+
if (fds[i].events & POLLOUT) {
125+
writefds->sockets[writefds->count++] = fds[i].fd;
126+
}
127+
}
128+
129+
struct timeval tvtimeout;
130+
struct timeval *ptvtimeout = NULL;
131+
132+
if (timeout >= 0) {
133+
tvtimeout.tv_sec = timeout / 1000;
134+
tvtimeout.tv_usec = (timeout % 1000) * 1000;
135+
ptvtimeout = &tvtimeout;
136+
}
137+
138+
// WinSock's select() ignores the first parameter, since it doesn't use bitsets, and SOCKETs aren't small integers. Just specify zero here.
139+
retval = select(0, (fd_set *) readfds, (fd_set *) writefds, (fd_set *) exceptfds, ptvtimeout);
140+
if (retval > 0) {
141+
#define CHECKSET(typ, flag) { \
142+
if (typ##fds != NULL) { \
143+
for (Uint32 i = 0; i < typ##fds->count; i++) { \
144+
SOCKET sock = typ##fds->sockets[i]; \
145+
for (unsigned int j = 0; j < nfds; j++) { \
146+
if (fds[j].fd == sock) { \
147+
fds[j].revents |= flag; \
148+
} \
149+
} \
150+
} \
151+
} \
152+
}
153+
CHECKSET(read, POLLIN);
154+
CHECKSET(write, POLLOUT);
155+
CHECKSET(except, POLLERR);
156+
#undef CHECKSET
157+
}
158+
}
159+
160+
if ((void *) readfds != (void *) stackbuf_read) { SDL_free(readfds); }
161+
if ((void *) writefds != (void *) stackbuf_write) { SDL_free(writefds); }
162+
if ((void *) exceptfds != (void *) stackbuf_except) { SDL_free(exceptfds); }
163+
164+
return retval;
165+
}
166+
167+
#define poll WindowsPoll
168+
61169
#else
62170
#include <sys/types.h>
63171
#include <sys/socket.h>
@@ -1950,6 +2058,7 @@ int NET_WaitUntilInputAvailable(void **vsockets, int numsockets, int timeoutms)
19502058
return SetLastSocketError("Socket poll failed");
19512059
}
19522060

2061+
// !!! FIXME: skip this loop if rc == 0.
19532062
pfd = &pfds[0];
19542063
for (int i = 0; i < numsockets; i++) {
19552064
NET_GenericSocket *sock = sockets[i];

0 commit comments

Comments
 (0)