From ee937667fd0ecd82faab4c88d756b906fb625f1a Mon Sep 17 00:00:00 2001 From: sc Date: Tue, 28 Nov 2000 17:07:47 +0000 Subject: Integration into IvyLeague Uvh -> Ivl Multiplexer.* is renamed into Scheduler.* A few name conflicts in the merger with ex-DNN have been solved Imakefile is replaced by Makefile Created InetAddress.* and UnixAddress.* from Address.* Created IrdaAddress.* OLD/TextStream has been updated --- comm/Scheduler.cc | 719 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 719 insertions(+) create mode 100644 comm/Scheduler.cc (limited to 'comm/Scheduler.cc') 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 // for NSIG +#include +#include +#include + +#include +#include +#include +#include + +#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 -- cgit v1.1