osmo-sip-connector/src/evpoll.c

110 lines
2.7 KiB
C
Raw Permalink Normal View History

/*
* (C) 2016 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "evpoll.h"
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <sys/select.h>
/* based on osmo_select_main GPLv2+ so combined compatible with AGPLv3+ */
int evpoll(struct pollfd *fds, nfds_t nfds, int timeout)
{
struct timeval *tv, null_tv = { 0, 0} , poll_tv;
fd_set readset, writeset, exceptset;
int maxfd, rc, i;
FD_ZERO(&readset);
FD_ZERO(&writeset);
FD_ZERO(&exceptset);
/* prepare read and write fdsets */
maxfd = osmo_fd_fill_fds(&readset, &writeset, &exceptset);
for (i = 0; i < nfds; ++i) {
if (fds[i].fd < 0)
continue;
if ((fds[i].events & (POLLIN | POLLOUT | POLLPRI)) == 0)
continue;
/* copy events. Not sure why glib maps PRI to exceptionset */
if (fds[i].events & POLLIN)
FD_SET(fds[i].fd, &readset);
if (fds[i].events & POLLOUT)
FD_SET(fds[i].fd, &writeset);
if (fds[i].events & POLLPRI)
FD_SET(fds[i].fd, &exceptset);
if (fds[i].fd > maxfd)
maxfd = fds[i].fd;
}
osmo_timers_check();
osmo_timers_prepare();
/*
* 0 == wake-up immediately
* -1 == block forever.. which will depend on our timers
*/
if (timeout == 0) {
tv = &null_tv;
} else if (timeout == -1) {
tv = osmo_timers_nearest();
} else {
poll_tv.tv_sec = timeout / 1000;
poll_tv.tv_usec = (timeout % 1000) * 1000;
tv = osmo_timers_nearest();
if (!tv)
tv = &poll_tv;
else if (timercmp(&poll_tv, tv, <))
tv = &poll_tv;
}
rc = select(maxfd+1, &readset, &writeset, &exceptset, tv);
if (rc < 0)
return 0;
/* fire timers */
osmo_timers_update();
/* call registered callback functions */
osmo_fd_disp_fds(&readset, &writeset, &exceptset);
for (i = 0; i < nfds; ++i) {
fds[i].revents = 0;
if (fds[i].fd < 0)
continue;
if (FD_ISSET(fds[i].fd, &readset))
evpoll: Add workaround for usage of IP_RECVERR in sofia-sip We are using glib to benefit from the sofia-sip-glib eventloop integration and set a poll func (evpoll) to be called by glib to integrate with the rest of libosmocore. Sofia-sip will use IP_RECVERR to enable error reporting on the socket and then sets SU_WAIT_ERR (mapped to POLLERR if not using kqueue) in the internal events flag of the socket. This will be registered with a su_wait (mapped to struct pollfd) and then glib will be called with g_source_add_poll. At this point the the fd->events will still have the POLLERR bit set. Before glib is calling its internal poll routine or our one it will copy all registered fd into an array and mask the events flags: /* In direct contradiction to the Unix98 spec, IRIX runs into * difficulty if you pass in POLLERR, POLLHUP or POLLNVAL * flags in the events field of the pollfd while it should * just ignoring them. So we mask them out here. */ events = pollrec->fd->events & ~(G_IO_ERR|G_IO_HUP|G_IO_NVAL); This leads to the POLLERR flag never been set in the revents of the struct poll_fd and as such we never put them in the exceptionset and as such: static int tport_base_wakeup(tport_t *self, int events) { int error = 0; if (events & SU_WAIT_ERR) error = tport_error_event(self); tport_base_wakeup will never call tport_error_event. And the error will be stuck in the socket data forever and recvmsg will return a zero length packet. And this will repeat until the end of time. As a first hack I mapped SU_WAIT_ERR to POLLPRI but when using select the Linux kernel will not put the socket error into the except queue unless the sockopt SOCK_SELECT_ERR_QUEUE is used. One option is to use poll and then map the select requirements to poll. Right now I just signal POLLERR as well to trigger tport_error_event. This will result in extra syscalls for each received UDP message right now. Change-Id: I5bec4a7b70f421ab670e649e5bc1ea6faf59707c
2017-03-07 06:59:00 +00:00
fds[i].revents = POLLIN | POLLERR;
if (FD_ISSET(fds[i].fd, &writeset))
fds[i].revents |= POLLOUT;
if (FD_ISSET(fds[i].fd, &exceptset))
fds[i].revents |= POLLPRI;
}
return rc;
}