Also see this question, unanswered as of now.
There is a lot of confusion about EPOLLHUP
, even in the man
and Kernel docs. People seem to believe it is returned when polling on a descriptor locally closed for writing, i.e. shutdown(SHUT_WR)
, i.e. the same call that causes an EPOLLRDHUP
at the peer. But this is not true, in my experiments I get EPOLLOUT
, and no EPOLLHUP
, after shutdown(SHUT_WR)
(yes, it's counterintuitive to get writable, as the writing half is closed, but this is not the main point of the question).
The man is poor, because it says EPOLLHUP
comes when Hang up happened on the associated file descriptor, without saying what "hang up" means - what did the peer do? what packets were sent? This other article just confuses things further and seems outright wrong to me.
My experiments show EPOLLHUP
arrives once EOF (FIN packets) are exchanged both ways, i.e. once both sides issue shutdown(SHUT_WR)
. It has nothing to do with SHUT_RD
, which I never call. Also nothing to do with close
. In terms of packets, I have the suspicion that EPOLLHUP
is raised on the ack of the hosts' sent FIN, i.e. the termination initiator raises this event in step 3 of the 4-way shutdown handshake, and the peer, in step 4 (see here). If confirmed, this is great, because it fills a gap that I've been looking for, namely how to poll non-blocking sockets for the final ack, without LINGER. Is this correct?
(note: I'm using ET, but I don't think it's relevant for this)
Sample code and output.
The code being in a framework, I extracted the meat of it, with the exception of TcpSocket::createListener
, TcpSocket::connect
and TcpSocket::accept
, which do what you'd expect (not shown here).
void registerFd(int pollFd, int fd, const char* description)
{
epoll_event ev = {
EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET,
const_cast<char*>(description) // union aggregate initialisation, initialises first member (void* ptr)
};
epoll_ctl(pollFd, EPOLL_CTL_ADD, fd, &ev);
}
struct EventPrinter
{
friend std::ostream& operator<<(std::ostream& stream, const EventPrinter& obj)
{
return stream << "0x" << std::hex << obj.events_ << " = "
<< ((obj.events_& EPOLLIN) ? "EPOLLIN " : " ")
<< ((obj.events_& EPOLLOUT) ? "EPOLLOUT " : " ")
<< ((obj.events_& EPOLLERR) ? "EPOLLERR " : " ")
<< ((obj.events_& EPOLLRDHUP) ? "EPOLLRDHUP " : " ")
<< ((obj.events_& EPOLLHUP) ? "EPOLLHUP " : " ");
}
const uint32_t events_;
};
void processEvents(int pollFd)
{
static int iterationCount = 0;
++iterationCount;
std::array<epoll_event, 25> events;
int eventCount;
if (-1 ==
(eventCount = epoll_wait(pollFd, events.data(), events.size(), 1)))
{
throw Exception("fatal: epoll_wait failed");
}
for (int i = 0; i < eventCount; ++i)
{
std::cout << "iteration #" << iterationCount << ": events on [" << static_cast<const char*>(events[i].data.ptr) << "]: [" << EventPrinter{events[i].events} << "]" << std::endl;
}
}
TEST(EpollhupExample, SmokeTest)
{
int pollFd_;
if (-1 ==
(pollFd_ = epoll_create1(0)))
{
throw Exception("fatal: could not create epoll socket");
}
const TcpSocket listener_ = TcpSocket::createListener(13500);
if (!listener_.setFileStatusFlag(O_NONBLOCK, true))
throw Exception("could not make listener socket non-blocking");
registerFd(pollFd_, listener_.fd(), "listenerFD");
const TcpSocket client = TcpSocket::connect("127.0.0.1", AF_INET, 13500);
if (!client.valid()) throw;
registerFd(pollFd_, client.fd(), "clientFD");
//////////////////////////////////////////////
/// start event processing ///////////////////
//////////////////////////////////////////////
processEvents(pollFd_); // iteration 1
const TcpSocket conn = listener_.accept();
if (!conn.valid()) throw;
registerFd(pollFd_, conn.fd(), "serverFD");
processEvents(pollFd_); // iteration 2
conn.shutdown(SHUT_WR);
processEvents(pollFd_); // iteration 3
client.shutdown(SHUT_WR);
processEvents(pollFd_); // iteration 4
}
Output:
Info| TCP connection established to [127.0.0.1:13500]
iteration #1: events on [listenerFD]: [1 = EPOLLIN ]
iteration #1: events on [clientFD]: [4 = EPOLLOUT ]
Info| TCP connection accepted from [127.0.0.1:35160]
iteration #2: events on [serverFD]: [4 = EPOLLOUT ]
// calling serverFD.shutdown(SHUT_WR) here
iteration #3: events on [clientFD]: [2005 = EPOLLIN EPOLLOUT EPOLLRDHUP ] // EPOLLRDHUP arrives, nice.
iteration #3: events on [serverFD]: [4 = EPOLLOUT ] // serverFD (on which I called SHUT_WR) just reported as writable, not cool... but not the main point of the question
// calling clientFD.shutdown(SHUT_WR) here
iteration #4: events on [serverFD]: [2015 = EPOLLIN EPOLLOUT EPOLLRDHUP EPOLLHUP ] // EPOLLRDHUP arrives, nice. EPOLLHUP too!
iteration #4: events on [clientFD]: [2015 = EPOLLIN EPOLLOUT EPOLLRDHUP EPOLLHUP ] // EPOLLHUP on the other side as well. Why? What does EPOLLHUP mean actually?
There is not better way to rephrase the question, other than, what does EPOLLHUP mean? I made the case that documentation is poor, and information in other places (e.g. here and here) is wrong or useless.
Note: To consider the Q answered, I want confirmation that EPOLLHUP is raised on the final FIN-ACKs of both directions.