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

termios non canonical mode reads should not block #3507

Closed
markpizz opened this issue Aug 28, 2018 · 7 comments
Closed

termios non canonical mode reads should not block #3507

markpizz opened this issue Aug 28, 2018 · 7 comments
Labels

Comments

@markpizz
Copy link

  • Your Windows build number: (Type ver at a Windows Command Prompt)

Microsoft Windows [Version 10.0.17134.228]

  • What's wrong / what should be happening instead:

tcsetattr() configures non canonical mode, but reads in non-canonical mode block indefinitely. The below example program blocks on WSL, and doesn't on Linux.

The termios man page describes non-canonical mode behaviors:

       In noncanonical mode input is available immediately (without the user
       having to type a line-delimiter character), no input processing is
       performed, and line editing is disabled.  The read buffer will only
       accept 4095 chars; this provides the necessary space for a newline
       char if the input mode is switched to canonical.  The settings of MIN
       (c_cc[VMIN]) and TIME (c_cc[VTIME]) determine the circumstances in
       which a read(2) completes; there are four distinct cases:

       MIN == 0, TIME == 0 (polling read)
              If data is available, read(2) returns immediately, with the
              lesser of the number of bytes available, or the number of
              bytes requested.  If no data is available, read(2) returns 0.

       MIN > 0, TIME == 0 (blocking read)
              read(2) blocks until MIN bytes are available, and returns up
              to the number of bytes requested.

       MIN == 0, TIME > 0 (read with timeout)
              TIME specifies the limit for a timer in tenths of a second.
              The timer is started when read(2) is called.  read(2) returns
              either when at least one byte of data is available, or when
              the timer expires.  If the timer expires without any input
              becoming available, read(2) returns 0.  If data is already
              available at the time of the call to read(2), the call behaves
              as though the data was received immediately after the call.

       MIN > 0, TIME > 0 (read with interbyte timeout)
              TIME specifies the limit for a timer in tenths of a second.
              Once an initial byte of input becomes available, the timer is
              restarted after each further byte is received.  read(2)
              returns when any of the following conditions is met:

              *  MIN bytes have been received.

              *  The interbyte timer expires.

              *  The number of bytes requested by read(2) has been received.
                 (POSIX does not specify this termination condition, and on
                 some other implementations read(2) does not return in this
                 case.)

              Because the timer is started only after the initial byte
              becomes available, at least one byte will be read.  If data is
              already available at the time of the call to read(2), the call
              behaves as though the data was received immediately after the
              call.

       POSIX does not specify whether the setting of the O_NONBLOCK file
       status flag takes precedence over the MIN and TIME settings.  If
       O_NONBLOCK is set, a read(2) in noncanonical mode may return immedi‐
       ately, regardless of the setting of MIN or TIME.  Furthermore, if no
       data is available, POSIX permits a read(2) in noncanonical mode to
       return either 0, or -1 with errno set to EAGAIN.

My use case sets VMIN and VTIME to 0, which absolutely should not block, and specifically fixing this case shouldn't be hard since if I explicitly use fcntrl to set the fd to non blocking, it doesn't block. This could be done under the covers even a thorough interpretation of VMIN and VTIME might be much harder to do.

  • Test program that demonstrates the problem:
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

int sig_int_happened = 0;

void int_handler (int sig)
{
sig_int_happened = 1;
}

struct termios initial_tty, set_tty;
int initial_fl, set_fl;

int main (int argc, char **argv)
{
time_t start, end;
unsigned char buf[1] = {0xFF};
int count, i;

if (tcgetattr (0, &initial_tty) < 0) {                      /* get old flags */
    fprintf (stderr, "tcgetattr() failed: %s\n", strerror(errno));
    return 1;
    }
set_tty = initial_tty;
set_tty.c_lflag = set_tty.c_lflag & ~(ECHO | ICANON);       /* no echo or edit */
set_tty.c_oflag = set_tty.c_oflag & ~OPOST;                 /* no output edit */
set_tty.c_iflag = set_tty.c_iflag & ~ICRNL;                 /* no cr conversion */
set_tty.c_cc[VINTR] = 5;                                    /* interrupt ^E */
set_tty.c_cc[VQUIT] = 0;                                    /* no quit */
set_tty.c_cc[VERASE] = 0;
set_tty.c_cc[VKILL] = 0;
set_tty.c_cc[VEOF] = 0;
set_tty.c_cc[VEOL] = 0;
set_tty.c_cc[VSTART] = 0;                                /* no host sync */
set_tty.c_cc[VSUSP] = 0;
set_tty.c_cc[VSTOP] = 0;
set_tty.c_cc[VMIN] = 0;                                  /* no waiting */
set_tty.c_cc[VTIME] = 0;
if (tcsetattr (0, TCSAFLUSH, &set_tty) < 0) {
    fprintf (stderr, "tcsetattr() failed: %s\n", strerror(errno));
    return 1;
    }
signal (SIGINT, int_handler);
for (i = 5; i > 0; i--) {
    fprintf (stderr, "Sleeping for %d seconds\r", i);
    sleep (1);
    }
if (sig_int_happened)
    fprintf (stderr, "\nSigInt happened\n");
fprintf (stderr, "\r\nReading a character which should not block: ");
time (&start);
count = read (0, buf, 1);
time (&end);
fprintf (stderr, "read (0, buf, 1) took %d seconds and returned %d, buf[0]=0x%02X\n", 
         (int)(end-start), count, (int)buf[0]);
if (tcsetattr (0, TCSAFLUSH, &initial_tty) < 0) {    /* Restore original settings */
    fprintf (stderr, "tcsetattr() failed: %s\n", strerror(errno));
    return 1;
    }
return 0;
}
@SubhashDoshi
Copy link

