Implementing a Timeout for a read in C

2021-04-12 c

Need to wait a specified amount of time for a read call to return before giving up and continuing on?

In Unix when we read from a file we can have a non-blocking read where if the file is not ready the read call will pass through. We can also specify a blocking read where the read will wait forever. What if we want read to wait a specified amount of time not zero or infinity?

This blog post will show 3 ways to implement a timeout on a read call to wait a specified amount of time on a read call before the code continues.

Forewarning, the third method just relates to tty terminals which can also be UARTs.

Examples

  1. Timeout using poll
  2. Timeout using select
  3. Timeout reading from a Terminal (UART)
  4. Which Example to Use?

Using poll to implement a timeout on a read

This example we’ll pass in the file name that we want to read from. If the file is ready to read it will read 1 byte from it. If it is not ready it will wait 3 seconds before timing out. This will all be accomplished by the poll system call.

Example usage

1
2
3
4
5
6
7
$ echo "hello" > world
$ ./poll_timeout world # open a file that is ready for reading
opening /bin/ls read only
file /bin/ls ready to read ... this is where we'd call fread(file)
read 1 bytes from /bin/ls
$ ./poll_timeout /dev/ttys000 # we will timeout after 3 seconds here
poll timed out

The /dev/ttys000 file is a Pseudo-Terminal I found using the who command.

From here we can see that a file which we know is ready

C implementation of a read timeout using poll

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <poll.h>

int
main(int argc, char *argv[])
{
  struct pollfd pfd[1];
  FILE *file;
  int ret;
  char buf[1];

  if(argc != 2)
    {
      fprintf(stderr, "usage: %s file\n", argv[0]);
      return 1;
    }

  printf("opening %s read only\n", argv[1]);

  file = fopen(argv[1], "r");

  if(file == NULL)
    {
      perror("open file");
      fprintf(stderr, "unable to open %s for reading\n", argv[1]);
      return 2;
    }

  pfd[0].fd = fileno(file);
  pfd[0].events = POLLIN;

  ret = poll(pfd, 1, 3000);

  if(ret == 0)
    {
      fprintf(stderr, "poll timed out\n");
    }
  else if (ret < 0)
    {
      perror("poll");
    }
  else if(pfd[0].revents & POLLIN)
    {
      printf("file %s ready to read ... this is where we'd call fread(file)\n", argv[1]);
      ret = fread(buf, 1, sizeof(buf), file);
      printf("read %d bytes from %s\n", ret, argv[1]);
    }

  fclose(file);
  return 0;
}

Using select to implement a timeout on a read

This example is the same as the poll example above, except we will implement it using select.

Example usage

1
2
3
4
5
6
7
$ echo "hello" > world
$ ./select_timeout world # open a file that is ready for reading
opening /bin/ls read only
file /bin/ls ready to read ... this is where we'd call fread(file)
read 1 bytes from /bin/ls
$ ./select_timeout /dev/ttys000 # we will timeout here after 3 seconds
poll timed out on file /dev/ttys000 (3)

C implementation of a read timeout using select

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
#include <stdio.h>
#include <sys/select.h>
#include <sys/time.h>

int
main(int argc, char *argv[])
{
  fd_set rfds;
  struct timeval tv;
  FILE *file;
  int ret, fd;
  char buf[1];

  if(argc != 2)
    {
      fprintf(stderr, "usage: %s file\n", argv[0]);
      return 1;
    }

  printf("opening %s read only\n", argv[1]);

  file = fopen(argv[1], "r");

  if(file == NULL)
    {
      perror("open file");
      fprintf(stderr, "unable to open %s for reading\n", argv[1]);
      return 2;
    }

  fd = fileno(file);
  FD_ZERO(&rfds);
  FD_SET(fd, &rfds);

  tv.tv_sec = 3;
  tv.tv_usec = 0;
  
  ret = select(fd+1, &rfds, NULL, NULL, &tv);

  if(ret == 0)
    {
      fprintf(stderr, "poll timed out on file %s (%d)\n", argv[1], fd);
    }
  else if (ret < 0)
    {
      perror("poll");
    }
  else if(FD_ISSET(fd, &rfds))
    {
      printf("file %s ready to read ... this is where we'd call fread(file)\n", argv[1]);
      ret = fread(buf, 1, sizeof(buf), file);
      printf("read %d bytes from %s\n", ret, argv[1]);
    }

  fclose(file);
  return 0;
}

Implementing a timeout when reading from a Terminal

Here is an example on a Raspberry Pi where we want to read from the /dev/ttyAMA0 terminal which is set to UART0. We don’t have anything connected and will timeout after waiting for 3 seconds. Full documentation on this example can be seen by doing a man 3 termios and is currently documented as such:

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 beā€coming 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.

Example Usage

$ ./tty_timeout /dev/ttyAMA0 # do not expect bytes from this terminal
opening terminal /dev/ttyAMA0
timed out

When we read from a terminal we can also implement a timeout. For example this would be used when reading from a UART on a Raspberry Pi.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 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
#include <stdio.h>

#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <string.h>


static int
tty_open(char *ptyName)
{
  int UART;
  struct termios ttyOrig;

  UART = open(ptyName, O_RDWR | O_NOCTTY);
  if(UART == -1)
    {
      perror("error opening terminal");
      close(UART);
      return -1;
    }

  if(tcgetattr(UART, &ttyOrig) == -1)
    {
      perror("unable to get tty attributes");
      close(UART);
      return -1;
    }

  cfsetispeed(&ttyOrig, B9600);
  cfsetospeed(&ttyOrig, B9600);
  cfmakeraw(&ttyOrig);

  ttyOrig.c_cflag |= CREAD | CLOCAL;

  /* read with timeout
   * read will return with at least one byte available
   * or will timeout
   */
  ttyOrig.c_cc[VMIN] = 0;
  ttyOrig.c_cc[VTIME] = 30; // in tenths of a second

  if(tcsetattr(UART, TCSANOW, &ttyOrig) == -1)
    {
      perror("error setting terminal attributes");
      close(UART);
      return -1;
    }

  tcflush(UART, TCIFLUSH);
  tcdrain(UART);

  return UART;
}

int terminal_open(char *path)
{
  int fd;
  /* check the terminal exits */
  if(access(path, F_OK) == -1)
    return -2;
  fd = tty_open(path);
  return fd;
}

int
main(int argc, char *argv[])
{
  int fd;
  ssize_t bytes;
  char buf[1];
  
  if(argc != 2)
    {
      fprintf(stderr, "usage: %s tty\n", argv[0]);
      return 1;
    }

  printf("opening terminal %s\n", argv[1]);

  fd = terminal_open(argv[1]);
  
  if(fd < 0)
    {
      fprintf(stderr, "unable to open terminal %s\n", argv[1]);
      return 2;
    }

  bytes = read(fd, buf, sizeof(buf));
  if(bytes == 0)
    {
      fprintf(stderr, "timed out\n");
    }
  else if(bytes == -1)
    {
      perror("read");
    }
  else
    {
      printf("read %ld bytes from %s\n", bytes, argv[1]);
    }

  close(fd);
    
  return 0;
}

Which example to use

I wrote a document comparing different system calls for select and poll titled I/O Multiplexing in Unix. It really depends on your use case. Here are some criteria:

  • How many files you’re polling
  • If you need to handle signals
  • If you need the high priority data. E.G. POLLPRI.

We don’t have examples for pselect and epoll, although, they are very similar to the select and poll examples.

The major challenge with all of the I/O multiplexing methods is they have a single timeout for all the file descriptors they are multiplexing. You don’t get fine grained control over each file descriptor.

comments powered by Disqus