]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4141] implemented epoll event handler
authorRazvan Becheriu <razvan@isc.org>
Tue, 4 Nov 2025 14:29:25 +0000 (16:29 +0200)
committerRazvan Becheriu <razvan@isc.org>
Fri, 21 Nov 2025 13:02:43 +0000 (13:02 +0000)
src/lib/dhcp/testutils/pkt_filter6_test_stub.cc
src/lib/dhcp/testutils/pkt_filter_test_stub.cc
src/lib/util/epoll_event_handler.cc [new file with mode: 0644]
src/lib/util/epoll_event_handler.h [new file with mode: 0644]
src/lib/util/fd_event_handler_factory.cc
src/lib/util/meson.build
src/lib/util/poll_event_handler.h
src/lib/util/tests/epoll_event_handler_unittests.cc [new file with mode: 0644]
src/lib/util/tests/fd_event_handler_unittests.h
src/lib/util/tests/meson.build
src/lib/util/tests/watch_socket_unittests.cc

index 0a2cb4b74251a17a7fc7e50f2f3ed2edaa380219..2dd0af5c70c37ef0197c830f8fdce34264b000c4 100644 (file)
@@ -20,11 +20,29 @@ SocketInfo
 PktFilter6TestStub::openSocket(const Iface&,
            const isc::asiolink::IOAddress& addr,
            const uint16_t port, const bool) {
-    if (open_socket_callback_) {
-        open_socket_callback_(port);
+    int pipefd[2];
+
+    int ret = pipe(pipefd);
+    if (ret < 0) {
+        const char* errmsg = strerror(errno);
+        isc_throw(Unexpected,
+                  "PktFilter6TestStub: cannot open pipe: " << errmsg);
+    }
+
+    try {
+        if (open_socket_callback_) {
+            open_socket_callback_(port);
+        }
+    } catch (...) {
+        // Don't leak fd on simulated errors.
+        close(pipefd[0]);
+        close(pipefd[1]);
+        throw;
     }
 
-    return (SocketInfo(addr, port, 0));
+    close(pipefd[1]);
+
+    return (SocketInfo(addr, port, pipefd[0]));
 }
 
 Pkt6Ptr
index 0c6d67bdd03eb7edf3540751070c902b7573ae2e..5accf92bd6a2b51a497cce97e1d8ca4af9e19224 100644 (file)
@@ -37,24 +37,29 @@ SocketInfo
 PktFilterTestStub::openSocket(Iface&,
            const isc::asiolink::IOAddress& addr,
            const uint16_t port, const bool, const bool) {
-    int fd = open("/dev/null", O_RDONLY);
-    if (fd < 0) {
+    int pipefd[2];
+
+    int ret = pipe(pipefd);
+    if (ret < 0) {
         const char* errmsg = strerror(errno);
         isc_throw(Unexpected,
-                  "PktFilterTestStub: cannot open /dev/null:" << errmsg);
+                  "PktFilterTestStub: cannot open pipe: " << errmsg);
     }
 
     try {
         if (open_socket_callback_) {
             open_socket_callback_(port);
         }
-    } catch(...) {
+    } catch (...) {
         // Don't leak fd on simulated errors.
-        close(fd);
+        close(pipefd[0]);
+        close(pipefd[1]);
         throw;
     }
 
-    return (SocketInfo(addr, port, fd));
+    close(pipefd[1]);
+
+    return (SocketInfo(addr, port, pipefd[0]));
 }
 
 Pkt4Ptr
diff --git a/src/lib/util/epoll_event_handler.cc b/src/lib/util/epoll_event_handler.cc
new file mode 100644 (file)
index 0000000..ce12806
--- /dev/null
@@ -0,0 +1,138 @@
+// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <util/epoll_event_handler.h>
+
+#include <cstring>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+
+EPollEventHandler::EPollEventHandler() : FDEventHandler(TYPE_EPOLL), epollfd_(-1) {
+    epollfd_ = epoll_create1(EPOLL_CLOEXEC);
+    if (epollfd_ == -1) {
+        isc_throw(Unexpected, "error opening epoll: " << strerror(errno));
+    }
+    if (pipe(pipefd)) {
+        close(epollfd_);
+        isc_throw(Unexpected, "error opening internal epoll pipe: " << strerror(errno));
+    }
+    clear();
+}
+
+EPollEventHandler::~EPollEventHandler() {
+    close(epollfd_);
+    close(pipefd[1]);
+    close(pipefd[0]);
+}
+
+void EPollEventHandler::add(int fd, bool read /* = true */, bool write /* = false */) {
+    if (fd < 0) {
+        isc_throw(BadValue, "invalid negative value for fd");
+    }
+    struct epoll_event data;
+    memset(&data, 0, sizeof(data));
+    data.data.fd = fd;
+    if (read) {
+        // Add this socket to read events
+        data.events |= EPOLLIN;
+    }
+    if (write) {
+        // Add this socket to write events
+        data.events |= EPOLLOUT;
+    }
+    data_.push_back(data);
+}
+
+int EPollEventHandler::waitEvent(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */,
+                                 bool use_timeout /* = true */) {
+    // Sanity check for microsecond timeout.
+    if (timeout_usec >= 1000000) {
+        isc_throw(BadValue, "fractional timeout must be shorter than"
+                  " one million microseconds");
+    }
+    int timeout = -1;
+    if (use_timeout) {
+        timeout = timeout_sec * 1000 + timeout_usec / 1000;
+    }
+    map_.clear();
+    used_data_.clear();
+    errors_.clear();
+    errno = 0;
+    int saved_errno = errno;
+    for (auto data : data_) {
+        if (epoll_ctl(epollfd_, EPOLL_CTL_ADD, data.data.fd, &data) == -1) {
+            saved_errno = errno;
+            errors_.insert(data.data.fd);
+        } else {
+            used_data_.push_back(data);
+        }
+    }
+    struct epoll_event dummy;
+    memset(&dummy, 0, sizeof(dummy));
+    dummy.data.fd = pipefd[0];
+    dummy.events |= EPOLLIN;
+    epoll_ctl(epollfd_, EPOLL_CTL_ADD, dummy.data.fd, &dummy);
+    used_data_.push_back(dummy);
+    int result = 0;
+    if (errors_.empty()) {
+        result = epoll_wait(epollfd_, used_data_.data(), used_data_.size(), timeout);
+        for (int i = 0; i < result; ++i) {
+             map_[used_data_[i].data.fd] = &used_data_[i];
+        }
+    }
+    for (auto data : data_) {
+        epoll_ctl(epollfd_, EPOLL_CTL_DEL, data.data.fd, &data);
+    }
+    epoll_ctl(epollfd_, EPOLL_CTL_DEL, dummy.data.fd, &dummy);
+    if (result != -1) {
+        errno = saved_errno;
+    }
+    if (errors_.size()) {
+        return (errors_.size());
+    }
+    return (result);
+}
+
+bool EPollEventHandler::readReady(int fd) {
+    if (map_.find(fd) == map_.end()) {
+        return (false);
+    }
+    return (map_[fd]->events & EPOLLIN);
+}
+
+bool EPollEventHandler::writeReady(int fd) {
+    if (map_.find(fd) == map_.end()) {
+        return (false);
+    }
+    return (map_[fd]->events & EPOLLOUT);
+}
+
+bool EPollEventHandler::hasError(int fd) {
+    if (errors_.count(fd)) {
+        return (true);
+    }
+    if (map_.find(fd) == map_.end()) {
+        return (false);
+    }
+    return (map_[fd]->events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP));
+}
+
+void EPollEventHandler::clear() {
+    data_.clear();
+    used_data_.clear();
+    errors_.clear();
+    map_.clear();
+    errno = 0;
+}
+
+} // end of namespace isc::util
+} // end of namespace isc
diff --git a/src/lib/util/epoll_event_handler.h b/src/lib/util/epoll_event_handler.h
new file mode 100644 (file)
index 0000000..fdb96bc
--- /dev/null
@@ -0,0 +1,99 @@
+// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef EPOLL_EVENT_HANDLER_H
+#define EPOLL_EVENT_HANDLER_H
+
+#include <util/fd_event_handler.h>
+
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <sys/epoll.h>
+
+namespace isc {
+namespace util {
+
+/// @brief File descriptor event handler class handles events for registered
+/// file descriptors. This class uses the OS select syscall for event handling.
+class EPollEventHandler : public FDEventHandler {
+public:
+    /// @brief Constructor.
+    EPollEventHandler();
+
+    /// @brief Destructor.
+    virtual ~EPollEventHandler();
+
+    /// @brief Add file descriptor to watch for events.
+    ///
+    /// @param fd The file descriptor.
+    /// @param read The flag indicating if the file descriptor should be
+    /// registered for read ready events.
+    /// @param write The flag indicating if the file descriptor should be
+    /// registered for write ready events.
+    void add(int fd, bool read = true, bool write = false);
+
+    /// @brief Wait for events on registered file descriptors.
+    ///
+    /// @param timeout_sec The wait timeout in seconds.
+    /// @param timeout_usec The wait timeout in micro seconds.
+    /// @param use_timeout Flag which indicates if function should wait
+    /// with no timeout (wait forever).
+    /// @return -1 on error, 0 if no data is available (timeout expired),
+    /// 1 if data is ready.
+    int waitEvent(uint32_t timeout_sec, uint32_t timeout_usec = 0,
+                  bool use_timeout = true);
+
+    /// @brief Check if file descriptor is ready for read operation.
+    ///
+    /// @param fd The file descriptor.
+    ///
+    /// @return True if file descriptor is ready for reading.
+    bool readReady(int fd);
+
+    /// @brief Check if file descriptor is ready for write operation.
+    ///
+    /// @param fd The file descriptor.
+    ///
+    /// @return True if file descriptor is ready for writing.
+    bool writeReady(int fd);
+
+    /// @brief Check if file descriptor has error.
+    ///
+    /// @param fd The file descriptor.
+    ///
+    /// @return True if file descriptor has error.
+    virtual bool hasError(int fd);
+
+    /// @brief Clear registered file descriptors.
+    void clear();
+
+private:
+    /// @brief The epoll file descriptor.
+    int epollfd_;
+
+    /// @brief The epoll file descriptors data.
+    std::vector<struct epoll_event> data_;
+
+    /// @brief The epoll file descriptors data.
+    std::vector<struct epoll_event> used_data_;
+
+    /// @brief The set of file descriptors with errors.
+    std::unordered_set<int> errors_;
+
+    /// @brief The map with file descriptor to data reference.
+    std::unordered_map<int, struct epoll_event*> map_;
+
+    /// @brief The pipe used to permit calling @ref waitEvent with no
+    /// registered file descriptors.
+    int pipefd[2];
+};
+
+}  // namespace isc::util;
+}  // namespace isc
+
+#endif  // EPOLL_EVENT_HANDLER_H
index 2997c44f74b04b3fe6c45179ef46e3e89b3587d5..ba63624296c881f11375b7c33c0e00e243ea655b 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <config.h>
 
