Add qt_safe_poll

This function is introduced to safely provide poll(2)-like semantics for
socket multiplexing on Unix-like platforms. For platforms where no poll
system call is available, an implementation based on select(2) is provided.

Change-Id: I320e97dae5924316675a74d1897c48cae292ac6d
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Louai Al-Khanji 2015-10-15 16:04:17 +03:00
parent f9ba58a13b
commit 105fc117b7
12 changed files with 641 additions and 6 deletions

View File

@ -0,0 +1,45 @@
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the config.tests of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <poll.h>
int main()
{
struct pollfd pfd;
pfd.fd = -1;
pfd.events = 0;
pfd.revents = 0;
return ::poll(&pfd, 1, 0);
}

View File

@ -0,0 +1,2 @@
SOURCES = poll.cpp
CONFIG -= qt

View File

@ -0,0 +1,51 @@
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the config.tests of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <poll.h>
#include <signal.h>
#include <time.h>
int main()
{
struct pollfd pfd;
struct timespec ts;
pfd.fd = -1;
pfd.events = 0;
pfd.revents = 0;
ts.tv_sec = 0;
ts.tv_nsec = 0;
return ::pollts(&pfd, 1, &ts, nullptr);
}

View File

@ -0,0 +1,2 @@
SOURCES = pollts.cpp
CONFIG -= qt

View File

@ -0,0 +1,50 @@
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the config.tests of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <signal.h>
#include <poll.h>
int main()
{
struct pollfd pfd;
struct timespec ts;
pfd.fd = -1;
pfd.events = 0;
pfd.revents = 0;
ts.tv_sec = 0;
ts.tv_nsec = 0;
return ::ppoll(&pfd, 1, &ts, nullptr);
}

View File

@ -0,0 +1,2 @@
SOURCES = ppoll.cpp
CONFIG -= qt

15
configure vendored
View File

@ -745,6 +745,7 @@ CFG_GETIFADDRS=auto
CFG_INOTIFY=auto
CFG_EVENTFD=auto
CFG_CLOEXEC=no
CFG_POLL=auto
CFG_RPATH=yes
CFG_FRAMEWORK=auto
CFG_USE_GOLD_LINKER=auto
@ -6135,6 +6136,16 @@ if compileTest unix/cloexec "cloexec"; then
CFG_CLOEXEC=yes
fi
if compileTest unix/ppoll "ppoll"; then
CFG_POLL="ppoll"
elif compileTest unix/pollts "pollts"; then
CFG_POLL="pollts"
elif compileTest unix/poll "poll"; then
CFG_POLL="poll"
else
CFG_POLL="select"
fi
if [ "$XPLATFORM_MAC" = "yes" ] && [ "$CFG_SECURETRANSPORT" != "no" ] && ([ "$CFG_OPENSSL" = "no" ] || [ "$CFG_OPENSSL" = "auto" ]); then
CFG_SECURETRANSPORT=yes
CFG_OPENSSL=no
@ -6444,6 +6455,10 @@ fi
if [ "$CFG_CLOEXEC" = "yes" ]; then
QT_CONFIG="$QT_CONFIG threadsafe-cloexec"
fi
if [ "$CFG_POLL" = "select" ]; then
QCONFIG_FLAGS="$QCONFIG_FLAGS QT_NO_NATIVE_POLL"
fi
QT_CONFIG="$QT_CONFIG poll_$CFG_POLL"
if [ "$CFG_LIBJPEG" = "no" ]; then
CFG_JPEG="no"
elif [ "$CFG_LIBJPEG" = "system" ]; then

View File

