summaryrefslogtreecommitdiff
path: root/comm/Scheduler.cc
diff options
context:
space:
mode:
Diffstat (limited to 'comm/Scheduler.cc')
-rw-r--r--comm/Scheduler.cc719
1 files changed, 719 insertions, 0 deletions
diff --git a/comm/Scheduler.cc b/comm/Scheduler.cc
new file mode 100644
index 0000000..075c461
--- /dev/null
+++ b/comm/Scheduler.cc
@@ -0,0 +1,719 @@
+/*
+ * The Unix Channel
+ *
+ * by Michel Beaudouin-Lafon
+ *
+ * Copyright 1990-1997
+ * Laboratoire de Recherche en Informatique (LRI)
+ *
+ * Channel sets, or multiplexers
+ *
+ * $Id$
+ * $CurLog$
+ * Amelioration de la gestion des timers dans Scan
+ * Amelioration de la gestion des timers dans LoopScan
+ * Modification du mecanisme d'ajout d'un Channel
+ * Remplacement du HandleSelect par un systeme de Hook
+ * Integration avec IvlBaseMultiplexer
+ * Meilleures verifications de NFILE, en attendant mieux
+ */
+
+#include "Scheduler.h"
+#include "TimeOut.h"
+#include "SignalHandler.h"
+
+#include <signal.h> // for NSIG
+#include <sys/errno.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+#include <setjmp.h>
+
+#if defined(__hpux) && !defined(__GNUG__)
+#define FD_SET_TYPE(x) ((int*) x)
+#else
+#define FD_SET_TYPE(x) (x)
+#endif
+
+#ifdef __osf__
+extern "C" int select (int, fd_set*, fd_set*, fd_set*, struct timeval*);
+#endif
+
+extern int errno;
+
+IvlBaseScheduler* IvlScd = 0;
+
+#ifndef FD_SET
+#ifndef NFDBITS
+#define FD_SET(n, p) ((p)->fds_bits[0] |= (1 << (n)))
+#define FD_CLR(n, p) ((p)->fds_bits[0] &= ~(1 << (n)))
+#define FD_ISSET(n, p) ((p)->fds_bits[0] & (1 << (n)))
+#define FD_ZERO(p) ((p)->fds_bits[0] = 0)
+#else
+#define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
+#define FD_CLR(n, p) ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
+#define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
+#define FD_ZERO(p) memset((char *)(p), 0, sizeof(*(p)))
+#endif
+#endif
+
+/*?class IvlBaseScheduler
+Schedulers make it possible to handle several communication channels at the
+same time; their implementation is based on the Unix \com{select} system call
+or similar mechanisms. Schedulers are also able to implement safe (ie. not
+interruption-based) timers and signal handlers.
+
+Some of the channels in the set are active, others can be inactive. A
+multiplexer can be scanned so that the functions \fun{HandleRead} and
+\fun{HandleWrite} are called for every active channel that is ready to
+read or write.
+
+If a channel needs to perform a background task or buffer input/output,
+it can add a hook on the multiplexer with \fun{AddHook} which will be
+called before actually scanning. For example that's the case for a connection
+with the X server, when some pending events have been buffered into the
+client by the previous \fun{HandleRead} call and need to be processed.
+
+Schedulers may have different implementations, depending on the
+environment. When no other library is involved, they are simply
+implemented on top of Unix. But if \uch\ channels have to be mixed
+with the X Toolkit for instance, the implementation uses the
+mechanisms provided by the X Toolkit to add new channels. The same
+holds for Tcl/Tk or other environments. Every such implentation
+corresponds to a derived class of \typ{IvlBaseScheduler}, which
+offers a uniform interface that hides the implementation
+differences. See the class \typ{IvlScheduler} for a ``pure \uch''
+implementation of multiplexers.
+
+Note that the functions that take an integer file descriptor as
+argument can be passed a \typ{FILDES} or a \typ{IvlChannel} because
+the corresponding conversion operators are defined.
+
+The way to Add/Remove a channel from a multiplexer is to call
+\fun{Add} and \fun{Remove} of the channel with the multiplexer as an
+argument for the first one.
+
+?*/
+
+/*?
+Create an empty channel set.
+?*/
+IvlBaseScheduler :: IvlBaseScheduler ()
+: IvlBaseMultiplexer (),
+ Channels (new (IvlChannel*) [NFILE]),
+ Timers (),
+ ReadCount (0),
+ WriteCount (0),
+ SelectCount (0),
+ SigFired (false),
+ Handlers (new IvlBaseScheduledHandler* [NSIG]),
+ NbSignals (new int [NSIG])
+{
+ memset (Handlers, 0, NSIG * sizeof (IvlBaseScheduledHandler*));
+ memset (NbSignals, 0, NSIG * sizeof (int));
+ memset (Channels, 0, NFILE * sizeof (IvlChannel*));
+}
+
+/*?nodoc?*/
+IvlBaseScheduler :: ~IvlBaseScheduler ()
+{
+#ifdef CPLUS_BUG4
+ delete [NFILE] Channels;
+ delete [NSIG] NbSignals;
+ delete [NSIG] Handlers;
+#else
+ delete [] Channels;
+ delete [] NbSignals;
+ delete [] Handlers;
+#endif
+ Channels = 0;
+}
+
+/*?
+This array operator returns a pointer to the channel corresponding to file descriptor \var{fd}.
+If there is no such channel in this channel set, the operator returns NIL.
+?*/
+IvlChannel*
+IvlBaseScheduler :: operator [] (int fd)
+{
+ return (fd < 0 || fd >= NFILE) ? 0 : Channels [fd];
+}
+
+/*?
+Add a channel to the multiplexer. No copy is made by this
+function. If a channel with the same file descriptor as the one being
+added is already in the set, the old channel is first removed. That
+means that for a given file descriptor, there can be only one channel
+working on it at a time.
+?*/
+bool
+IvlBaseScheduler :: Add (IvlChannel* chan)
+{
+ int fd = chan->FilDes ();
+ if (fd < 0 || fd >= NFILE)
+ return false;
+ IvlChannel* ochan = Channels [fd];
+ if (ochan)
+ ochan->Remove ();
+ Channels [fd] = chan;
+ SetMasks (fd, chan->IOMode ());
+ return true;
+}
+
+/*?
+Remove a channel from the set. This function can be passed a \var{IvlChannel}.
+?*/
+bool
+IvlBaseScheduler :: Remove (int fd)
+{
+ if (fd < 0 || fd >= NFILE)
+ return false;
+ IvlChannel* ch = Channels [fd];
+ if (ch) {
+ SetMasks (fd, IONone);
+ Channels [fd] = 0;
+ }
+ return true;
+}
+
+/*?
+Remove all channels from this channel set.
+This function calls \fun{Remove} for all channels of the set.
+This is a way of exiting from \fun{LoopScan}.
+?*/
+void
+IvlBaseScheduler :: RemoveAll ()
+{
+ for (int fd = 0; fd < NFILE; fd++) {
+ IvlChannel* ch = Channels [fd];
+ if (ch)
+ ch->Remove (); // Calls IvlBaseMultiplexer::Remove
+ }
+}
+
+/*?
+Change the mode of a channel in the set.
+Mode \var{IONone} makes the channel inactive (without removing it).
+This function can be passed a \var{IvlChannel}.
+?*/
+void
+IvlBaseScheduler :: SetMode (int fd, IOMODE mode)
+{
+ if (fd < 0)
+ return;
+
+ SetMasks (fd, mode);
+ Channels [fd] -> SetMode (mode);
+}
+
+/*!
+This function is called by IvlScheduledHandlers when a signal is
+received. It just stores the signal for future (and safe) handling.
+!*/
+/*?hidden?*/
+void
+IvlBaseScheduler :: HandleSignal (IvlBaseScheduledHandler& h)
+{
+ if (!SigFired)
+ AddSignalHook ();
+ SigFired = true;
+ int sig = h.GetSignal () - 1;
+ /* We have to work with arrays, because we are in a signal
+ handler, and we cannot use lists */
+ Handlers [sig] = &h;
+ NbSignals [sig]++;
+
+}
+
+/*!
+This virtual function is called the first time when a signal is received by a
+signal handler associated to the multiplexer. It makes it possible
+to call implementation-dependant functions that defer the handling to
+safer times.
+!*/
+/*
+Does not need to be defined in IvlScheduler, because
+they use the flag SigFired.
+*/
+/*?hidden?*/
+void
+IvlBaseScheduler :: AddSignalHook ()
+{
+}
+
+/*!
+This function triggers the handling of all deferred signals. It should be
+called when \fun{AddSignalHook} has been called and the times are safe.
+!*/
+/*?hidden?*/
+void
+IvlBaseScheduler :: HandleDeferredSignals ()
+{
+ IvlSignalBlocker b (AllSigs);
+ int c;
+ for (int i = 0; i < NSIG; ++i)
+ if (c = NbSignals [i]) {
+ Handlers [i]->DeferredHandle (c);
+ NbSignals [i] = 0;
+ Handlers [i] = 0;
+ }
+}
+
+/*?hidden?*/
+void
+IvlBaseScheduler :: SetMasks (int, IOMODE)
+{
+}
+
+static jmp_buf reset_run;
+
+class IvlMpxAborter : public IvlBaseScheduledHandler {
+public:
+ IvlMpxAborter (IvlBaseScheduler& m, int s) : IvlBaseScheduledHandler (m, s) {}
+ void DeferredHandle (int) { MyScd.Abort (); }
+};
+
+/*?
+Run a multiplexer: make it repeatedly scan its channels, and take the
+appropriate actions. This function will exit under a number of
+circumstances, reflected by its return value.
+\small
+\begin{tabular}{ll}
+\var{isMpxTerminated}&the method \fun{Close} has been called.\\
+\var{isMpxEmpty}&there are no more channels in the set.\\
+\var{isMpxAborted}&the method \fun{Abort} has been called,
+or signals SIGINT or SIGTERM were received.\\
+\var{isMpxError}&an error has occured.
+\end{tabular}
+?*/
+MPX_RES
+IvlBaseScheduler :: Run ()
+{
+ if (setjmp (reset_run))
+ return isMpxAborted;
+
+ IvlMpxAborter h1 (*this, SigTerm);
+ IvlMpxAborter h2 (*this, SigInt);
+
+ return Loop ();
+}
+
+/*?
+Remove all channels from the multiplexer, thus stopping it if running.
+?*/
+void
+IvlBaseScheduler :: Close ()
+{
+ RemoveAll ();
+ Looping = false;
+}
+
+/*?
+Stop the multiplexer, without cleaning it.
+?*/
+void
+IvlBaseScheduler :: Abort ()
+{
+ if (Looping)
+ longjmp (reset_run, 1);
+}
+
+/*?class IvlScheduler
+The class \typ{IvlScheduler} is the default implementation of multiplexers,
+used when there are no compatibility needs. For historical reasons, it offers
+a number of additional methods.
+?*/
+
+/*?
+Build an empty multiplexer.
+?*/
+IvlScheduler :: IvlScheduler ()
+: IvlBaseScheduler (),
+ TimeOut (-1),
+ Looping (false),
+ Hooks ()
+
+{
+ FD_ZERO (&ReadMask);
+ FD_ZERO (&WriteMask);
+ FD_ZERO (&SelectMask);
+}
+
+/*?nodoc?*/
+IvlScheduler :: ~IvlScheduler ()
+{
+}
+
+// update the masks when channel fd changes its mode
+//
+/*?hidden?*/
+void
+IvlScheduler :: SetMasks (int fd, IOMODE mode)
+{
+ if (mode & IORead) {
+ if (! FD_ISSET (fd, &ReadMask)) {
+ ReadCount++;
+ FD_SET (fd, &ReadMask);
+ }
+ } else {
+ if (FD_ISSET (fd, &ReadMask)) {
+ FD_CLR (fd, &ReadMask);
+ ReadCount--;
+ }
+ }
+
+ if (mode & IOWrite) {
+ if (! FD_ISSET (fd, &WriteMask)) {
+ WriteCount++;
+ FD_SET (fd, &WriteMask);
+ }
+ } else {
+ if (FD_ISSET (fd, &WriteMask)) {
+ FD_CLR (fd, &WriteMask);
+ WriteCount--;
+ }
+ }
+
+ if (mode & IOSelect) {
+ if (! FD_ISSET (fd, &SelectMask)) {
+ SelectCount++;
+ FD_SET (fd, &SelectMask);
+ }
+ } else {
+ if (FD_ISSET (fd, &SelectMask)) {
+ FD_CLR (fd, &SelectMask);
+ SelectCount--;
+ }
+ }
+
+}
+
+#if 0
+/*?
+This function calls the select handler of each channel of the set in select mode.
+It returns true as soon as one select handler returns true,
+else it returns false when the select handlers have been called.
+?*/
+bool
+IvlScheduler :: HandleSelect ()
+{
+ int fd, nfd;
+
+ if (fd0 >= NFILE)
+ fd0 = 0;
+ for (fd = fd0++, nfd = SelectCount; nfd; (fd < NFILE) ? fd++ : (fd = 0)) {
+ if (! FD_ISSET (fd, &SelectMask))
+ continue;
+ if (Channels [fd] -> HandleSelect ())
+ return true;
+ nfd--;
+ if (! Looping)
+ return false;
+ }
+ return false;
+}
+
+#endif
+
+////// should add signal handling
+////// Hooks are probably badly handled
+////// Scan should disappear anyway. Only use is in Xtv.
+/*?nextdoc?*/
+int
+IvlScheduler :: Scan (bool nointr, bool poll)
+{
+ fd_set rmsk, wmsk;
+ int nfd, ret = -1;
+ register int fd;
+ struct timeval tv;
+ struct timeval* timeout = 0;
+
+ if (poll) {
+ timeout = &tv;
+ tv.tv_sec = tv.tv_usec = 0;
+ }
+
+ while (ret <= 0) {
+ if (ReadCount == 0 && WriteCount == 0 && SelectCount == 0) {
+ ExecHooks (true);
+ return 0;
+ }
+ ExecHooks (false);
+
+ rmsk = ReadMask;
+ wmsk = WriteMask;
+
+ /* let's fiddle with ret to skip select if we are late with timers */
+ ret = -1;
+ if (!poll && TimeOut != -1) {
+ IvlTimeStamp now;
+ Millisecond delay = TimeOut - now;
+ /* if delay < 0, select is unhappy */
+ if (delay < 0)
+ ret = 0;
+ tv.tv_sec = delay / 1000;
+ tv.tv_usec = 1000 * (delay % 1000);
+ timeout = &tv;
+ }
+
+ /* skipping select is a trick to avoid problems with timers. However,
+ something more general should be done to:
+ - manage priorities (I'm not sure the current situation is sound)
+ - handle situations when we are late.
+ */
+ /* Call select, except if late with timers */
+ if (ret != 0)
+ ret = select (NFILE, FD_SET_TYPE(&rmsk), FD_SET_TYPE(&wmsk), 0, timeout);
+
+ /* Handle time out */
+ if (ret == 0) {
+ if (TimeOut != -1)
+ /* Fire might be called without reason if polling is on. Too bad... */
+ IvlCoreTimer::Fire (&Timers);
+ else if (!poll)
+ fprintf (stderr, "select returned 0 without reason!\n");
+ return 0;
+
+ /* Handle errors */
+ } else if (ret < 0) {
+ if (nointr && ret == -1 && errno == EINTR)
+ continue;
+ else {
+ ExecHooks (true);
+ return ret;
+ }
+ }
+ }
+
+ /* Finally, handle pending input and output */
+ for (fd = 0, nfd = ret; nfd; fd++) {
+ if (FD_ISSET (fd, &wmsk)) {
+ nfd--;
+ IvlChannel* ch = Channels [fd];
+ if (ch)
+ ch->HandleWrite ();
+ }
+ if (FD_ISSET (fd, &rmsk)) {
+ nfd--;
+ IvlChannel* ch = Channels [fd];
+ if (ch)
+ ch->HandleRead ();
+ }
+ }
+
+ return ret;
+}
+
+/*?
+Scan the channels in the set and call the channel's handlers.
+First the select handler (\fun{IvlChannel::HandleSelect}) of each channel with mode
+\var{IOSelect} is called.
+If it returns true, \fun{Scan} exits immediately, while \fun{LoopScan} loops immediately.
+If no select handler returns true, the system call \fun{select} is used to poll or
+wait for the channels that are ready.
+When the select returns normally, the write handlers \fun{HandleWrite} of the
+channels in mode \var{IOWrite} that are ready to write are called, and
+the read handlers (\fun{HandleRead}) of the channels in mode \fun{IORead}
+that are ready to read are called.
+If \var{nointr} is true, ignore the interrupted system calls, else return an error
+whenever the \fun{select} system call is interrupted.
+If \var{poll} is true, the call is non-blocking, else it is blocking.
+\fun{Scan} calls \fun{select} only once;
+it returns -2 if a select handler returns, it returns 0 if the channel set has no active channels,
+else it returns the return code of \fun{select}.
+\fun{LoopScan} calls \fun{select} repeatedly until \var{Stop} is called, or
+an error occurs, the channel set has no more active channel
+This can occur because the select, read or write handler of a channel may remove
+the channel or change its mode.
+This is usually done when the read handler detects an end of file.
+\fun{LoopScan} returns \var{isMpxEmpty} when there are no more active channel in the set,
+it returns \var{isMpxError} when an error occured (the code is in \var{errno}),
+and it returns \var{isMpxTerminated} if \fun{Stop} or \fun{Close} was called.
+?*/
+MPX_RES
+IvlScheduler :: LoopScan (bool nointr)
+{
+ fd_set rmsk, wmsk;
+ register int nb_pending_fd, fd, fd0;
+ Looping = true;
+
+ for (fd0 = 0; Looping; fd0 < NFILE ? fd0++ : (fd0 = 0)) {
+
+ /* First, handle signals */
+ if (SigFired)
+ HandleDeferredSignals ();
+
+#if 0
+ /* Then, timers */
+ IvlCoreTimer::Fire (&Timers);
+#endif
+ /* Then, check I/Os */
+ if (ReadCount == 0 && WriteCount == 0 && SelectCount == 0) {
+ ExecHooks (true);
+ return isMpxEmpty;
+ }
+
+ /* Then other hooks */
+ ExecHooks (false);
+
+ /* Lets get ready for calling select: set masks and compute next time out.
+ We fiddle with nb_pending_fd to skip select if we are late with timers */
+ nb_pending_fd = -1;
+ rmsk = ReadMask;
+ wmsk = WriteMask;
+ struct timeval tv;
+ struct timeval* timeout = 0;
+ if (TimeOut != -1) {
+ IvlTimeStamp now;
+ Millisecond delay = TimeOut - now;
+ if (delay < 0)
+ nb_pending_fd = 0;
+ tv.tv_sec = delay / 1000;
+ tv.tv_usec = 1000 * (delay % 1000);
+ timeout = &tv;
+ }
+
+ /* Call select, except if late with timers */
+ if (nb_pending_fd != 0)
+ nb_pending_fd = select (NFILE, FD_SET_TYPE(&rmsk), FD_SET_TYPE(&wmsk), 0, timeout);
+ /* Handle time out */
+ if (nb_pending_fd == 0) {
+ if (TimeOut != -1)
+ IvlCoreTimer::Fire (&Timers);
+ else
+ fprintf (stderr, "select returned 0 without reason!\n");
+ continue;
+
+ /* Handle errors */
+ } else if (nb_pending_fd < 0) {
+ if (nointr && nb_pending_fd == -1 && errno == EINTR)
+ continue;
+ else {
+ ExecHooks (true);
+ return isMpxError;
+ }
+ }
+
+ /* Finally, handle pending input and output */
+ for (fd = fd0; nb_pending_fd; (fd < NFILE) ? fd++ : (fd = 0)) {
+ if (FD_ISSET (fd, &wmsk)) {
+ nb_pending_fd--;
+ Channels[fd]->HandleWrite ();
+ if (! Looping) {
+ ExecHooks (true);
+ return isMpxTerminated;
+ }
+ }
+ if (FD_ISSET (fd, &rmsk)) {
+ nb_pending_fd--;
+ Channels[fd]->HandleRead ();
+ if (! Looping) {
+ ExecHooks (true);
+ return isMpxTerminated;
+ }
+ }
+ }
+ }
+
+ ExecHooks (true);
+ return isMpxTerminated;
+}
+
+/*?nodoc?*/
+char*
+IvlScheduler :: StrRepr (char* buf)
+{
+ sprintf (buf, "R:%ux W:%ux", ReadMask, WriteMask);
+ return buf;
+}
+
+/*?hidden?*/
+void
+IvlScheduler :: SetTimeOut (Millisecond delay)
+{
+ IvlTimeStamp now;
+ TimeOut = now + delay;
+}
+
+/*?hidden?*/
+void
+IvlScheduler :: SuppressTimeOut ()
+{
+ TimeOut = -1;
+}
+
+/*?hidden?*/
+MPX_RES
+IvlScheduler :: Loop ()
+{
+ return LoopScan ();
+}
+
+void
+IvlScheduler :: Stop ()
+{
+ Looping = false;
+}
+
+void
+IvlScheduler :: AddHook (IvlMpxHook* h, bool final)
+{
+ Hooks.Append ((void*) h);
+ if (final)
+ FinalHooks.Append ((void*) h);
+}
+
+void
+IvlScheduler :: RemoveHook (IvlMpxHook* h, bool final)
+{
+ Hooks.Remove ((void*) h);
+ if (final)
+ FinalHooks.Remove ((void*) h);
+}
+
+void
+IvlScheduler :: AddFinalHook (IvlMpxHook* h)
+{
+ FinalHooks.Append ((void*) h);
+}
+
+void
+IvlScheduler :: RemoveFinalHook (IvlMpxHook* h)
+{
+ FinalHooks.Remove ((void*) h);
+}
+
+void
+IvlScheduler :: ExecHooks (bool final)
+{
+ IvlList* hooks = final ? &FinalHooks : &Hooks;
+ IvlListIter li = *hooks;
+ while (++li) {
+ IvlMpxHook* h = (IvlMpxHook*) *li;
+ (*h)();
+ }
+}
+
+void
+IvlOpen (IvlBaseScheduler* m)
+{
+ IvlScd = m ? m : new IvlScheduler;
+ IvlMpx = IvlScd;
+}
+
+#if 0 /* rendu inutile par la fusion avec DNN ? */
+MPX_RES
+IvlLoop ()
+{
+ return IvlScd->Loop ();
+}
+
+void
+IvlStop ()
+{
+ IvlScd->Stop ();
+}
+#endif