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 belie
For these kind of questions, use the source! Among other interesting comments, there is this text:
EPOLLHUPis UNMASKABLE event (...). It means that after we receivedEOF,pollalways returns immediately, making impossiblepoll()onwrite()in stateCLOSE_WAIT. One solution is evident --- to setEPOLLHUPif and only ifshutdownhas been made in both directions.
And then the only code that sets EPOLLHUP:
if (sk->sk_shutdown == SHUTDOWN_MASK || state == TCP_CLOSE)
mask |= EPOLLHUP;
Being SHUTDOWN_MASK equal to RCV_SHUTDOWN |SEND_SHUTDOWN.
TL; DR; You are right, this flag is only sent when the shutdown has been both for read and write (I reckon that the peer shutdowning the write equals to my shutdowning the read). Or when the connection is closed, of course.
UPDATE: From reading the source code with more detail, these are my conclusions.
About shutdown:
shutdown(SHUT_WR) sends a FIN and marks the socket with SEND_SHUTDOWN.shutdown(SHUT_RD) sends nothing and marks the socket with RCV_SHUTDOWN.FIN marks the socket with RCV_SHUTDOWN.And about epoll:
SEND_SHUTDOWN and RCV_SHUTDOWN, poll will return EPOLLHUP.RCV_SHUTDOWN, poll will return EPOLLRDHUP.So the HUP events can be read as:
EPOLLRDHUP: you have received FIN or you have called shutdown(SHUT_RD). In any case your reading half-socket is hung, that is, you will read no more data.EPOLLHUP: you have both half-sockets hung. The reading half-socket is just like the previous point, For the sending half-socket you did something like shutdown(SHUT_WR).To complete a a graceful shutdown I would do:
shutdown(SHUT_WR) to send a FIN and mark the end of sending data.EPOLLRDHUP.PS: About your comment:
it's counterintuitive to get writable, as the writing half is closed
It is actually expected if you understand the output of epoll not as ready but as will not block. That is, if you get EPOLLOUT you have the guarantee that calling write() will not block. And certainly, after shutdown(SHUT_WR), write() will return immediately.