@ -143,6 +143,10 @@ unix|integrity {
kernel/qeventdispatcher_unix_p.h \
kernel/qtimerinfo_unix_p.h
contains(QT_CONFIG, poll_poll): DEFINES += QT_HAVE_POLL
contains(QT_CONFIG, poll_ppoll): DEFINES += QT_HAVE_POLL QT_HAVE_PPOLL
contains(QT_CONFIG, poll_pollts): DEFINES += QT_HAVE_POLL QT_HAVE_POLLTS
contains(QT_CONFIG, glib) {
SOURCES += \
kernel/qeventdispatcher_glib.cpp

View File

@ -52,6 +52,33 @@
QT_BEGIN_NAMESPACE
#if !defined(QT_HAVE_PPOLL) && defined(QT_HAVE_POLLTS)
# define ppoll pollts
# define QT_HAVE_PPOLL
#endif
#ifndef _POSIX_POLL
# if defined(QT_HAVE_PPOLL) || _POSIX_VERSION >= 200809L || _XOPEN_VERSION >= 700
# define _POSIX_POLL 1
# elif defined(QT_HAVE_POLL)
# define _POSIX_POLL 0
# else
# define _POSIX_POLL -1
# endif
#endif
#if defined(Q_OS_QNX) || _POSIX_POLL <= 0 || defined(QT_BUILD_INTERNAL)
static inline struct timeval timespecToTimeval(const struct timespec &ts)
{
struct timeval tv;
tv.tv_sec = ts.tv_sec;
tv.tv_usec = ts.tv_nsec / 1000;
return tv;
}
#endif
static inline bool time_update(struct timespec *tv, const struct timespec &start,
const struct timespec &timeout)
{
@ -81,9 +108,7 @@ int qt_safe_select(int nfds, fd_set *fdread, fd_set *fdwrite, fd_set *fdexcept,
#ifndef Q_OS_QNX
ret = ::pselect(nfds, fdread, fdwrite, fdexcept, &timeout, 0);
#else
timeval timeoutVal;
timeoutVal.tv_sec = timeout.tv_sec;
timeoutVal.tv_usec = timeout.tv_nsec / 1000;
timeval timeoutVal = timespecToTimeval(timeout);
ret = ::select(nfds, fdread, fdwrite, fdexcept, &timeoutVal);
#endif
if (ret != -1 || errno != EINTR)
@ -98,15 +123,268 @@ int qt_safe_select(int nfds, fd_set *fdread, fd_set *fdwrite, fd_set *fdexcept,
}
}
static inline struct timespec millisecsToTimespec(const unsigned int ms)
{
struct timespec tv;
tv.tv_sec = ms / 1000;
tv.tv_nsec = (ms % 1000) * 1000 * 1000;
return tv;
}
int qt_select_msecs(int nfds, fd_set *fdread, fd_set *fdwrite, int timeout)
{
if (timeout < 0)
return qt_safe_select(nfds, fdread, fdwrite, 0, 0);
struct timespec tv;
tv.tv_sec = timeout / 1000;
tv.tv_nsec = (timeout % 1000) * 1000 * 1000;
struct timespec tv = millisecsToTimespec(timeout);
return qt_safe_select(nfds, fdread, fdwrite, 0, &tv);
}
#if _POSIX_POLL <= 0 || defined(QT_BUILD_INTERNAL)
#define QT_POLL_READ_MASK (POLLIN | POLLRDNORM)
#define QT_POLL_WRITE_MASK (POLLOUT | POLLWRNORM | POLLWRBAND)
#define QT_POLL_EXCEPT_MASK (POLLPRI | POLLRDBAND)
#define QT_POLL_ERROR_MASK (POLLERR | POLLNVAL)
#define QT_POLL_EVENTS_MASK (QT_POLL_READ_MASK | QT_POLL_WRITE_MASK | QT_POLL_EXCEPT_MASK)
static inline int qt_poll_prepare(struct pollfd *fds, nfds_t nfds,
fd_set *read_fds, fd_set *write_fds, fd_set *except_fds)
{
int max_fd = -1;
FD_ZERO(read_fds);
FD_ZERO(write_fds);
FD_ZERO(except_fds);
for (nfds_t i = 0; i < nfds; i++) {
if (fds[i].fd >= FD_SETSIZE) {
errno = EINVAL;
return -1;
}
if ((fds[i].fd < 0) || (fds[i].revents & QT_POLL_ERROR_MASK))
continue;
if (fds[i].events & QT_POLL_READ_MASK)
FD_SET(fds[i].fd, read_fds);
if (fds[i].events & QT_POLL_WRITE_MASK)
FD_SET(fds[i].fd, write_fds);
if (fds[i].events & QT_POLL_EXCEPT_MASK)
FD_SET(fds[i].fd, except_fds);
if (fds[i].events & QT_POLL_EVENTS_MASK)
max_fd = qMax(max_fd, fds[i].fd);
}
return max_fd + 1;
}
static inline void qt_poll_examine_ready_read(struct pollfd &pfd)
{
int res;
char data;
EINTR_LOOP(res, ::recv(pfd.fd, &data, sizeof(data), MSG_PEEK));
const int error = (res < 0) ? errno : 0;
if (res == 0) {
pfd.revents |= POLLHUP;
} else if (res > 0 || error == ENOTSOCK || error == ENOTCONN) {
pfd.revents |= QT_POLL_READ_MASK & pfd.events;
} else {
switch (error) {
case ESHUTDOWN:
case ECONNRESET:
case ECONNABORTED:
case ENETRESET:
pfd.revents |= POLLHUP;
break;
default:
pfd.revents |= POLLERR;
break;
}
}
}
static inline int qt_poll_sweep(struct pollfd *fds, nfds_t nfds,
fd_set *read_fds, fd_set *write_fds, fd_set *except_fds)
{
int result = 0;
for (nfds_t i = 0; i < nfds; i++) {
if (fds[i].fd < 0)
continue;
if (FD_ISSET(fds[i].fd, read_fds))
qt_poll_examine_ready_read(fds[i]);
if (FD_ISSET(fds[i].fd, write_fds))
fds[i].revents |= QT_POLL_WRITE_MASK & fds[i].events;
if (FD_ISSET(fds[i].fd, except_fds))
fds[i].revents |= QT_POLL_EXCEPT_MASK & fds[i].events;
if (fds[i].revents != 0)
result++;
}
return result;
}
static inline bool qt_poll_is_bad_fd(int fd)
{
int ret;
EINTR_LOOP(ret, fcntl(fd, F_GETFD));
return (ret == -1 && errno == EBADF);
}
static inline int qt_poll_mark_bad_fds(struct pollfd *fds, const nfds_t nfds)
{
int n_marked = 0;
for (nfds_t i = 0; i < nfds; i++) {
if (fds[i].fd < 0)
continue;
if (fds[i].revents & QT_POLL_ERROR_MASK)
continue;
if (qt_poll_is_bad_fd(fds[i].fd)) {
fds[i].revents |= POLLNVAL;
n_marked++;
}
}
return n_marked;
}
Q_AUTOTEST_EXPORT int qt_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts)
{
if (!fds && nfds) {
errno = EFAULT;
return -1;
}
fd_set read_fds, write_fds, except_fds;
struct timeval tv, *ptv = 0;
if (timeout_ts) {
tv = timespecToTimeval(*timeout_ts);
ptv = &tv;
}
int n_bad_fds = 0;
for (nfds_t i = 0; i < nfds; i++) {
fds[i].revents = 0;
if (fds[i].fd < 0)
continue;
if (fds[i].events & QT_POLL_EVENTS_MASK)
continue;
if (qt_poll_is_bad_fd(fds[i].fd)) {
// Mark bad file descriptors that have no event flags set
// here, as we won't be passing them to select below and therefore
// need to do the check ourselves
fds[i].revents = POLLNVAL;
n_bad_fds++;
}
}
forever {
const int max_fd = qt_poll_prepare(fds, nfds, &read_fds, &write_fds, &except_fds);
if (max_fd < 0)
return max_fd;
if (n_bad_fds > 0) {
tv.tv_sec = 0;
tv.tv_usec = 0;
ptv = &tv;
}
const int ret = ::select(max_fd, &read_fds, &write_fds, &except_fds, ptv);
if (ret == 0)
return n_bad_fds;
if (ret > 0)
return qt_poll_sweep(fds, nfds, &read_fds, &write_fds, &except_fds);
if (errno != EBADF)
return -1;
// We have at least one bad file descriptor that we waited on, find out which and try again
n_bad_fds += qt_poll_mark_bad_fds(fds, nfds);
}
}
#endif // _POSIX_POLL <= 0 || defined(QT_BUILD_INTERNAL)
#if !defined(QT_HAVE_PPOLL) && ((_POSIX_POLL > 0) || defined(_SC_POLL))
static inline int timespecToMillisecs(const struct timespec *ts)
{
return (ts == NULL) ? -1 :
(ts->tv_sec * 1000) + (ts->tv_nsec / 1000000);
}
#endif
static inline int qt_ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts)
{
#if defined(QT_HAVE_PPOLL)
return ::ppoll(fds, nfds, timeout_ts, Q_NULLPTR);
#elif _POSIX_POLL > 0
return ::poll(fds, nfds, timespecToMillisecs(timeout_ts));
#else
# if defined(_SC_POLL)
static const bool have_poll = (sysconf(_SC_POLL) > 0);
if (have_poll)
return ::poll(fds, nfds, timespecToMillisecs(timeout_ts));
# endif
return qt_poll(fds, nfds, timeout_ts);
#endif
}
/*!
\internal
Behaves as close to POSIX poll(2) as practical but may be implemented
using select(2) where necessary. In that case, returns -1 and sets errno
to EINVAL if passed any descriptor greater than or equal to FD_SETSIZE.
*/
int qt_safe_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts)
{
if (!timeout_ts) {
// no timeout -> block forever
int ret;
EINTR_LOOP(ret, qt_ppoll(fds, nfds, Q_NULLPTR));
return ret;
}
timespec start = qt_gettime();
timespec timeout = *timeout_ts;
// loop and recalculate the timeout as needed
forever {
const int ret = qt_ppoll(fds, nfds, &timeout);
if (ret != -1 || errno != EINTR)
return ret;
// recalculate the timeout
if (!time_update(&timeout, start, *timeout_ts)) {
// timeout during update
// or clock reset, fake timeout error
return 0;
}
}
}
QT_END_NAMESPACE

