vfs2/epoll: fix missing event trigger in epoll-in-epoll case

In some cases, epoll fd would be registered in another epoll fd. Process
may call epoll_wait on the upper layer epoll fd, and the lower layer epoll
fd should generate EPOLLIN event if itself get any event.
But in VFS2, events generated for epoll fd could only be cleaned when
(*EpollInstance).ReadEvents is called. And this function is only called
when epoll_wait on the epoll fd. Therefore, when epoll_wait on the upper
layer epoll fd, the events generated in lower layer epoll fd would not
be cleaned even if it's not valid anymore, and lower layer epoll fd would
not report event to upper layer even if new event is triggered.

In this commit, (*EpollInstance).Readiness would also clean invalid events.
So, when epoll_wait on the upper layer epoll fd, Readiness function called
on lower layer epoll fd would clean invalid events. And lower layer could
report event to upper layer if new event is triggered.

A syscall test case is added to verify the commit.

Fixes https://github.com/google/gvisor/issues/6427

Signed-off-by: Jielong Zhou <jielong.zjl@antgroup.com>
This commit is contained in:
Jielong Zhou
2021-08-09 20:07:58 +08:00
parent c07dc3828a
commit 57da3c7ddd
3 changed files with 50 additions and 1 deletions
+5 -1
View File
@@ -136,12 +136,16 @@ func (ep *EpollInstance) Readiness(mask waiter.EventMask) waiter.EventMask {
return 0
}
ep.mu.Lock()
for epi := ep.ready.Front(); epi != nil; epi = epi.Next() {
var next *epollInterest
for epi := ep.ready.Front(); epi != nil; epi = next {
next = epi.Next()
wmask := waiter.EventMaskFromLinux(epi.mask)
if epi.key.file.Readiness(wmask)&wmask != 0 {
ep.mu.Unlock()
return waiter.ReadableEvents
}
ep.ready.Remove(epi)
epi.ready = false
}
ep.mu.Unlock()
return 0
+1
View File
@@ -583,6 +583,7 @@ cc_binary(
"//test/util:posix_error",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
],
)
+44
View File
@@ -496,6 +496,50 @@ TEST(EpollTest, PipeReaderHupAfterWriterClosed) {
EXPECT_EQ(result[0].data.u64, kMagicConstant);
}
TEST(EpollTest, DoubleLayerEpoll) {
int listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in sa;
sa.sin_family = AF_INET;
inet_aton("127.0.0.1", &sa.sin_addr);
sa.sin_port = htons(8888);
bind(listen_fd, (struct sockaddr *)&sa, sizeof(sa));
listen(listen_fd, 1);
auto epfd1 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
ASSERT_NO_ERRNO(RegisterEpollFD(epfd1.get(), listen_fd, EPOLLIN|EPOLLHUP, listen_fd));
auto epfd2 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
ASSERT_NO_ERRNO(RegisterEpollFD(epfd2.get(), epfd1.get(), EPOLLIN|EPOLLHUP, epfd1.get()));
struct epoll_event ret_events[2];
// first time
ScopedThread thread1([&sa]() {
sleep(1);
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
ASSERT_THAT(connect(sock, (struct sockaddr *)&sa, sizeof(sa)), SyscallSucceeds());
});
ASSERT_THAT(epoll_wait(epfd2.get(), ret_events, 2, 2000), SyscallSucceedsWithValue(1));
ASSERT_EQ(ret_events[0].data.fd, epfd1.get());
ASSERT_THAT(epoll_wait(epfd1.get(), ret_events, 2, 2000), SyscallSucceedsWithValue(1));
ASSERT_EQ(ret_events[0].data.fd, listen_fd);
int server_fd = accept(listen_fd, NULL, NULL);
ASSERT_THAT(close(server_fd), SyscallSucceeds());
// second time to check if event in epfd1 is cleaned correctly
ScopedThread thread2([&sa]() {
sleep(1);
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
ASSERT_THAT(connect(sock, (struct sockaddr *)&sa, sizeof(sa)), SyscallSucceeds());
});
ASSERT_THAT(epoll_wait(epfd2.get(), ret_events, 2, 2000), SyscallSucceedsWithValue(1));
ASSERT_EQ(ret_events[0].data.fd, epfd1.get());
ASSERT_THAT(epoll_wait(epfd1.get(), ret_events, 2, 2000), SyscallSucceedsWithValue(1));
ASSERT_EQ(ret_events[0].data.fd, listen_fd);
server_fd = accept(listen_fd, NULL, NULL);
ASSERT_THAT(close(server_fd), SyscallSucceeds());
}
} // namespace
} // namespace testing