Expected result when racing listen(2) on two sockets bound to the same address

From: Alexander Kurtz
Date: Wed May 23 2018 - 06:32:01 EST


[Please keep me CC'ed; I'm not subscribed to the list]

Hi!

The program shown below (also available at [0]) does the following:

* Create two sockets
* Enable SO_REUSEADDR on both
* Bind both sockets to [::1]:12345
* Spawn two threads which both call listen(2) on one socket each
* Check that at least one thread succeeded

Unfortunately, when running this program on Linux 4.16, it is sometimes
possible that neither thread succeeds in calling listen(2):

$ uname -a
Linux shepard 4.16.0-1-amd64 #1 SMP Debian 4.16.5-1 (2018-04-29) x86_64 GNU/Linux
$ time make
cc -Wall -Wextra -pedantic -Werror -O3 listenrace.c -lpthread -o listenrace
for i in `seq 10000`; do ./listenrace; done
listenrace: listenrace.c:58: main: Assertion `result1 == 0 || result2 == 0' failed.
Aborted
listenrace: listenrace.c:58: main: Assertion `result1 == 0 || result2 == 0' failed.
Aborted
listenrace: listenrace.c:58: main: Assertion `result1 == 0 || result2 == 0' failed.
Aborted

real 0m8.201s
user 0m6.801s
sys 0m2.141s
$

As can be seen, on 3 runs (out of 10000) calling listen(2) failed in
*both* threads. Is this to be expected (i.e. "don't do this then") or
could this be some race condition in the Linux kernel?

Best regards

Alexander Kurtz

[0] https://github.com/AlexanderKurtz/listenrace

==> Makefile <==
CFLAGS = -Wall -Wextra -pedantic -Werror -O3
LDLIBS = -lpthread

all: listenrace
for i in `seq 10000`; do ./listenrace; done

listenrace: listenrace.c

clean:
rm 'listenrace'

.PHONY: all clean

==> listenrace.c <==
#include <assert.h> // for assert
#include <netdb.h> // for addrinfo, getaddrinfo
#include <pthread.h> // for pthread_create, pthread_join, pthread_t
#include <stdint.h> // for intptr_t
#include <stdio.h> // for NULL
#include <sys/socket.h> // for bind, setsockopt, listen, socket, AF_INET6

int my_socket (struct addrinfo* address) {
// Create socket
int fd = socket (address->ai_family, address->ai_socktype, 0);
assert (fd >= 0);

// Enable SO_REUSEADDR
assert (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, & (int) { 1 }, sizeof (int)) == 0);

// Bind socket
assert (bind (fd, address->ai_addr, address->ai_addrlen) == 0);

// Return socket
return fd;
}

void* my_listen (void* data) {
int fd = (intptr_t) data;

intptr_t result = listen (fd, 64);

return (void*) result;
}

int main () {
// Create sockaddr struct
struct addrinfo hints = {
.ai_family = AF_INET6,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo* address = NULL;
assert (getaddrinfo ("::1", "12345", &hints, &address) == 0);
assert (address);

// Create sockets
int socket1 = my_socket (address);
int socket2 = my_socket (address);

// Create threads
pthread_t thread1;
pthread_t thread2;
assert (pthread_create (&thread1, NULL, my_listen, (void*) (intptr_t) socket1) == 0);
assert (pthread_create (&thread2, NULL, my_listen, (void*) (intptr_t) socket2) == 0);

// Wait for the threads
void* result1 = NULL;
void* result2 = NULL;
assert (pthread_join (thread1, &result1) == 0);
assert (pthread_join (thread2, &result2) == 0);

// Check that at least one thread could successfully call listen
assert (result1 == 0 || result2 == 0);

return 0;
}

Attachment: signature.asc
Description: This is a digitally signed message part