I can confirm I have this issue as well. When I set a serial port for non-blocking mode, it appears to block regardless. I tested a similar script as the one above on a standard linux Ubuntu distro against the latest WSL w/ ubuntu and had the same behavior. It also seems to ignore the MIN setting if you set MIN to a number, it returns less than MIN on a blocking read with TIME set to 0?

@markpizz I know your issue is specifically with it always blocking, but did you happen to be able to test if while blocking and setting MIN to a larger number, did the read return less than MIN chars as well? Just curious. Thanks!

@markpizz
Copy link
Author

I did not test what you're asking.

The test program in the report is a minimal use of what my application does/needs.

Sorry.

@cakira
Copy link

cakira commented May 19, 2020

I confirm this error is still present, at least, in WSL 1.

As a workaround, I solved my problem coding with the function select(), as presented in this stack overflow answer.

@pablojimpas
Copy link

I was following this tutorial and I had this issue even in WSL 2.

osa1 referenced this issue in osa1/tiny Nov 27, 2020
Normally term_input is used with non-canonical input with VMIN=0 and
VTIME=0. On Linux in this mode stdin does not block (returns 0 when it's
not ready for reading, see [1] and [2]). However on WSL it doesn't work
that way and we have to set stdin to non-blocking mode to be able to do
`read(stdin)` without blocking.

We now set stdin to non-blocking mode when creating an `Input` and
restore the old flags when dropping it.

Fixes #269

[1]: https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html
[2]: https://www.gnu.org/software/libc/manual/html_node/Noncanonical-Input.html
@osa1
Copy link

osa1 commented Nov 27, 2020

Here's another reproducer:

C program
// clang -Wall main.c -o main -DNONBLOCK

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

void main_loop();
bool read_until_empty();

int main()
{
#if defined(NONBLOCK)
    int old_stdin_flags = fcntl(STDIN_FILENO, F_GETFL);
    fcntl(STDIN_FILENO, F_SETFL, old_stdin_flags | O_NONBLOCK);
    printf("Enabled non-blocking mode\n");
#endif

    bool fail = false;

    int tty = open("/dev/tty", O_RDWR);
    if (tty == -1) {
        printf("Unable to open /dev/tty\n");
        fail = true;
        goto cleanup;
    }

    struct termios old_termios;
    if (tcgetattr(STDIN_FILENO, &old_termios) == -1) {
        printf("tcgetattr failed\n");
        fail = true;
        goto cleanup;
    }

    struct termios new_termios = old_termios;
    cfmakeraw(&new_termios);
    // These really need to be after cfmakeraw() !!!!
    new_termios.c_cc[VMIN] = 0;
    new_termios.c_cc[VTIME] = 0;
    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) == -1) {
        printf("tcsetattr failed\n");
        fail = true;
        goto cleanup;
    }

    printf("Type 'q' to quit.\n");
    main_loop();

cleanup:
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_termios);

#if defined(NONBLOCK)
    fcntl(STDIN_FILENO, F_SETFL, old_stdin_flags);
    printf("Restored stdin flags\n");
#endif

    if (fail) {
        return 1;
    }
}

void main_loop()
{
    printf("main_loop\n");
    struct pollfd fds[1] = { { .fd = STDIN_FILENO, .events = POLLIN } };
    
    for (;;)
    {
        printf("Calling poll()...\n");
        int poll_ret = poll(fds, 1, -1);
        if (poll_ret > 0)
        {
            printf("stdin ready for reading\n");
            if (read_until_empty())
            {
                return;
            }
        }
    }
}

bool read_until_empty()
{
    uint8_t buf[10000];

    for (;;)
    {
        printf("Calling read()...\n");
        ssize_t n_read = read(STDIN_FILENO, buf, 10000);

        if (n_read == -1)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                printf("EAGAIN or EWOULDBLOCK\n");
                return false;
            }
            else
            {
                printf("Error %d: %s\n", errno, strerror(errno));
                return false;
            }
        }
        else if (n_read == 0)
        {
            printf("stdin is empty\n");
            return false;
        }
        else
        {
            printf("Read %zd bytes\n", n_read);
            if (buf[0] == 3 || buf[0] == 113)
            {
                return true;
            }
        }
    }
}

If you run this on Linux (ignore the CPP) you'll see that the program always blocks after printing "Calling poll()...". On WSL it blocks after printing "Calling read()...". In other words, on Linux poll() blocks, on WSL read() blocks, even though with non-canonical input with VMIN=0 and VTIME=0 read() should not block.

@therealkenc
Copy link
Collaborator

Test program that demonstrates the problem:

Thank-you for the tight repro; they are always appreciated. WSL1 still does not respect VMIN/VTIME, as of 21370. Some other tty settings too probably... #169 got kicked to "console" but is really termios.

Better on WSL2.

image

Copy link
Contributor

This issue has been automatically closed since it has not had any activity for the past year. If you're still experiencing this issue please re-file this as a new issue or feature request.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants