inputReady.c 15.2 KB
Newer Older
1
/*
2
 * (c) The GRASP/AQUA Project, Glasgow University, 1994-2002
3
 *
4
 * hWaitForInput Runtime Support
5 6
 */

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/* FD_SETSIZE defaults to 64 on Windows, which makes even the most basic
 * programs break that use select() on a socket FD.
 * Thus we raise it here (before any #include of network-related headers)
 * to 1024 so that at least those programs would work that would work on
 * Linux if that used select() (luckily it uses poll() by now).
 * See https://ghc.haskell.org/trac/ghc/ticket/13497#comment:23
 * The real solution would be to remove all uses of select()
 * on Windows, too, and use IO Completion Ports instead.
 * Note that on Windows, one can simply define FD_SETSIZE to the desired
 * size before including Winsock2.h, as described here:
 *   https://msdn.microsoft.com/en-us/library/windows/desktop/ms740141(v=vs.85).aspx
 */
#if defined(_WIN32)
#define FD_SETSIZE 1024
#endif

23 24
/* select and supporting types is not Posix */
/* #include "PosixSource.h" */
25 26
#include <limits.h>
#include <stdbool.h>
27
#include "HsBase.h"
28
#include "Rts.h"
29 30 31
#if !defined(_WIN32)
#include <poll.h>
#endif
32

33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
/*
 * Returns a timeout suitable to be passed into poll().
 *
 * If `infinite`, `remaining` is ignored.
 */
static inline
int
compute_poll_timeout(bool infinite, Time remaining)
{
    if (infinite) return -1;

    if (remaining < 0) return 0;

    if (remaining > MSToTime(INT_MAX)) return INT_MAX;

    return TimeToMS(remaining);
}

#if defined(_WIN32)
/*
 * Returns a timeout suitable to be passed into select() on Windows.
 *
 * The given `remaining_tv` serves as a storage for the timeout
 * when needed, but callers should use the returned value instead
 * as it will not be filled in all cases.
 *
 * If `infinite`, `remaining` is ignored and `remaining_tv` not touched
 * (and may be passed as NULL in that case).
 */
static inline
struct timeval *
compute_windows_select_timeout(bool infinite, Time remaining,
                               /* out */ struct timeval * remaining_tv)
{
    if (infinite) {
        return NULL;
    }

    ASSERT(remaining_tv);

    if (remaining < 0) {
        remaining_tv->tv_sec = 0;
        remaining_tv->tv_usec = 0;
    } else if (remaining > MSToTime(LONG_MAX)) {
        remaining_tv->tv_sec = LONG_MAX;
        remaining_tv->tv_usec = LONG_MAX;
    } else {
        remaining_tv->tv_sec  = TimeToMS(remaining) / 1000;
        remaining_tv->tv_usec = TimeToUS(remaining) % 1000000;
    }

    return remaining_tv;
}

/*
 * Returns a timeout suitable to be passed into WaitForSingleObject() on
 * Windows.
 *
 * If `infinite`, `remaining` is ignored.
 */
static inline
DWORD
compute_WaitForSingleObject_timeout(bool infinite, Time remaining)
{
    // WaitForSingleObject() has the fascinating delicacy behaviour
    // that it waits indefinitely if the `DWORD dwMilliseconds`
    // is set to 0xFFFFFFFF (the maximum DWORD value), which is
    // 4294967295 seconds == ~49.71 days
    // (the Windows API calls this constant INFINITE...).
    //   https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx
    //
    // We ensure that if accidentally `remaining == 4294967295`, it does
    // NOT wait forever, by never passing that value to
    // WaitForSingleObject() (so, never returning it from this function),
    // unless `infinite`.

    if (infinite) return INFINITE;

    if (remaining < 0) return 0;

    if (remaining >= MSToTime(INFINITE)) return INFINITE - 1;

    return (DWORD) TimeToMS(remaining);
}
#endif

119 120
/*
 * inputReady(fd) checks to see whether input is available on the file
121 122
 * descriptor 'fd' within 'msecs' milliseconds (or indefinitely if 'msecs' is
 * negative). "Input is available" is defined as 'can I safely read at least a
123 124 125 126 127
 * *character* from this file object without blocking?' (this does not work
 * reliably on Linux when the fd is a not-O_NONBLOCK socket, so if you pass
 * socket fds to this function, ensure they have O_NONBLOCK;
 * see `man 2 poll` and `man 2 select`, and
 * https://ghc.haskell.org/trac/ghc/ticket/13497#comment:26).
128 129 130
 *
 * This function blocks until either `msecs` have passed, or input is
 * available.
131 132 133 134
 *
 * Returns:
 *   1 => Input ready, 0 => not ready, -1 => error
 * On error, sets `errno`.
135 136
 */
int
137
fdReady(int fd, bool write, int64_t msecs, bool isSock)
138
{
139 140
    bool infinite = msecs < 0;

141
    // if we need to track the time then record the end time in case we are
142
    // interrupted.
143
    Time endTime = 0;
144
    if (msecs > 0) {
145
        endTime = getProcessElapsedTime() + MSToTime(msecs);
146 147
    }

148 149 150 151 152
    // Invariant of all code below:
    // If `infinite`, then `remaining` and `endTime` are never used.

    Time remaining = MSToTime(msecs);

153 154 155
#if !defined(_WIN32)
    struct pollfd fds[1];

156 157 158 159
    fds[0].fd = fd;
    fds[0].events = write ? POLLOUT : POLLIN;
    fds[0].revents = 0;

160 161 162
    // The code below tries to make as few syscalls as possible;
    // in particular, it eschews getProcessElapsedTime() calls
    // when `infinite` or `msecs == 0`.
163

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
    // We need to wait in a loop because poll() accepts `int` but `msecs` is
    // `int64_t`, and because signals can interrupt it.

    while (true) {
        int res = poll(fds, 1, compute_poll_timeout(infinite, remaining));

        if (res < 0 && errno != EINTR)
            return (-1); // real error; errno is preserved

        if (res > 0)
            return 1; // FD has new data

        if (res == 0 && !infinite && remaining <= MSToTime(INT_MAX))
            return 0; // FD has no new data and we've waited the full msecs

        // Non-exit cases
        CHECK( ( res < 0 && errno == EINTR ) || // EINTR happened
               // need to wait more
               ( res == 0 && (infinite ||
                              remaining > MSToTime(INT_MAX)) ) );

        if (!infinite) {
            Time now = getProcessElapsedTime();
            if (now >= endTime) return 0;
            remaining = endTime - now;
189 190 191
        }
    }

192
#else
193 194

    if (isSock) {
195
        int maxfd;
196
        fd_set rfd, wfd;
197 198
        struct timeval remaining_tv;

199
        if ((fd >= (int)FD_SETSIZE) || (fd < 0)) {
200
            barf("fdReady: fd is too big: %d but FD_SETSIZE is %d", fd, (int)FD_SETSIZE);
201
        }
202 203
        FD_ZERO(&rfd);
        FD_ZERO(&wfd);
204 205 206 207 208
        if (write) {
            FD_SET(fd, &wfd);
        } else {
            FD_SET(fd, &rfd);
        }
209 210 211 212 213 214

        /* select() will consider the descriptor set in the range of 0 to
         * (maxfd-1)
         */
        maxfd = fd + 1;

215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
        // We need to wait in a loop because the `timeval` `tv_*` members
        // passed into select() accept are `long` (which is 32 bits on 32-bit
        // and 64-bit Windows), but `msecs` is `int64_t`, and because signals
        // can interrupt it.
        //   https://msdn.microsoft.com/en-us/library/windows/desktop/ms740560(v=vs.85).aspx
        //   https://stackoverflow.com/questions/384502/what-is-the-bit-size-of-long-on-64-bit-windows#384672

        while (true) {
            int res = select(maxfd, &rfd, &wfd, NULL,
                             compute_windows_select_timeout(infinite, remaining,
                                                            &remaining_tv));

            if (res < 0 && errno != EINTR)
                return (-1); // real error; errno is preserved

            if (res > 0)
                return 1; // FD has new data

            if (res == 0 && !infinite && remaining <= MSToTime(INT_MAX))
                return 0; // FD has no new data and we've waited the full msecs

            // Non-exit cases
            CHECK( ( res < 0 && errno == EINTR ) || // EINTR happened
                   // need to wait more
                   ( res == 0 && (infinite ||
                                  remaining > MSToTime(INT_MAX)) ) );

            if (!infinite) {
                Time now = getProcessElapsedTime();
                if (now >= endTime) return 0;
                remaining = endTime - now;
246 247 248
            }
        }

249
    } else {
250 251
        DWORD rc;
        HANDLE hFile = (HANDLE)_get_osfhandle(fd);
252
        DWORD avail = 0;
253

254
        switch (GetFileType(hFile)) {
255

256 257 258 259
            case FILE_TYPE_CHAR:
                {
                    INPUT_RECORD buf[1];
                    DWORD count;
260

261 262 263 264 265 266 267
                    // nightmare.  A Console Handle will appear to be ready
                    // (WaitForSingleObject() returned WAIT_OBJECT_0) when
                    // it has events in its input buffer, but these events might
                    // not be keyboard events, so when we read from the Handle the
                    // read() will block.  So here we try to discard non-keyboard
                    // events from a console handle's input buffer and then try
                    // the WaitForSingleObject() again.
268

269
                    while (1) // keep trying until we find a real key event
270
                    {
271 272 273
                        rc = WaitForSingleObject(
                            hFile,
                            compute_WaitForSingleObject_timeout(infinite, remaining));
274
                        switch (rc) {
275 276 277 278 279 280 281 282 283
                            case WAIT_TIMEOUT:
                                // We need to use < here because if remaining
                                // was INFINITE, we'll have waited for
                                // `INFINITE - 1` as per
                                // compute_WaitForSingleObject_timeout(),
                                // so that's 1 ms too little. Wait again then.
                                if (!infinite && remaining < MSToTime(INFINITE))
                                    return 0;
                                goto waitAgain;
284 285
                            case WAIT_OBJECT_0: break;
                            default: /* WAIT_FAILED */ maperrno(); return -1;
286 287
                        }

288
                        while (1) // discard non-key events
289
                        {
290
                            BOOL success = PeekConsoleInput(hFile, buf, 1, &count);
291
                            // printf("peek, rc=%d, count=%d, type=%d\n", rc, count, buf[0].EventType);
292
                            if (!success) {
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
                                rc = GetLastError();
                                if (rc == ERROR_INVALID_HANDLE || rc == ERROR_INVALID_FUNCTION) {
                                    return 1;
                                } else {
                                    maperrno();
                                    return -1;
                                }
                            }

                            if (count == 0) break; // no more events => wait again

                            // discard console events that are not "key down", because
                            // these will also be discarded by ReadFile().
                            if (buf[0].EventType == KEY_EVENT &&
                                buf[0].Event.KeyEvent.bKeyDown &&
                                buf[0].Event.KeyEvent.uChar.AsciiChar != '\0')
                            {
                                // it's a proper keypress:
                                return 1;
                            }
                            else
314
                            {
315 316
                                // it's a non-key event, a key up event, or a
                                // non-character key (e.g. shift).  discard it.
317 318
                                BOOL success = ReadConsoleInput(hFile, buf, 1, &count);
                                if (!success) {
319 320 321 322 323 324 325 326
                                    rc = GetLastError();
                                    if (rc == ERROR_INVALID_HANDLE || rc == ERROR_INVALID_FUNCTION) {
                                        return 1;
                                    } else {
                                        maperrno();
                                        return -1;
                                    }
                                }
327
                            }
328
                        }
329

330 331 332
                        Time now;
                    waitAgain:
                        now = getProcessElapsedTime();
333
                        remaining = endTime - now;
334
                    }
335
                }
336

337 338 339 340
            case FILE_TYPE_DISK:
                // assume that disk files are always ready:
                return 1;

341
            case FILE_TYPE_PIPE: {
342 343 344
                // WaitForMultipleObjects() doesn't work for pipes (it
                // always returns WAIT_OBJECT_0 even when no data is
                // available).  If the HANDLE is a pipe, therefore, we try
345
                // PeekNamedPipe():
346
                //
347 348 349
                // PeekNamedPipe() does not block, so if it returns that
                // there is no new data, we have to sleep and try again.
                while (avail == 0) {
350 351
                    BOOL success = PeekNamedPipe( hFile, NULL, 0, NULL, &avail, NULL );
                    if (success) {
352 353 354
                        if (avail != 0) {
                            return 1;
                        } else { // no new data
355 356 357 358 359 360
                            if (infinite) {
                                Sleep(1); // 1 millisecond (smallest possible time on Windows)
                                continue;
                            } else if (msecs == 0) {
                                return 0;
                            } else {
361 362 363 364 365 366
                                Time now = getProcessElapsedTime();
                                if (now >= endTime) return 0;
                                Sleep(1); // 1 millisecond (smallest possible time on Windows)
                                continue;
                            }
                        }
367
                    } else {
368 369 370 371 372 373 374 375
                        rc = GetLastError();
                        if (rc == ERROR_BROKEN_PIPE) {
                            return 1; // this is probably what we want
                        }
                        if (rc != ERROR_INVALID_HANDLE && rc != ERROR_INVALID_FUNCTION) {
                            maperrno();
                            return -1;
                        }
376
                    }
377
                }
378 379
            }
            /* PeekNamedPipe didn't work - fall through to the general case */
380

381
            default:
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
                while (true) {
                    rc = WaitForSingleObject(
                        hFile,
                        compute_WaitForSingleObject_timeout(infinite, remaining));

                    switch (rc) {
                        case WAIT_TIMEOUT:
                            // We need to use < here because if remaining
                            // was INFINITE, we'll have waited for
                            // `INFINITE - 1` as per
                            // compute_WaitForSingleObject_timeout(),
                            // so that's 1 ms too little. Wait again then.
                            if (!infinite && remaining < MSToTime(INFINITE))
                                return 0;
                            break;
                        case WAIT_OBJECT_0: return 1;
                        default: /* WAIT_FAILED */ maperrno(); return -1;
                    }

                    // EINTR or a >(INFINITE - 1) timeout completed
                    if (!infinite) {
                        Time now = getProcessElapsedTime();
                        if (now >= endTime) return 0;
                        remaining = endTime - now;
                    }
407
                }
408
        }
409
    }
410
#endif
411
}