Specifically, I believe the problem lies inside daemon.c (however, I may be
wrong as I have not tested)
if (ret < (ssize_t) i)
{
/* partial read --- no longer read-ready */
...
}
because reading less bytes than the provided buffer size does not seem to
mean that the next read will return EAGAIN (hence the socket could very
well remain read-ready). This can be shown with the following program
exhibiting the same connection leak.
#include <sys/socket.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <unistd.h>
union sockaddr_any
{
struct sockaddr addr;
struct sockaddr_in ipv4;
struct sockaddr_in6 ipv6;
};
static int xsocket(int flags)
{
int sock = socket(AF_INET, SOCK_STREAM | flags, 0);
if (sock < 0)
{
exit(EXIT_FAILURE);
}
return sock;
}
static int xepoll_create1(int flags)
{
int epfd = epoll_create1(flags);
if (epfd < 0)
{
exit(EXIT_FAILURE);
}
return epfd;
}
static void xepoll_add(int epfd, int fd, uint32_t events)
{
struct epoll_event event;
int error;
event.events = events;
event.data.fd = fd;
error = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
if (error)
{
exit(EXIT_FAILURE);
}
}
static void xreuseaddr(int sock, int on)
{
int err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof
on);
if (err) exit(EXIT_FAILURE);
}
static void xbind(int sock, uint32_t addr, in_port_t port)
{
union sockaddr_any any;
int err;
any.ipv4.sin_family = AF_INET;
any.ipv4.sin_addr.s_addr = htonl(addr);
any.ipv4.sin_port = htons(port);
err = bind(sock, &any.addr, sizeof any.ipv4);
if (err) exit(EXIT_FAILURE);
}
static void xlisten(int sock, int backlog)
{
int err = listen(sock, backlog);
if (err) exit(EXIT_FAILURE);
}
static void slurp(int fd)
{
static unsigned char buffer [4096];
ssize_t nbytes = sizeof buffer;
while (nbytes == sizeof buffer)
{
nbytes = read(fd, buffer, sizeof buffer);
}
if (!nbytes) close(fd);
}
int main(void)
{
int sock = xsocket(SOCK_NONBLOCK);
int epfd = xepoll_create1(0);
xreuseaddr(sock, 1);
xbind(sock, 0x7f000001, 8080);
xepoll_add(epfd, sock, EPOLLIN | EPOLLET);
xlisten(sock, 32);
for (;;)
{
struct epoll_event ev;
if (epoll_wait(epfd, &ev, 1, -1) != 1)
{
continue;
}
if (ev.data.fd == sock)
{
int conn = accept(sock, 0, 0);
xepoll_add(epfd, conn, EPOLLIN | EPOLLET);
continue;
}
slurp(ev.data.fd);
}
return EXIT_SUCCESS;
}
Note however, that if the while loop inside slurp is changed to
while (nbytes > sizeof buffer)
{
nbytes = read(fd, buffer, sizeof buffer);
}
Closed connections are properly terminated.
On Tue, May 10, 2016 at 6:16 PM, Chris Penev <[email protected]> wrote:
> Hello everyone. When I use epoll (specifically external select), I can
> create scenarios where the microhttpd library fails to detect client
> disconnects. Assuming microtthpd is listening on 127.0.0.1:8080 and a
> client does
>
> $ echo -n GET | nc 127.0.0.1 8080
>
> The microtthpd library receives two epoll events. Hence an strace shows
> something along the lines of
>
> epoll_wait(...)
>
> recvfrom(..., "GET", ...)
> recvfrom(..., "")
> close(...)
>
>
> However, if the client does something like this
>
> $ { sleep 1; echo -n GET; } | nc 127.0.0.1 8080
>
> Then in the above case microhttpd receives only one epoll event. Hence,
> strace shows something along the lines of
>
> epoll_wait(...)
>
> recvfrom(..., "GET", ...)
>
>
> Even futher calls to epoll_wait will not return additional events, and
> microhttpd will never attempt a second recvfrom, and never figure out that
> the client is gone.
>
> I believe that because the epoll interface used by microhttpd is the edge
> triggered one (which should be the more efficient one), microhttpd needs to
> continue calling read until it receives EAGAIN, as documented under "man
> epoll".
>
> Otherwise, the connection ends up in the CLOSE-WAIT state. Note that in
> the above example, none of the client handlers have been called either, so
> the application cannot do anything about it either.
>
> Lastly, I've attempted to subscribe to the mailing list, but have not
> received a confirmation email, so please note that I may not be able to
> read a reply that replies to the mailing list only.
>
> Sincerely,
> Chris P
>