+#include <util/epoll_event_handler.h>
 #include <util/fd_event_handler_factory.h>
 #include <util/poll_event_handler.h>
 #include <util/select_event_handler.h>
@@ -28,10 +29,15 @@ FDEventHandlerPtr FDEventHandlerFactory::factoryFDEventHandler() {
         if (string(env_type) == string("poll")) {
             type = FDEventHandler::TYPE_POLL;
         }
+        if (string(env_type) == string("epoll")) {
+            type = FDEventHandler::TYPE_EPOLL;
+        }
     }
     switch(type) {
     case FDEventHandler::TYPE_SELECT:
         return (FDEventHandlerPtr(new SelectEventHandler()));
+    case FDEventHandler::TYPE_EPOLL:
+        return (FDEventHandlerPtr(new EPollEventHandler()));
     case FDEventHandler::TYPE_POLL:
         return (FDEventHandlerPtr(new PollEventHandler()));
     default:
index 8131a7f13fd1d14c522e0e98c3598b1e4f05c1b4..1e7196e0f28fb5c5434046583c3efa0ddf66971d 100644 (file)
@@ -6,6 +6,7 @@ kea_util_lib = shared_library(
     'dhcp_space.cc',
     'encode/encode.cc',
     'encode/utf8.cc',
+    'epoll_event_handler.cc',
     'fd_event_handler.cc',
     'fd_event_handler_factory.cc',
     'filesystem.cc',
@@ -47,6 +48,7 @@ kea_util_headers = [
     'doubles.h',
     'encode/encode.h',
     'encode/utf8.h',
+    'epoll_event_handler.h',
     'fd_event_handler.h',
     'fd_event_handler_factory.h',
     'filesystem.h',
index 933284361349785fdb7c19b14d1b06e4a80ce1d0..f102a0fc09dd565f2b7bfc54822dded645b2931a 100644 (file)
@@ -10,6 +10,7 @@
 #include <util/fd_event_handler.h>
 
 #include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 #include <sys/poll.h>
diff --git a/src/lib/util/tests/epoll_event_handler_unittests.cc b/src/lib/util/tests/epoll_event_handler_unittests.cc
new file mode 100644 (file)
index 0000000..05173c9
--- /dev/null
@@ -0,0 +1,12 @@
+// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#define FDEventHandlerType EPollEventHandler
+#define FDEventHandlerTest EPollEventHandlerTest
+
+#include <fd_event_handler_unittests.h>
index 5037186e05cf53e66fcdd53899a4736d4ddc74f8..5b5367b781f62fcb5fdc59f4c1541a2c617bb35c 100644 (file)
@@ -8,6 +8,7 @@
 
 #include <exceptions/exceptions.h>
 #include <util/fd_event_handler.h>
+#include <util/epoll_event_handler.h>
 #include <util/poll_event_handler.h>
 #include <util/select_event_handler.h>
 
@@ -35,20 +36,20 @@ public:
     /// @brief Constructor.
     FDEventHandlerTest() {
         handler_.reset(new FDEventHandlerType);
-        pipe(pipefd);
+        pipe(pipefd_);
     }
 
     /// @brief Destructor.
     ~FDEventHandlerTest() {
-        close(pipefd[1]);
-        close(pipefd[0]);
+        close(pipefd_[1]);
+        close(pipefd_[0]);
     }
 
     /// @brief The tested fd event handler.
     FDEventHandlerPtr handler_;
 
     /// @brief The pipe used for testing read and write operations.
-    int pipefd[2];
+    int pipefd_[2];
 };
 
 TEST_F(FDEventHandlerTest, events) {
@@ -58,58 +59,58 @@ TEST_F(FDEventHandlerTest, events) {
 
     EXPECT_THROW(handler_->add(-1), BadValue);
 
-    EXPECT_NO_THROW(handler_->add(pipefd[0], true, false));
-    EXPECT_NO_THROW(handler_->add(pipefd[1], false, true));
+    EXPECT_NO_THROW(handler_->add(pipefd_[0], true, false));
+    EXPECT_NO_THROW(handler_->add(pipefd_[1], false, true));
 
-    EXPECT_FALSE(handler_->readReady(pipefd[0]));
-    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
-    EXPECT_FALSE(handler_->readReady(pipefd[1]));
-    EXPECT_FALSE(handler_->writeReady(pipefd[1]));
+    EXPECT_FALSE(handler_->readReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+    EXPECT_FALSE(handler_->writeReady(pipefd_[1]));
 
     EXPECT_EQ(1, handler_->waitEvent(0, 1000));
 
-    EXPECT_FALSE(handler_->readReady(pipefd[0]));
-    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
-    EXPECT_FALSE(handler_->readReady(pipefd[1]));
-    EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+    EXPECT_FALSE(handler_->readReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+    EXPECT_TRUE(handler_->writeReady(pipefd_[1]));
 
-    EXPECT_EQ(1, write(pipefd[1], &MARKER, sizeof(MARKER)));
+    EXPECT_EQ(1, write(pipefd_[1], &MARKER, sizeof(MARKER)));
 
     EXPECT_EQ(2, handler_->waitEvent(0, 1000));
 
-    EXPECT_TRUE(handler_->readReady(pipefd[0]));
-    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
-    EXPECT_FALSE(handler_->readReady(pipefd[1]));
-    EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+    EXPECT_TRUE(handler_->readReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+    EXPECT_TRUE(handler_->writeReady(pipefd_[1]));
 
-    EXPECT_EQ(1, write(pipefd[1], &MARKER, sizeof(MARKER)));
+    EXPECT_EQ(1, write(pipefd_[1], &MARKER, sizeof(MARKER)));
 
     EXPECT_EQ(2, handler_->waitEvent(0, 1000));
 
-    EXPECT_TRUE(handler_->readReady(pipefd[0]));
-    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
-    EXPECT_FALSE(handler_->readReady(pipefd[1]));
-    EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+    EXPECT_TRUE(handler_->readReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+    EXPECT_TRUE(handler_->writeReady(pipefd_[1]));
 
     unsigned char data;
 
-    EXPECT_EQ(1, read(pipefd[0], &data, sizeof(data)));
+    EXPECT_EQ(1, read(pipefd_[0], &data, sizeof(data)));
 
     EXPECT_EQ(2, handler_->waitEvent(0, 1000));
 
-    EXPECT_TRUE(handler_->readReady(pipefd[0]));
-    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
-    EXPECT_FALSE(handler_->readReady(pipefd[1]));
-    EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+    EXPECT_TRUE(handler_->readReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+    EXPECT_TRUE(handler_->writeReady(pipefd_[1]));
 
-    EXPECT_EQ(1, read(pipefd[0], &data, sizeof(data)));
+    EXPECT_EQ(1, read(pipefd_[0], &data, sizeof(data)));
 
     EXPECT_EQ(1, handler_->waitEvent(0, 1000));
 
-    EXPECT_FALSE(handler_->readReady(pipefd[0]));
-    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
-    EXPECT_FALSE(handler_->readReady(pipefd[1]));
-    EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+    EXPECT_FALSE(handler_->readReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd_[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd_[1]));
+    EXPECT_TRUE(handler_->writeReady(pipefd_[1]));
 
     EXPECT_NO_THROW(handler_->clear());
 
@@ -143,7 +144,16 @@ TEST_F(FDEventHandlerTest, events) {
 }
 
 TEST_F(FDEventHandlerTest, badFD) {
-    int fd = open("/dev/zero", O_RDONLY, 0);
+    errno = 0;
+    int fd;
+
+    if (handler_->type() == FDEventHandler::TYPE_EPOLL) {
+        // epoll does not allow add of /dev/zero to registered events.
+        fd = pipefd_[0];
+        EXPECT_EQ(1, write(pipefd_[1], &MARKER, sizeof(MARKER)));
+    } else {
+        fd = open("/dev/zero", O_RDONLY, 0);
+    }
 
     ASSERT_GE(fd, 0);
 
@@ -171,11 +181,21 @@ TEST_F(FDEventHandlerTest, badFD) {
         EXPECT_TRUE(handler_->readReady(fd));
         EXPECT_FALSE(handler_->hasError(fd));
         EXPECT_EQ(EBADF, errno);
-    } else {
+    } else if (handler_->type() == FDEventHandler::TYPE_POLL) {
         EXPECT_EQ(1, handler_->waitEvent(0, 1000));
         EXPECT_FALSE(handler_->readReady(fd));
         EXPECT_TRUE(handler_->hasError(fd));
         EXPECT_EQ(0, errno);
+    } else {
+        EXPECT_EQ(1, handler_->waitEvent(0, 1000));
+        EXPECT_FALSE(handler_->readReady(fd));
+        EXPECT_TRUE(handler_->hasError(fd));
+        EXPECT_EQ(EBADF, errno);
+    }
+
+    if (handler_->type() == FDEventHandler::TYPE_EPOLL) {
+        close(pipefd_[1]);
+        pipe(pipefd_);
     }
 }
 
index 964dafc945eff16bdd1554a03d5d86f05efd7e4d..8e48a3a5aca734b6f74b29858c73799cbf100661 100644 (file)
@@ -12,6 +12,7 @@ kea_util_tests = executable(
     'dhcp_space_unittest.cc',
     'doubles_unittest.cc',
     'encode_unittest.cc',
+    'epoll_event_handler_unittests.cc',
     'fd_event_handler_factory_unittests.cc',
     'fd_tests.cc',
     'filesystem_unittests.cc',
index 375252d574317fec058ff0e95f4be1071c2b4c80..4346d738c693976f3279ab69d67d851241f788d7 100644 (file)
@@ -119,6 +119,12 @@ TEST(WatchSocketTest, closedWhileReady) {
     EXPECT_EQ(1, selectCheck(select_fd));
     EXPECT_TRUE(watch->isReady());
 
+    // The epoll event handler must be created before closing the socket.
+    // It creates an internal pipe which will match the closed fd and the
+    // check for bad file descriptor will fail.
+    FDEventHandlerPtr handler = FDEventHandlerFactory::factoryFDEventHandler();
+    bool use_select = FDEventHandlerFactory::factoryFDEventHandler()->type() == FDEventHandler::TYPE_SELECT;
+
     // Interfere by closing the fd.
     ASSERT_EQ(0, close(select_fd));
 
@@ -132,13 +138,12 @@ TEST(WatchSocketTest, closedWhileReady) {
     ASSERT_NO_THROW(watch->clearReady());
 
     // Verify the select_fd fails as socket is invalid/closed.
-    if (FDEventHandlerFactory::factoryFDEventHandler()->type() == FDEventHandler::TYPE_SELECT) {
-        EXPECT_EQ(-1, selectCheck(select_fd));
+    if (use_select) {
+        ASSERT_EQ(-1, selectCheck(select_fd));
     } else {
-        FDEventHandlerPtr handler = FDEventHandlerFactory::factoryFDEventHandler();
         handler->add(select_fd);
         EXPECT_EQ(1, handler->waitEvent(0, 0));
-        EXPECT_TRUE(handler->hasError(select_fd));
+        ASSERT_TRUE(handler->hasError(select_fd));
     }
 
     // Verify that subsequent attempts to mark it will fail.
@@ -198,6 +203,12 @@ TEST(WatchSocketTest, badReadOnClear) {
     EXPECT_TRUE(watch->isReady());
     EXPECT_EQ(1, selectCheck(select_fd));
 
+    // The epoll event handler must be created before closing the socket.
+    // It creates an internal pipe which will match the closed fd and the
+    // check for bad file descriptor will fail.
+    FDEventHandlerPtr handler = FDEventHandlerFactory::factoryFDEventHandler();
+    bool use_select = FDEventHandlerFactory::factoryFDEventHandler()->type() == FDEventHandler::TYPE_SELECT;
+
     // Interfere by reading the fd. This should empty the read pipe.
     uint32_t buf = 0;
     ASSERT_EQ((read (select_fd, &buf, 1)), 1);
@@ -210,10 +221,9 @@ TEST(WatchSocketTest, badReadOnClear) {
 
     // Verify the select_fd does not evaluate to ready.
     EXPECT_FALSE(watch->isReady());
-    if (FDEventHandlerFactory::factoryFDEventHandler()->type() == FDEventHandler::TYPE_SELECT) {
+    if (use_select) {
         EXPECT_EQ(-1, selectCheck(select_fd));
     } else {
-        FDEventHandlerPtr handler = FDEventHandlerFactory::factoryFDEventHandler();
         handler->add(select_fd);
         EXPECT_EQ(1, handler->waitEvent(0, 0));
         EXPECT_TRUE(handler->hasError(select_fd));