View File

@ -66,6 +66,28 @@
# include <ioLib.h>
#endif
#ifndef QT_NO_NATIVE_POLL
# include <poll.h>
#else
struct pollfd {
int fd;
short events, revents;
};
typedef unsigned long int nfds_t;
# define POLLIN 0x001
# define POLLPRI 0x002
# define POLLOUT 0x004
# define POLLERR 0x008
# define POLLHUP 0x010
# define POLLNVAL 0x020
# define POLLRDNORM 0x040
# define POLLRDBAND 0x080
# define POLLWRNORM 0x100
# define POLLWRBAND 0x200
#endif
struct sockaddr;
#define EINTR_LOOP(var, cmd) \
@ -303,6 +325,8 @@ static inline pid_t qt_safe_waitpid(pid_t pid, int *status, int options)
timespec qt_gettime() Q_DECL_NOTHROW;
void qt_nanosleep(timespec amount);
Q_CORE_EXPORT int qt_safe_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts);
Q_CORE_EXPORT int qt_safe_select(int nfds, fd_set *fdread, fd_set *fdwrite, fd_set *fdexcept,
const struct timespec *tv);

View File

@ -0,0 +1,4 @@
CONFIG += testcase
TARGET = tst_qt_poll
QT = core-private network testlib
SOURCES = tst_qt_poll.cpp

