You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
arts/mcop/iomanager.cpp

495 lines
11 KiB

/*
Copyright (C) 2000 Stefan Westerfeld
stefan@space.twc.de
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "iomanager.h"
#include "dispatcher.h"
#include "notification.h"
#include "thread.h"
#include <stdio.h>
#include <fcntl.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h> // Needed on some systems.
#endif
// WABA: NOTE!
// sys/select.h is needed on e.g. AIX to define "fd_set".
// However, we can not include config.h in a header file.
// The right solution would be not to use "fd_set" in the
// header file but to use it only in a private datastructure
// defined in the .cpp file.
using namespace std;
using namespace Arts;
namespace Arts {
/* internal data structures */
class IOWatchFD {
int _fd, _types;
IONotify *_notify;
public:
int activeTypes;
IOWatchFD(int fd, int types, IONotify *notify);
inline int fd() { return _fd; };
inline int types() { return _types; };
inline IONotify *notify() { return _notify; };
inline void remove(int types) { _types &= ~types; }
};
class TimeWatcher {
int milliseconds;
TimeNotify *_notify;
timeval nextNotify;
bool active, destroyed;
bool earlier(const timeval& reference);
public:
TimeWatcher(int _milliseconds, TimeNotify *notify);
inline TimeNotify *notify() { return _notify; };
timeval advance(const timeval& currentTime);
void destroy();
};
}
/*
* Enable this if you want to debug how long certain plugins / operations
* take to perform. You'll get the times between two select() calls that are
* done by the IOManager, which is equivalent to the time the input/output
* remains unserved. For apps like artsd, it gives the minimum audio latency
* users will need to specify to avoid dropouts.
*/
#undef IOMANAGER_DEBUG_LATENCY
#ifdef IOMANAGER_DEBUG_LATENCY
static timeval iomanager_debug_latency_time = { 0, 0 };
static void iomanager_debug_latency_end()
{
if(iomanager_debug_latency_time.tv_sec)
{
timeval end;
gettimeofday(&end,0);
float diff = (end.tv_usec-iomanager_debug_latency_time.tv_usec)/1000.0
+ (end.tv_sec-iomanager_debug_latency_time.tv_sec)*1000.0;
/* change this value if you get your screen filled up with messages */
if(diff >= 1.5)
fprintf(stderr,"IOManager: latency for operation: %2.3f ms\n",diff);
}
}
static void iomanager_debug_latency_start()
{
gettimeofday(&iomanager_debug_latency_time,0);
}
#else
static inline void iomanager_debug_latency_end()
{
}
static inline void iomanager_debug_latency_start()
{
}
#endif
IOWatchFD::IOWatchFD(int fd, int types, IONotify *notify)
{
_fd = fd;
_types = types;
_notify = notify;
activeTypes = 0;
}
StdIOManager::StdIOManager()
{
// force initialization of the fd_set's
fdListChanged = true;
timeListChanged = false;
level = 0;
}
void StdIOManager::processOneEvent(bool blocking)
{
assert(SystemThreads::the()->isMainThread());
level++;
// we release and acquire the lock only on level 1
if(level == 1)
Dispatcher::lock();
// notifications not carried out reentrant
if(level == 1)
NotificationManager::the()->run();
// FIXME: timers *could* change the file descriptors to select...
//---
if(fdListChanged)
{
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&exceptfds);
FD_ZERO(&reentrant_readfds);
FD_ZERO(&reentrant_writefds);
FD_ZERO(&reentrant_exceptfds);
maxfd = 0;
list<IOWatchFD *>::iterator i;
for(i = fdList.begin(); i != fdList.end(); i++)
{
IOWatchFD *w = *i;
if(w->types() & IOType::read) FD_SET(w->fd(),&readfds);
if(w->types() & IOType::write) FD_SET(w->fd(),&writefds);
if(w->types() & IOType::except) FD_SET(w->fd(),&exceptfds);
if(w->types() & IOType::reentrant)
{
if(w->types() & IOType::read)
FD_SET(w->fd(),&reentrant_readfds);
if(w->types() & IOType::write)
FD_SET(w->fd(),&reentrant_writefds);
if(w->types() & IOType::except)
FD_SET(w->fd(),&reentrant_exceptfds);
}
if(w->types() && w->fd() > maxfd) maxfd = w->fd();
}
fdListChanged = false;
}
fd_set rfd,wfd,efd;
if(level == 1)
{
rfd = readfds;
wfd = writefds;
efd = exceptfds;
}
else
{
// watch out, this is reentrant I/O
rfd = reentrant_readfds;
wfd = reentrant_writefds;
efd = reentrant_exceptfds;
}
/* default timeout 5 seconds */
long selectabs;
if(blocking)
selectabs = 5000000;
else
selectabs = 0;
/* prepare timers - only at level 1 */
if(level == 1 && timeList.size())
{
struct timeval currenttime;
gettimeofday(&currenttime,0);
list<TimeWatcher *>::iterator ti;
timeListChanged = false;
ti = timeList.begin();
while(ti != timeList.end())
{
TimeWatcher *w = *ti++;
timeval timertime = w->advance(currenttime);
// if that may happen in the next ten seconds
if(timertime.tv_sec < currenttime.tv_sec+10)
{
long timerabs = (timertime.tv_sec - currenttime.tv_sec)*1000000;
timerabs += (timertime.tv_usec - currenttime.tv_usec);
if(timerabs < selectabs) selectabs = timerabs;
}
if(timeListChanged)
{
ti = timeList.begin();
timeListChanged = false;
}
}
}
timeval select_timeout;
select_timeout.tv_sec = selectabs / 1000000;
select_timeout.tv_usec = selectabs % 1000000;
if(level == 1) iomanager_debug_latency_end();
// we release and acquire the lock only on level 1
if(level == 1)
Dispatcher::unlock();
int retval = select(maxfd+1,&rfd,&wfd,&efd,&select_timeout);
// we release and acquire the lock only on level 1
if(level == 1)
Dispatcher::lock();
if(level == 1) iomanager_debug_latency_start();
if(retval > 0)
{
/*
* the problem is, that objects that are being notified may change
* the watch list, add fds, remove fds, remove objects and whatever
* else
*
* so we can' notify them from the loop - but we can make a stack
* of "notifications to do" and send them as soon as we looked up
* in the list what to send
*/
long tonotify = 0;
list<IOWatchFD *>::iterator i;
for(i = fdList.begin(); i != fdList.end(); i++) {
IOWatchFD *w = *i;
int match = 0;
if(FD_ISSET(w->fd(),&rfd) && (w->types() & IOType::read))
match |= IOType::read;
if(FD_ISSET(w->fd(),&wfd) && (w->types() & IOType::write))
match |= IOType::write;
if(FD_ISSET(w->fd(),&efd) && (w->types() & IOType::except))
match |= IOType::except;
if((w->types() & IOType::reentrant) == 0 && level != 1)
match = 0;
if(match) {
tonotify++;
w->activeTypes = match;
notifyStack.push(w);
}
}
while(tonotify != 0)
{
if(!fdListChanged)
{
IOWatchFD *w = notifyStack.top();
int activeTypes = w->activeTypes;
int fd = w->fd();
IONotify *notify = w->notify();
w->activeTypes = 0;
notify->notifyIO(fd, activeTypes);
// warning: w and notify might no longer exist here
}
notifyStack.pop();
tonotify--;
}
}
/* handle timers - only at level 1 */
if(level == 1 && timeList.size())
{
struct timeval currenttime;
gettimeofday(&currenttime,0);
list<TimeWatcher *>::iterator ti;
timeListChanged = false;
ti = timeList.begin();
while(ti != timeList.end())
{
TimeWatcher *w = *ti++;
w->advance(currenttime);
if (timeListChanged)
{
ti = timeList.begin();
timeListChanged = false;
}
}
}
// notifications not carried out reentrant
if(level == 1)
NotificationManager::the()->run();
// we release and acquire the lock only on level 1
if(level == 1)
Dispatcher::unlock();
level--;
}
void StdIOManager::run()
{
assert(SystemThreads::the()->isMainThread());
assert(level == 0);
// FIXME: this might not be threadsafe, as there is no lock here!
terminated = false;
while(!terminated)
processOneEvent(true);
}
void StdIOManager::terminate()
{
terminated = true;
Dispatcher::wakeUp();
}
void StdIOManager::watchFD(int fd, int types, IONotify *notify)
{
/*
IOWatchFD *watchfd = findWatch(fd,notify);
if(watchfd)
{
watchfd->add(types);
}
else
{
fdList.push_back(new IOWatchFD(fd,types,notify));
}
*/
// FIXME: might want to reuse old watches
fdList.push_back(new IOWatchFD(fd,types,notify));
fdListChanged = true;
Dispatcher::wakeUp();
}
void StdIOManager::remove(IONotify *notify, int types)
{
list<IOWatchFD *>::iterator i;
i = fdList.begin();
while(i != fdList.end())
{
IOWatchFD *w = *i;
if(w->notify() == notify) w->remove(types);
// nothing left to watch?
if(w->types() == 0 || w->types() == IOType::reentrant)
{
i = fdList.erase(i);
delete w; // FIXME: shouldn't we have a destroy() similar
// to the one for timers
}
else i++;
}
fdListChanged = true;
}
void StdIOManager::addTimer(int milliseconds, TimeNotify *notify)
{
if (milliseconds == -1 && notify == 0) {
// HACK: in order to not add a virtual function to IOManager we're calling addTimer with
// magic values. This call tells the ioManager that notifications are pending and
// NotificationManager::run() should get called soon.
} else {
timeList.push_back(new TimeWatcher(milliseconds,notify));
timeListChanged = true;
Dispatcher::wakeUp();
}
}
void StdIOManager::removeTimer(TimeNotify *notify)
{
list<TimeWatcher *>::iterator i;
i = timeList.begin();
while(i != timeList.end())
{
TimeWatcher *w = *i;
if(w->notify() == notify)
{
i = timeList.erase(i);
timeListChanged = true;
w->destroy();
}
else i++;
}
}
TimeWatcher::TimeWatcher(int _milliseconds, TimeNotify *notify)
: milliseconds(_milliseconds),_notify(notify),active(false),destroyed(false)
{
gettimeofday(&nextNotify,0);
nextNotify.tv_usec += (milliseconds%1000)*1000;
nextNotify.tv_sec += (milliseconds/1000)+(nextNotify.tv_usec/1000000);
nextNotify.tv_usec %= 1000000;
}
timeval TimeWatcher::advance(const timeval& currentTime)
{
active = true;
while(earlier(currentTime))
{
nextNotify.tv_usec += (milliseconds%1000)*1000;
nextNotify.tv_sec += (milliseconds/1000)+(nextNotify.tv_usec/1000000);
nextNotify.tv_usec %= 1000000;
_notify->notifyTime();
if(destroyed)
{
delete this;
struct timeval never = { -1, 0 };
return never;
}
}
active = false;
return nextNotify;
}
bool TimeWatcher::earlier(const timeval& reference)
{
if(nextNotify.tv_sec > reference.tv_sec) return false;
if(nextNotify.tv_sec < reference.tv_sec) return true;
return (nextNotify.tv_usec < reference.tv_usec);
}
void TimeWatcher::destroy()
{
if(active)
{
destroyed = true;
}
else
{
delete this;
}
}