Hi,

as I've had an old version of libev (3.41) on a different machine, I ran
into the epoll spurious event notification bug.
I noticed the libev version to late and wrote example code to
reproduce the problem.
I guess it could be usefull for the epoll developers.

Basically,
 * accept a connection A
   * set A nonblocking
 * recv something on A
   * fork,
   * in child run system() or execv
 * close A
 * accept a new connection B which gets the same fd as A had previously

leads to an endless loop with the read callback for B getting called all
time, even though there is no data to read.

Attached is a small programm which triggers the bug here on a linux
2.6.27 x86_64 smp machine.

Compile using
gcc -Wall -L/opt/libev/lib/libev/ -I/opt/libev/include spurious.c -o
spurious -lev

run it
./spurious

in a different terminal

nc localhost 4711

send a character to fork a process
disconnect netcat

nc localhost 4711
triggers the bug

the sample program will loop with something like

read_connection loop 0x7fad4968a1c0 ev_io 0x18b2650 revents 1 (fd 6)
         EAGAIN


Works fine using libev 3.53.
/* triggers epoll spurious notification bug on libev 3.41 

Compile using
gcc -Wall -L/opt/libev/lib/libev/ -I/opt/libev/include spurious.c -o spurious -lev

run it
./spurious

in a different terminal

nc localhost 4711

> send a character to fork a process
> disconnect netcat

nc localhost 4711
> triggers the bug

the sample program will loop with something like

read_connection loop 0x7fad4968a1c0 ev_io 0x18b2650 revents 1 (fd 6)
	 EAGAIN

*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <assert.h>

#include <unistd.h> 
#include <ev.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

struct ev_loop *loop;

void child_watch(EV_P_ struct ev_child *w, int revents)
{
	printf("%s loop %p ev_child %p revents %i (pid %i)\n", __PRETTY_FUNCTION__, EV_A_ w, revents, w->pid);

	int status;
	if (waitpid(w->pid, &status, WNOHANG) != 0)
	{
		printf("catching dying child %i failed\n", w->pid);
	}

	ev_child_stop(EV_A_ w);
}

void read_connection(EV_P_ struct ev_io *w, int revents)
{
	printf("%s loop %p ev_io %p revents %i (fd %i)\n", __PRETTY_FUNCTION__, EV_A_ w, revents, w->fd);
	int size, buf_size = 1024;
	char buf[1024];
	size = recv(w->fd, buf, buf_size, 0);

	if(size <= 0)
	{
		if( size == -1 && errno == EAGAIN )
		{
			printf("\t EAGAIN\n");
		}else
		{
			ev_io_stop(loop, w);
			close(w->fd);
			free(w);
			printf("\t -> closed connection\n");
		}
		return;
	}

	pid_t p = fork();

	if(p == 0)
	{ // child
		printf("child alive\n");
		system("/bin/sh -c \"sleep 10; echo foo;\"");
		exit(EXIT_SUCCESS);
	}else
	{ // parent
		printf("parent alive (%i) \n", p);
		struct ev_child *child = malloc(sizeof(struct ev_child));
		ev_child_init(child, child_watch, p, 0);
		ev_child_start(loop, child);
	}
}


void accept_connection(EV_P_ struct ev_io *w, int revents)
{
	printf("%s loop %p ev_io %p revents %i (fd %i)\n", __PRETTY_FUNCTION__, EV_A_ w, revents, w->fd);
	struct ev_io *io = malloc(sizeof(struct ev_io));
	struct sockaddr sa;
	socklen_t sizeof_sa = sizeof(sa);
	int s = accept(w->fd, &sa, &sizeof_sa);
	fcntl(s,F_SETFL,O_NONBLOCK);
	ev_io_init(io, read_connection, s, EV_READ);
	ev_io_start(loop, io);
}


int main()
{

//	loop = ev_default_loop(EVBACKEND_SELECT);
	loop = ev_default_loop(EVBACKEND_EPOLL);

	int s = socket(AF_INET, SOCK_STREAM, 0);
	struct sockaddr sa;
	struct sockaddr_in *si = (struct sockaddr_in *)&sa;
	si->sin_family = AF_INET;
	si->sin_port = htons(4711);
	si->sin_addr.s_addr = INADDR_ANY;

	bind(s, &sa, sizeof(sa));
	listen(s, 10);

	struct ev_io *io = malloc(sizeof(struct ev_io));
	ev_io_init(io, accept_connection, s, EV_READ);
	ev_io_start(loop, io);

	ev_loop(loop, 0);

	return 0;
}


_______________________________________________
libev mailing list
[email protected]
http://lists.schmorp.de/cgi-bin/mailman/listinfo/libev

Reply via email to