Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C socket blocking call

I wonder about the behaviour of socket on blocking and nonblocking actions. What happens to threads blocking on socket when the socket blocking mode changes ? Here is the scenario; thread1(T1) creates a UDP socket and

fd = socket(AF_INET , SOCK_DGRAM, 0);

T1 waiting(sleeping) for receive

recv(fd, buf , sizeof(buf) , 0);

and thread2(T2) changes socket mode to non-blocking before socket receive any data

fcntl(fd, F_SETFL, O_NONBLOCK);

what happens to T1 ? Is it signalled/waked because the socket is no more blocking ?

like image 586
Qxtrml Avatar asked Aug 11 '17 13:08

Qxtrml


People also ask

What is a blocking call in C?

Blocking means that execution of your code (in that thread) will stop for the duration of the call. Essentially, the function call will not return until the blocking operation is complete. A blocking read will wait until there is data available (or a timeout, if any, expires), and then returns from the function call.

Is socket accept a blocking call?

The accept function can block the caller until a connection is present if no pending connections are present on the queue, and the socket is marked as blocking. If the socket is marked as nonblocking and no pending connections are present on the queue, accept returns an error as described in the following.

How do I make a socket non-blocking?

To mark a socket as non-blocking, we use the fcntl system call. Here's an example: int flags = guard(fcntl(socket_fd, F_GETFL), "could not get file flags"); guard(fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK), "could not set file flags"); Here's a complete example.

Is read a blocking call in C?

By default, read() waits until at least one byte is available to return to the application; this default is called “blocking” mode.


2 Answers

The behavior is literally unspecified: fcntl is not required to unblock any threads.

Linux just sets the flag in the file description struct file and returns without unblocking any blocked threads.

A thread already blocked in recv can be scheduled to run only if:

  • data to read becomes available;
  • or an error condition on the file descriptor is detected (FIN/RST, socket read timeout, TCP keep-alive failure, the file descriptor is closed by another thread);
  • or a signal is received and the signal disposition does not include SA_RESTART;
  • or it is pthread_cancelled.

The fact that you are trying to change flags of a file descriptor of another thread suggests that your design requires a review. Ideally, threads must not share any data and not poke at each other's state, rather they should use message passing to communicate with each other.

like image 124
Maxim Egorushkin Avatar answered Oct 30 '22 21:10

Maxim Egorushkin


You made me curious. First, it's quite obvious that since there is no standard that specifies that the socket should wake up, it will not be awoken because it would be quite a pain to implement this (since the non-blocking flag is in a different layer than where the socket is blocking). So we can say quite confidently that the socket will not wake up until we receive a packet. Or will it?

#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <err.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>

int sock;

static void
sighand(int s)
{
        write(1, "sig\n", 4);
}

static void *
rcv(void *v)
{
        struct sigaction sa;

        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = sighand;
        sa.sa_flags = SA_RESTART;
        if (sigaction(SIGUSR1, &sa, NULL) == -1)
                err(1, "sigaction");

        char buf[64];
        ssize_t sz = recv(sock, buf, sizeof(buf), 0);
        printf("recv %d\n", (int)sz);
        return NULL;
}

pthread_t t1, t2;

static void *
beeper(void *v)
{
        for (;;) {
                nanosleep(&((struct timespec){.tv_sec = 1}), NULL);
                if (pthread_kill(t1, SIGUSR1))
                        errx(1, "pthread_kill");
                printf("beep\n");
        }
}

int
main(int argc, char **argv)
{
        if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
                err(1, "socket");

        struct sockaddr_in sin;
        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(4711);
        sin.sin_addr.s_addr = htonl(INADDR_ANY);
        if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1)
                err(1, "bind");

        if (pthread_create(&t1, NULL, rcv, NULL))
                errx(1, "pthread_create");

        if (pthread_create(&t2, NULL, beeper, NULL))
                errx(1, "pthread_create");

        /* pretend that this is correct synchronization. */
        nanosleep(&((struct timespec){.tv_sec = 3}), NULL);

        printf("setting non-block\n");
        if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1)
                err(1, "fcntl");
        printf("set\n");

        nanosleep(&((struct timespec){.tv_sec = 3}), NULL);

        return 0;
}

The code above (sorry, couldn't make it shorter). Blocks the thread in recv, waits a bit and then sets non-blocking on the file descriptor. As expected nothing happens. But then I added a twist. The receiving thread has a signal handler that wakes up once a second with SA_RESTART. Of course, since we have SA_RESTART recv should not wake up.

On OSX this will "misbehave" if we consider any behavior correct. I'm pretty sure this will behave the same way on all BSDs. Same on Linux. In fact, the way SA_RESTART is usually implemented I'm pretty sure this will "misbehave" pretty much everywhere.

Funny. This of course doesn't mean that the code above is useful for anything, but it's an interesting curiosity. To answer your question, this is unspecified, but in most cases it won't wake up, unless it will. Please don't do weird things like this.

like image 31
Art Avatar answered Oct 30 '22 22:10

Art