// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-
// vim:set sts=4 ts=8:
// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.
#ident "$XORP: xorp/libxorp/selector.cc,v 1.39 2007/02/16 22:46:22 pavlin Exp $"
#include "libxorp_module.h"
#include "libxorp/xorp.h"
#ifndef HOST_OS_WINDOWS // Entire file is stubbed out on Windows.
#include "libxorp/debug.h"
#include "libxorp/xlog.h"
#include "libxorp/xorpfd.hh"
#include "libxorp/timeval.hh"
#include "libxorp/clock.hh"
#include "libxorp/eventloop.hh"
#include "libxorp/utility.h"
#include "selector.hh"
// ----------------------------------------------------------------------------
// Helper function to deal with translating between old and new
// I/O event notification mechanisms.
static SelectorMask
map_ioevent_to_selectormask(const IoEventType type)
{
SelectorMask mask = SEL_NONE;
// Convert new event type to legacy UNIX event mask used by SelectorList.
switch (type) {
case IOT_READ:
mask = SEL_RD;
break;
case IOT_WRITE:
mask = SEL_WR;
break;
case IOT_EXCEPTION:
mask = SEL_EX;
break;
case IOT_ACCEPT:
mask = SEL_RD;
break;
case IOT_CONNECT:
mask = SEL_WR;
break;
case IOT_DISCONNECT:
mask = SEL_EX; // XXX: Disconnection isn't a distinct event in UNIX
break;
case IOT_ANY:
mask = SEL_ALL;
break;
}
return (mask);
}
// ----------------------------------------------------------------------------
// SelectorList::Node methods
inline
SelectorList::Node::Node()
{
_mask[SEL_RD_IDX] = _mask[SEL_WR_IDX] = _mask[SEL_EX_IDX] = 0;
}
inline bool
SelectorList::Node::add_okay(SelectorMask m, IoEventType type,
const IoEventCb& cb, int priority)
{
int i;
// always OK to try to register for nothing
if (!m)
return true;
// Sanity Checks
// 0. We understand all bits in 'mode'
assert((m & (SEL_RD | SEL_WR | SEL_EX)) == m);
// 1. Check that bits in 'mode' are not already registered
for (i = 0; i < SEL_MAX_IDX; i++) {
if (_mask[i] & m) {
return false;
}
}
// 2. If not already registered, find empty slot and add entry.
// XXX: TODO: Determine if the node we're about to add is for
// an accept event, so we know how to map it back.
for (i = 0; i < SEL_MAX_IDX; i++) {
if (!_mask[i]) {
_mask[i] = m;
_cb[i] = IoEventCb(cb);
_iot[i] = type;
_priority[i] = priority;
return true;
}
}
assert(0);
return false;
}
inline int
SelectorList::Node::run_hooks(SelectorMask m, XorpFd fd)
{
int n = 0;
/*
* This is nasty. We dispatch the callbacks here associated with
* the file descriptor fd. Unfortunately these callbacks can
* manipulate the mask and callbacks associated with the
* descriptor, ie the data change beneath our feet. At no time do
* we want to call a callback that has been removed so we can't
* just copy the data before starting the dispatch process. We do
* not want to perform another callback here on a masked bit that
* we have already done a callback on. We therefore keep track of
* the bits already matched with the variable already_matched.
*
* An alternate fix is to change the semantics of add_ioevent_cb so
* there is one callback for each I/O event.
*
* Yet another alternative is to have an object, let's call it a
* Selector that is a handle state of a file descriptor: ie an fd, a
* mask, a callback and an enabled flag. We would process the Selector
* state individually.
*/
SelectorMask already_matched = SelectorMask(0);
for (int i = 0; i < SEL_MAX_IDX; i++) {
SelectorMask match = SelectorMask(_mask[i] & m & ~already_matched);
if (match) {
assert(_cb[i].is_empty() == false);
_cb[i]->dispatch(fd, _iot[i]);
n++;
}
already_matched = SelectorMask(already_matched | match);
}
return n;
}
inline void
SelectorList::Node::clear(SelectorMask zap)
{
for (size_t i = 0; i < SEL_MAX_IDX; i++) {
_mask[i] &= ~zap;
if (_mask[i] == 0) {
_cb[i].release();
_priority[i] = XorpTask::PRIORITY_INFINITY;
}
}
}
inline bool
SelectorList::Node::is_empty()
{
return ((_mask[SEL_RD_IDX] == 0) && (_mask[SEL_WR_IDX] == 0) &&
(_mask[SEL_EX_IDX] == 0));
}
// ----------------------------------------------------------------------------
// SelectorList implementation
SelectorList::SelectorList(ClockBase *clock)
: _clock(clock), _observer(NULL), _maxfd(0), _descriptor_count(0)
{
static_assert(SEL_RD == (1 << SEL_RD_IDX) && SEL_WR == (1 << SEL_WR_IDX)
&& SEL_EX == (1 << SEL_EX_IDX) && SEL_MAX_IDX == 3);
for (int i = 0; i < SEL_MAX_IDX; i++)
FD_ZERO(&_fds[i]);
}
SelectorList::~SelectorList()
{
}
bool
SelectorList::add_ioevent_cb(XorpFd fd,
IoEventType type,
const IoEventCb& cb,
int priority)
{
SelectorMask mask = map_ioevent_to_selectormask(type);
if (mask == 0) {
XLOG_FATAL("SelectorList::add_ioevent_cb: attempt to add invalid event "
"type (type = %d)\n", type);
}
if (!fd.is_valid()) {
XLOG_FATAL("SelectorList::add_ioevent_cb: attempt to add invalid file "
"descriptor (fd = %s)\n", fd.str().c_str());
}
bool resize = false;
if (fd >= _maxfd) {
_maxfd = fd;
size_t entries_n = _selector_entries.size();
if ((size_t)fd >= entries_n) {
_selector_entries.resize(fd + 32);
for (size_t j = entries_n; j < _selector_entries.size(); j++) {
for (int i = 0; i < SEL_MAX_IDX; i++) {
_selector_entries[j]._priority[i] = XorpTask::PRIORITY_INFINITY;
}
}
resize = true;
}
}
bool no_selectors_with_fd = _selector_entries[fd].is_empty();
if (_selector_entries[fd].add_okay(mask, type, cb, priority) == false) {
return false;
}
if (no_selectors_with_fd)
_descriptor_count++;
for (int i = 0; i < SEL_MAX_IDX; i++) {
if (mask & (1 << i)) {
FD_SET(fd, &_fds[i]);
if (_observer) _observer->notify_added(fd, mask);
}
}
return true;
}
void
SelectorList::remove_ioevent_cb(XorpFd fd, IoEventType type)
{
bool found = false;
if (fd < 0 || fd >= (int)_selector_entries.size()) {
XLOG_ERROR("Attempting to remove fd = %d that is outside range of "
"file descriptors 0..%u", (int)fd,
XORP_UINT_CAST(_selector_entries.size()));
return;
}
SelectorMask mask = map_ioevent_to_selectormask(type);
for (int i = 0; i < SEL_MAX_IDX; i++) {
if (mask & (1 << i) && FD_ISSET(fd, &_fds[i])) {
found = true;
FD_CLR(fd, &_fds[i]);
if (_observer)
_observer->notify_removed(fd, ((SelectorMask) (1 << i)));
}
}
if (! found) {
// XXX: no event that needs to be removed has been found
return;
}
_selector_entries[fd].clear(mask);
if (_selector_entries[fd].is_empty()) {
assert(FD_ISSET(fd, &_fds[SEL_RD_IDX]) == 0);
assert(FD_ISSET(fd, &_fds[SEL_WR_IDX]) == 0);
assert(FD_ISSET(fd, &_fds[SEL_EX_IDX]) == 0);
_descriptor_count--;
}
}
bool
SelectorList::ready()
{
fd_set testfds[SEL_MAX_IDX];
int n = 0;
memcpy(testfds, _fds, sizeof(_fds));
struct timeval tv_zero;
tv_zero.tv_sec = 0;
tv_zero.tv_usec = 0;
n = ::select(_maxfd + 1,
&testfds[SEL_RD_IDX],
&testfds[SEL_WR_IDX],
&testfds[SEL_EX_IDX],
&tv_zero);
if (n < 0) {
switch (errno) {
case EBADF:
callback_bad_descriptors();
break;
case EINVAL:
XLOG_FATAL("Bad select argument");
break;
case EINTR:
// The system call was interrupted by a signal, hence return
// immediately to the event loop without printing an error.
debug_msg("SelectorList::ready() interrupted by a signal\n");
break;
default:
XLOG_ERROR("SelectorList::ready() failed: %s", strerror(errno));
break;
}
return false;
}
if (n == 0)
return false;
else
return true;
}
int
SelectorList::get_ready_priority()
{
fd_set testfds[SEL_MAX_IDX];
int n = 0;
memcpy(testfds, _fds, sizeof(_fds));
struct timeval tv_zero;
tv_zero.tv_sec = 0;
tv_zero.tv_usec = 0;
n = ::select(_maxfd + 1,
&testfds[SEL_RD_IDX],
&testfds[SEL_WR_IDX],
&testfds[SEL_EX_IDX],
&tv_zero);
if (n < 0) {
switch (errno) {
case EBADF:
callback_bad_descriptors();
break;
case EINVAL:
XLOG_FATAL("Bad select argument");
break;
case EINTR:
// The system call was interrupted by a signal, hence return
// immediately to the event loop without printing an error.
debug_msg("SelectorList::ready() interrupted by a signal\n");
break;
default:
XLOG_ERROR("SelectorList::ready() failed: %s", strerror(errno));
break;
}
return XorpTask::PRIORITY_INFINITY;
}
if (n == 0)
return XorpTask::PRIORITY_INFINITY;
int max_priority = XorpTask::PRIORITY_INFINITY;
for (int fd = 0; fd <= _maxfd; fd++) {
for (int sel_idx = 0; sel_idx < SEL_MAX_IDX; sel_idx++) {
if (FD_ISSET(fd, &testfds[sel_idx])) {
int p = _selector_entries[fd]._priority[sel_idx];
if (p < max_priority)
max_priority = p;
}
}
}
return max_priority;
}
int
SelectorList::wait_and_dispatch(TimeVal* timeout)
{
fd_set testfds[SEL_MAX_IDX];
int n = 0;
memcpy(testfds, _fds, sizeof(_fds));
if (timeout == 0 || *timeout == TimeVal::MAXIMUM()) {
n = ::select(_maxfd + 1,
&testfds[SEL_RD_IDX],
&testfds[SEL_WR_IDX],
&testfds[SEL_EX_IDX],
0);
} else {
struct timeval tv_to;
timeout->copy_out(tv_to);
n = ::select(_maxfd + 1,
&testfds[SEL_RD_IDX],
&testfds[SEL_WR_IDX],
&testfds[SEL_EX_IDX],
&tv_to);
}
_clock->advance_time();
if (n < 0) {
switch (errno) {
case EBADF:
callback_bad_descriptors();
break;
case EINVAL:
XLOG_FATAL("Bad select argument (probably timeval)");
break;
case EINTR:
// The system call was interrupted by a signal, hence return
// immediately to the event loop without printing an error.
debug_msg("SelectorList::wait_and_dispatch() interrupted by a signal\n");
break;
default:
XLOG_ERROR("SelectorList::wait_and_dispatch() failed: %s", strerror(errno));
break;
}
return 0;
}
for (int fd = 0; fd <= _maxfd; fd++) {
int mask = 0;
if (FD_ISSET(fd, &testfds[SEL_RD_IDX])) {
mask |= SEL_RD;
FD_CLR(fd, &testfds[SEL_RD_IDX]); // paranoia
}
if (FD_ISSET(fd, &testfds[SEL_WR_IDX])) {
mask |= SEL_WR;
FD_CLR(fd, &testfds[SEL_WR_IDX]); // paranoia
}
if (FD_ISSET(fd, &testfds[SEL_EX_IDX])) {
mask |= SEL_EX;
FD_CLR(fd, &testfds[SEL_EX_IDX]); // paranoia
}
if (mask) {
_selector_entries[fd].run_hooks(SelectorMask(mask), fd);
}
}
for (int i = 0; i <= _maxfd; i++) {
assert(!FD_ISSET(i, &testfds[SEL_RD_IDX])); // paranoia
assert(!FD_ISSET(i, &testfds[SEL_WR_IDX])); // paranoia
assert(!FD_ISSET(i, &testfds[SEL_EX_IDX])); // paranoia
}
return n;
}
int
SelectorList::wait_and_dispatch(int millisecs)
{
TimeVal t(millisecs / 1000, (millisecs % 1000) * 1000);
return wait_and_dispatch(&t);
}
void
SelectorList::get_fd_set(SelectorMask selected_mask, fd_set& fds) const
{
if ((SEL_RD != selected_mask) && (SEL_WR != selected_mask) &&
(SEL_EX != selected_mask)) return;
if (SEL_RD == selected_mask) fds = _fds [SEL_RD_IDX];
if (SEL_WR == selected_mask) fds = _fds [SEL_WR_IDX];
if (SEL_EX == selected_mask) fds = _fds [SEL_EX_IDX];
return;
}
int
SelectorList::get_max_fd() const
{
return _maxfd;
}
//
// Note that this method should be called only if there are bad file
// descriptors.
//
void
SelectorList::callback_bad_descriptors()
{
int bc = 0; /* bad descriptor count */
for (int fd = 0; fd <= _maxfd; fd++) {
if (_selector_entries[fd].is_empty() == true)
continue;
/*
* Check whether fd is valid.
*/
struct stat sb;
if ((fstat(fd, &sb) < 0) && (errno == EBADF)) {
//
// Force callbacks, should force read/writes that fail and
// client should remove descriptor from list.
//
XLOG_ERROR("SelectorList found file descriptor %d no longer "
"valid.", fd);
_selector_entries[fd].run_hooks(SEL_ALL, fd);
bc++;
}
}
//
// Assert should only fail if we called this method when there were
// no bad file descriptors, or if fstat() didn't return the appropriate
// error.
//
XLOG_ASSERT(bc != 0);
}
void
SelectorList::set_observer(SelectorListObserverBase& obs)
{
_observer = &obs;
_observer->_observed = this;
return;
}
void
SelectorList::remove_observer()
{
if (_observer) _observer->_observed = NULL;
_observer = NULL;
return;
}
SelectorListObserverBase::~SelectorListObserverBase()
{
if (_observed) _observed->remove_observer();
}
#endif // !HOST_OS_WINDOWS
syntax highlighted by Code2HTML, v. 0.9.1