View File

@ -0,0 +1,158 @@
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtNetwork>
#include <private/qcore_unix_p.h>
#ifdef QT_BUILD_INTERNAL
QT_BEGIN_NAMESPACE
Q_AUTOTEST_EXPORT int qt_poll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts);
QT_END_NAMESPACE
#endif // QT_BUILD_INTERNAL
QT_USE_NAMESPACE
class tst_qt_poll : public QObject
{
Q_OBJECT
#ifdef QT_BUILD_INTERNAL
private slots:
void pollout();
void pollin();
void pollnval();
void pollprihup();
#endif // QT_BUILD_INTERNAL
};
#ifdef QT_BUILD_INTERNAL
void tst_qt_poll::pollout()
{
int fds[2];
QCOMPARE(pipe(fds), 0);
struct pollfd pfd = { fds[1], POLLOUT, 0 };
const int nready = qt_poll(&pfd, 1, NULL);
QCOMPARE(nready, 1);
QCOMPARE(pfd.revents, short(POLLOUT));
qt_safe_close(fds[0]);
qt_safe_close(fds[1]);
}
void tst_qt_poll::pollin()
{
int fds[2];
QCOMPARE(pipe(fds), 0);
const char data = 'Q';
QCOMPARE(qt_safe_write(fds[1], &data, 1), 1);
struct pollfd pfd = { fds[0], POLLIN, 0 };
const int nready = qt_poll(&pfd, 1, NULL);
QCOMPARE(nready, 1);
QCOMPARE(pfd.revents, short(POLLIN));
qt_safe_close(fds[0]);
qt_safe_close(fds[1]);
}
void tst_qt_poll::pollnval()
{
struct pollfd pfd = { 42, POLLOUT, 0 };
int nready = qt_poll(&pfd, 1, NULL);
QCOMPARE(nready, 1);
QCOMPARE(pfd.revents, short(POLLNVAL));
pfd.events = 0;
pfd.revents = 0;
nready = qt_poll(&pfd, 1, NULL);
QCOMPARE(nready, 1);
QCOMPARE(pfd.revents, short(POLLNVAL));
}
void tst_qt_poll::pollprihup()
{
QTcpServer server;
QTcpSocket client_socket;
QVERIFY(server.listen(QHostAddress::LocalHost));
const quint16 server_port = server.serverPort();
client_socket.connectToHost(server.serverAddress(), server_port);
QVERIFY(client_socket.waitForConnected());
QVERIFY(server.waitForNewConnection());
QTcpSocket *server_socket = server.nextPendingConnection();
server.close();
// TCP supports only a single byte of urgent data
static const char oob_out = 'Q';
QCOMPARE(::send(server_socket->socketDescriptor(), &oob_out, 1, MSG_OOB),
ssize_t(1));
struct pollfd pfd = {
int(client_socket.socketDescriptor()),
POLLPRI | POLLIN,
0
};
int res = qt_poll(&pfd, 1, NULL);
QCOMPARE(res, 1);
QCOMPARE(pfd.revents, short(POLLPRI | POLLIN));
char oob_in = 0;
// We do not specify MSG_OOB here as SO_OOBINLINE is turned on by default
// in the native socket engine
QCOMPARE(::recv(client_socket.socketDescriptor(), &oob_in, 1, 0),
ssize_t(1));
QCOMPARE(oob_in, oob_out);
server_socket->close();
pfd.events = POLLIN;
res = qt_poll(&pfd, 1, NULL);
QCOMPARE(res, 1);
QCOMPARE(pfd.revents, short(POLLHUP));
}
#endif // QT_BUILD_INTERNAL
QTEST_APPLESS_MAIN(tst_qt_poll)
#include "tst_qt_poll.moc"