diff mbox series

accept.2: Added information about what can cause EAGAIN and EWOULDBLOCK errors

Message ID CABMFubf=Ht7xFaN+9jiddDj08D150jwHpio8i09KHCSD_9bK=Q@mail.gmail.com
State Not Applicable
Delegated to: David Miller
Headers show
Series accept.2: Added information about what can cause EAGAIN and EWOULDBLOCK errors | expand

Commit Message

Jeremy Rifkin Nov. 26, 2019, 10:29 p.m. UTC
Hello,
According to the accept.2 man page, EAGAIN and EWOULDBLOCK errors can occur when
"The socket is marked nonblocking and no connections are present to be
accepted". I have found that these errors can also occur when a receive timeout
has been set on the socket and the timeout expires before a connection can be
accepted. This appears to be the same behavior of the recv system call whose man
page states that EAGAIN and EWOULDBLOCK occur when "The socket is marked
nonblocking and the receive operation would block, or a receive timeout had been
set and the timeout expired before data was received."

I've included a test program to demonstrate that accept will fail with exit code
EAGAIN/EWOULDBLOCK when a receive timeout is set and the timeout expires.

This patch applies to the latest version man-pages, 5.04. I have amended
accept.2 to include this second reason why EAGAIN/EWOULDBLOCK can
occur. I have tried to use similar wording to that of the recv.2 man page.

======== Begin Diff ========


========= End Diff =========

- Jeremy Rifkin
// Jeremy M. Rifkin 11.25.19
// Demonstrate that EAGAIN/EWOULDBLOCK can be triggered on accept(2) by a recv timeout
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>

void fail(char* format) {
	fprintf(stderr, format, errno);
	fflush(stderr);
	exit(1);
}

int main (int argc, char *argv[]) {
	int err;
	int socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if(socketfd < 0)
		fail("\x1b[1;35mError:\x1b[0m failed to create socket (errno: %d)... This shouldn't have "
			"happened.\n");

	// Configure socket
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(0); // port 0 for random port
	server.sin_addr.s_addr = htonl(INADDR_ANY);

	// Set reuseaddr
	int opt = 1;
	err = setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	if(err < 0)
		fail("\x1b[1;35mError:\x1b[0m failed to set socket reuseaddr (errno: %d)... This shouldn't "
			"have happened.\n");

	// Configure timeout
	struct timeval timeout;
	timeout.tv_sec = 4; // 4 second timeout
	timeout.tv_usec = 0;
	err = setsockopt(socketfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof timeout);
	if(err < 0)
		fail("\x1b[1;35mError:\x1b[0m failed to set socket recv timeout (errno: %d)... This "
			"shouldn't have happened.\n");

	// Bind the socket
	err = bind(socketfd, (struct sockaddr*) &server, sizeof(server));
	if(err < 0)
		fail("\x1b[1;35mError:\x1b[0m failed to bind socket (errno: %d)... This shouldn't have "
			"happened.\n");

	// Mark socket as passive.
	err = listen(socketfd, 128);
	if(err < 0)
		fail("\x1b[1;35mError:\x1b[0m failed to listen on socket (errno: %d)... This shouldn't "
			"have happened.\n");

	// Get port
	// Serves as both a check and also accommodates for "random" ports.
	struct sockaddr_in addr;
	socklen_t addr_len = sizeof(addr);
	err = getsockname(socketfd, (struct sockaddr*)&addr, &addr_len);
	if(err < 0)
		fail("\x1b[1;35mError:\x1b[0m failed to get socket address (errno: %d)... This shouldn't "
			"have happened.\n");
	printf("Socket listening on port %d\n", ntohs(addr.sin_port));
	
	// Demonstrate recv timeout failure
	printf("\nA 4 second recv timeout has been set on the socket.\n");
	printf("The program will now attempt to accept connections.\n");
	printf("It should fail with error EAGAIN/EWOULDBLOCK roughly\n");
	printf("every 4 seconds.\n\n");
	while(1) {
		struct sockaddr_in client;
		socklen_t client_len = sizeof(client);
		int clientfd = accept(socketfd, (struct sockaddr*)&client, &client_len);

		if(clientfd < 0) {
			if(errno == EAGAIN)
				fprintf(stderr, "\x1b[1;35mError:\x1b[0m Socket failed to accept with error EAGAIN."
					"\n");
			else if(errno == EWOULDBLOCK)
				fprintf(stderr, "\x1b[1;35mError:\x1b[0m Socket failed to accept with error "
					"EWOULDBLOCK.\n");
			else
				fprintf(stderr, "\x1b[1;35mError:\x1b[0m failed to establish new connection "
					"(errno: %d)... This shouldn't have happened.\n",
					errno);
			fflush(stderr);
		}
	}

	return 0;
}
diff mbox series

Patch

diff --git a/man2/accept.2 b/man2/accept.2
index a4bebd214..63e90a5e6 100644
--- a/man2/accept.2
+++ b/man2/accept.2
@@ -208,7 +208,9 @@  and
 .BR EAGAIN " or " EWOULDBLOCK
 .\" Actually EAGAIN on Linux
 The socket is marked nonblocking and no connections are
-present to be accepted.
+present to be accepted, or a receive timeout has been
+set and the timeout expired before a new connection
+was available to be accepted.
 POSIX.1-2001 and POSIX.1-2008
 allow either error to be returned for this case,
 and do not require these constants to have the same value,