summaryrefslogtreecommitdiffstats
path: root/mcop/iomanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mcop/iomanager.cpp')
-rw-r--r--mcop/iomanager.cpp494
1 files changed, 494 insertions, 0 deletions
diff --git a/mcop/iomanager.cpp b/mcop/iomanager.cpp
new file mode 100644
index 0000000..897a61c
--- /dev/null
+++ b/mcop/iomanager.cpp
@@ -0,0 +1,494 @@
+ /*
+
+ Copyright (C) 2000 Stefan Westerfeld
+
+ 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;
+ }
+}