Logo Search packages:      
Sourcecode: pcp version File versions  Download package

loop.c

/*
 * Copyright (c) * 2004-2006 Silicon Graphics, Inc.  All Rights Reserved.
 * 
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 2.1 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 Lesser General Public
 * License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA.
 * 
 * Contact information: Silicon Graphics, Inc., 1500 Crittenden Lane,
 * Mountain View, CA 94043, USA, or: http://www.sgi.com
*
* $Id: loop.c,v 1.2 2006/06/20 09:43:08 makc Exp $
*/

#include "platform_defs.h"

#include <assert.h>
#include <sys/poll.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifndef HAVE_WAIT_DEFINES_RUSAGE
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <sys/resource.h>
#endif
#include <sys/syslog.h>

#include "pmapi.h"
#include "impl.h"


typedef enum {
    FALSE = 0,
    TRUE = 1
} bool_t;

/* I would use SIGRTMAX...except it's not constant on Linux */
#define SIGMAX    64

typedef struct loop_input_s loop_input_t;
typedef struct loop_signal_s loop_signal_t;
typedef struct loop_timeout_s loop_timeout_t;
typedef struct loop_child_s loop_child_t;
typedef struct loop_idle_s loop_idle_t;
typedef struct loop_main_s loop_main_t;

struct loop_input_s
{
loop_input_t *next;
int tag;
int fd;
int flags;
int (*callback)(int fd, int flags, void *closure);
void *closure;
int priority;
};

struct loop_signal_s
{
loop_signal_t *next;
int tag;
int (*callback)(int sig, void *closure);
void *closure;
};

struct loop_timeout_s
{
loop_timeout_t *next;
int tag;
int delay;
int tout_msec;
int (*callback)(void *closure);
void *closure;
};

struct loop_child_s
{
loop_child_t *next;
int tag;
pid_t pid;
int (*callback)(pid_t pid, int status, const struct rusage *, void *closure);
void *closure;
};

struct loop_idle_s
{
loop_idle_t *next;
int tag;
int (*callback)(void *closure);
void *closure;
};

/*
* per-loop state, kept in an implicit stack
* by the main loop and subsidiary loops.
*/
struct loop_main_s
{
loop_main_t *next;
bool_t running;
loop_timeout_t *current_timeout;
loop_child_t *current_child;
};

#define pmLoopDebug ((pmDebug & DBG_TRACE_LOOP) != 0)

static int num_inputs = 0;
static loop_input_t *input_list = NULL;
static struct pollfd *pfd;
static loop_input_t **inputs;
static int next_tag = 1;
static bool_t inputs_dirty = TRUE;
static loop_signal_t *signals[SIGMAX];
static volatile bool_t signals_pending[SIGMAX];
static struct timeval poll_start;
static loop_timeout_t *timeout_list;
static loop_main_t *main_stack;
static loop_child_t *child_list;
static bool_t child_pending = FALSE;
static int sigchld_tag;
static loop_idle_t *idle_list;

static int
tv_sub(const struct timeval *a, const struct timeval *b)
{
struct timeval t;

t.tv_sec = a->tv_sec - b->tv_sec;
if (a->tv_usec >= b->tv_usec) {
    t.tv_usec = a->tv_usec - b->tv_usec;
} else {
    t.tv_sec--;
    t.tv_usec = 1000000 + a->tv_usec - b->tv_usec;
}

return (t.tv_sec*1000 + t.tv_usec / 1000);
}

static void
tv_now(struct timeval *tv)
{
gettimeofday(tv, NULL);
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

int
pmLoopRegisterInput(
    int fd,
    int flags,
    int (*callback)(int fd, int flags, void *closure),
    void *closure,
    int priority)
{
    loop_input_t *ii;

    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG,
                  "loop_register_input: fd=%d flags=0x%x "
                  "callback=%p closure=%p priority=%d",
                  fd, flags, callback, closure, priority);

    if ((ii = (loop_input_t *)malloc(sizeof(loop_input_t))) == NULL) {
      return (-ENOMEM);
    }

    ii->tag = next_tag++;
    ii->fd = fd;
    ii->flags = flags;
    ii->callback = callback;
    ii->closure = closure;

    ii->next = input_list;
    input_list = ii;
    num_inputs++;

    inputs_dirty = TRUE;

    return ii->tag;
}


void
pmLoopUnregisterInput(int tag)
{
    loop_input_t *ii, *previi;

    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG, "loop_unregister_input: tag=%d", tag);

    for (ii = input_list, previi = NULL ;
       ii != NULL && ii->tag != tag ;
       previi = ii, ii = ii->next)
      ;
    if (ii == NULL)
      return;

    if (previi == NULL)
      input_list = ii->next;
    else
      previi->next = ii->next;
    num_inputs--;
    free(ii);

    inputs_dirty = TRUE;
}

static int
loop_compare_by_priority(const void *av, const void *bv)
{
    const loop_input_t *a = *(const loop_input_t **)av;
    const loop_input_t *b = *(const loop_input_t **)bv;

    return a->priority - b->priority;
}

static int
loop_setup_inputs(void)
{
    loop_input_t *ii;
    int i;

    if (num_inputs <= 0) {
      return (0);
    }

    if (inputs_dirty) {
      pfd = (struct pollfd *)realloc(pfd,
                               num_inputs * sizeof(struct pollfd));
      inputs = (loop_input_t **)realloc(inputs,
                                num_inputs * sizeof(loop_input_t *));
      if ((pfd == NULL) || (inputs == NULL)) {
            return (-ENOMEM);
      }
      inputs_dirty = FALSE;
    }

    for (ii = input_list, i = 0; ii != NULL ; ii = ii->next, i++)
      inputs[i] = ii;
    qsort(inputs, num_inputs, sizeof(loop_input_t *), loop_compare_by_priority);

    for (i = 0 ; i < num_inputs ; i++)
    {
      ii = inputs[i];
      if (pmLoopDebug)
          __pmNotifyErr(LOG_DEBUG, 
                    "loop_setup_inputs: inputs[%d] = (fd=%d "
                    "callback=%p closure=%p)",
                    i, ii->fd, ii->callback, ii->closure);

      pfd[i].fd = ii->fd;
      pfd[i].events = ii->flags;
      pfd[i].revents = 0;
    }
    return (num_inputs);
}

static void
loop_dispatch_inputs(void)
{
    int i;
    loop_input_t *ii;
    int n = num_inputs; /* because num_inputs can change inside the loop */

    for (i = 0 ; i < n; i++) {
      ii = inputs[i];

      if ((pfd[i].revents & POLLNVAL)) {
          /* invalid fd... */
          pmLoopUnregisterInput(ii->tag);
          continue;
      }

      if (pmLoopDebug)
          __pmNotifyErr(LOG_DEBUG,
                    "loop_dispatch_inputs: pfd[%i]=(fd=%d "
                    "events=0x%x revents=0x%x)",
                    i, pfd[i].fd, pfd[i].events, pfd[i].revents);

      if ((pfd[i].revents & (ii->flags | POLLHUP | POLLERR)))     {
          if ((*ii->callback)(ii->fd, pfd[i].revents, ii->closure)) {
            if (pmLoopDebug)
                __pmNotifyErr(LOG_DEBUG,
                          "loop_dispatch_inputs: deregistering "
                          "input with tag %d\n",
                          ii->tag);
            
            pmLoopUnregisterInput(ii->tag);
          }
      }
    }
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static void
loop_sig_handler(int sig)
{
    signals_pending[sig] = TRUE;
}

int
pmLoopRegisterSignal(
    int sig,
    int (*callback)(int sig, void *closure),
    void *closure)
{
    loop_signal_t *ss;
    bool_t doinstall;

    if (sig < 0 || sig >= SIGMAX)
      return -EINVAL;

    if ((ss = (loop_signal_t *)malloc(sizeof(loop_signal_t))) != NULL) {
      return (-ENOMEM);
    }

    ss->tag = next_tag++;
    ss->callback = callback;
    ss->closure = closure;

    doinstall = (signals[sig] == NULL);
    ss->next = signals[sig];
    signals[sig] = ss;

    if (doinstall)
    {
      struct sigaction sa;

      memset(&sa, 0, sizeof(sa));
      sa.sa_flags = 0;
      sa.sa_handler = loop_sig_handler;
      if (sigaction(sig, &sa, NULL) < 0) {
          int ee = errno;

          __pmNotifyErr(LOG_WARNING, 
                    "sigaction failed - %s", strerror(ee));
          return (-ee);
      }
    }

    return ss->tag;
}

void
pmLoopUnregisterSignal(int tag)
{
    int sig;
    loop_signal_t *ss, *prevss;

    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG,"loop_unregister_signal: tag=%d", tag);

    for (sig = 0 ; sig < SIGMAX ; sig++)
    {
      for (ss = signals[sig], prevss = NULL ;
           ss != NULL && ss->tag != tag ;
           prevss = ss, ss = ss->next)
          ;
      if (ss == NULL)
          continue;

      if (prevss == NULL)
          signals[sig] = ss->next;
      else
          prevss->next = ss->next;
      free(ss);

      if (signals[sig] == NULL)
      {
          struct sigaction sa;

          memset(&sa, 0, sizeof(sa));
          sa.sa_flags = 0;
          sa.sa_handler = SIG_DFL;
          if (sigaction(sig, &sa, NULL) < 0) {
            __pmNotifyErr(LOG_WARNING, 
                        "sigaction failed - %s", strerror(errno));
            return;
          }
      }
      break;
    }
}

static void
loop_dispatch_signals(void)
{
    int sig;
    loop_signal_t *ss, *nextss;

    for (sig = 0 ; sig < SIGMAX ; sig++) {
      if (signals_pending[sig]) {
          signals_pending[sig] = FALSE;

          for (ss = signals[sig]; ss != NULL;  ss = nextss) {
            nextss = ss->next;
            if ((*ss->callback)(sig, ss->closure)) {
                pmLoopUnregisterSignal(ss->tag);
            }
          }
      }
    }
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

/*
 * A few words about the timeout data structure.  This is the classic
 * (i.e. simple and not scalable) data structure for multiplexing
 * multiple virtual timeouts onto one real one, as described in any
 * OS internals book.
 *
 * A singly-linked list of loop_timeout_t structures is kept, the head
 * is pointed to by timeout_list and threaded by tt->next.  Each entry
 * stores in tt->delay a timeout in millisecons which expresses when
 * the entry is scheduled to fire as the elapsed time after the
 * previous entry is scheduled to fire.  Thus the delay field in the
 * head of the list is logically the amount of time from now until the
 * first timeout is scheduled, and so is used directly as the timeout
 * for poll().
 *
 * From this data structure we can derive the insert algorithm.
 * The algorithm walks down the list from the head, keeping a running
 * relative delay from the last entry iterated over by subtracting
 * from it the tt->delay of each entry skipped.  When iterating to the
 * next entry would cause this running relative delay to go negative,
 * we know we've arrived at the right place to insert the new entry.
 * Note that the check is for negative, not negative or zero: this
 * ensures that multiple entries for the same scheduled time are
 * stored in the same order that they were inserted, which is the
 * most intuitive behaviour for the application programmer.
 *
 * The remove algorithm is simpler, it just scans the list trying
 * to match the unique tag.
 *
 * There are some more hairy parts as well.  It is possible for poll()
 * to return before the timeout expires, for example if input becomes
 * available on a file descriptor.  The poll() call does not give any
 * indication of how much time remained until the timeout would have
 * fired (on some operating systems, the select(2) does this).  So a
 * sample of the system time is taken before and after every call
 * to poll(), the elapsed time in the poll() is calculated, and the
 * tt->delay in the head of the timeout_list is adjusted to account
 * for the elapsed time.  This is necessary to avoid restarting the
 * poll() with too long a timeout.  An example of the resulting bug
 * would be a timeout registered for 10 seconds from now, but every
 * 1 second input becomes available on some file descriptor; if the
 * poll() timeout were not adjusted the timeout callback would never
 * be called and would always be 10 seconds in the future.
 */

static void
loop_dump_timeouts(void)
{
    loop_timeout_t *tt;

    __pmNotifyErr(LOG_DEBUG,"timeout_list {");
    for (tt = timeout_list ; tt != NULL ; tt = tt->next) {
      __pmNotifyErr(LOG_DEBUG,"    %dms %p %p",
                  tt->delay, tt->callback, tt->closure);
    }
    __pmNotifyErr(LOG_DEBUG,"}");
}

static void
loop_insert_timeout(loop_timeout_t *tt)
{
    loop_timeout_t *next, *prev;
    int delay = tt->delay;

    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG, "loop_insert_timeout: %d %p %p",
                  tt->delay, tt->callback, tt->closure);

    for (next = timeout_list, prev = NULL ;
       (next != NULL) && ((delay - next->delay) >= 0);
       prev = next, next = next->next)
      delay -= next->delay;

    if (prev == NULL)
      timeout_list = tt;
    else
      prev->next = tt;
    tt->next = next;

    if (next != NULL)
      next->delay -= delay;

    tt->delay = delay;

    if (pmLoopDebug)
      loop_dump_timeouts();
}

int
pmLoopRegisterTimeout(
    int tout_msec,
    int (*callback)(void *closure),
    void *closure)
{
    loop_timeout_t *tt;

    if (tout_msec < 0) {
      return (-EINVAL);
    }

    if ((tt = (loop_timeout_t *)malloc(sizeof(loop_timeout_t))) == NULL) {
      return (-ENOMEM);
    }

    tt->tag = next_tag++;
    tt->delay = tt->tout_msec = tout_msec;
    tt->callback = callback;
    tt->closure = closure;

    loop_insert_timeout(tt);

    return tt->tag;
}

void
pmLoopUnregisterTimeout(int tag)
{
    loop_main_t *lm;
    loop_timeout_t *tt, *prevtt;

    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG,"loop_unregister_timeout: tag=%d", tag);

    /*
     * Because the timeout object is detached from the
     * global timeout list while its being dispatched
     * (and yes there are good reasons for this), we
     * have to search for the timeout tag in all the
     * currently stacked loops.
     */
    for (lm = main_stack ; lm != NULL ; lm = lm->next)
    {
      if (lm->current_timeout != NULL &&
          lm->current_timeout->tag == tag)
      {
          free(lm->current_timeout);
          lm->current_timeout = NULL;
          return;
      }
    }

    for (tt = timeout_list, prevtt = NULL;
       tt != NULL && tt->tag != tag ;
       prevtt = tt, tt = tt->next)
      ;

    if (tt == NULL)
      return;

    if (prevtt == NULL)
      timeout_list = tt->next;
    else
      prevtt->next = tt->next;

    if (tt->next != NULL)
      tt->next->delay += tt->delay;

    free(tt);
}

/* returns milliseconds */
static int
loop_setup_timeouts(void)
{
    tv_now(&poll_start);

    if (idle_list != NULL)
      return 0;   /* poll() returns immediately */
    if (timeout_list == NULL)
      return -1;  /* poll() waits forever */
    return (timeout_list->delay);
}

static void
loop_dispatch_timeouts(void)
{
    if (timeout_list == NULL)
      return;

    timeout_list->delay = 0;
    while (timeout_list != NULL && (timeout_list->delay == 0)) {
      loop_main_t *lm = main_stack;
      int isdone;
      assert(lm != NULL);
      assert(lm->current_timeout == NULL);
      lm->current_timeout = timeout_list;
      timeout_list = timeout_list->next;

      isdone = (*lm->current_timeout->callback)(lm->current_timeout->closure);

      assert(lm == main_stack);

      if (!isdone && (lm->current_timeout != NULL)) {
          lm->current_timeout->delay = lm->current_timeout->tout_msec;
          loop_insert_timeout(lm->current_timeout);
      } else {
          pmLoopUnregisterTimeout(lm->current_timeout->tag);
      }
      lm->current_timeout = NULL;
    }
}

static void
loop_adjust_timeout(void)
{
    struct timeval now;
    int spent;

    if (timeout_list == NULL)
      return;

    tv_now(&now);
    spent = tv_sub(&now, &poll_start);
    if (spent >= timeout_list->delay) {
      timeout_list->delay = 0;
      loop_dispatch_timeouts();
    } else {
      timeout_list->delay -= spent;
    }
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static int
loop_sigchld_handler(int sig, void *closure)
{
    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG,"loop_sigchld_handler");
    child_pending = TRUE;
    return (0);
}


int
pmLoopRegisterChild(
    pid_t pid,
    int (*callback)(pid_t pid, int status, const struct rusage *, void *closure),
    void *closure)
{
    loop_child_t *cc;
    bool_t doinstall;

    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG,"loop_register_child: pid=%d callback=%p closure=%p",
            (int)pid, callback, closure);

    if (pid <= 0)
      return -1;
    if ((cc = (loop_child_t *)malloc(sizeof(loop_child_t))) == NULL) {
      return (-ENOMEM);
    }

    cc->tag = next_tag++;
    cc->pid = pid;
    cc->callback = callback;
    cc->closure = closure;

    doinstall = (child_list == NULL);
    cc->next = child_list;
    child_list = cc;

    if (doinstall)
      sigchld_tag = pmLoopRegisterSignal(SIGCHLD,
                                 loop_sigchld_handler,
                                 NULL);

    return cc->tag;
}

void
pmLoopUnregisterChild(int tag)
{
    loop_main_t *lm;
    loop_child_t *cc, *prevcc;

    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG,"loop_unregister_child: tag=%d", tag);

    for (cc = child_list, prevcc = NULL ;
       cc != NULL && cc->tag != tag ;
       prevcc = cc, cc = cc->next)
      ;
    if (cc == NULL)
      return;

    if (prevcc == NULL)
      child_list = cc->next;
    else
      prevcc->next = cc->next;

    for (lm = main_stack ; lm != NULL ; lm = lm->next)
    {
      if (cc == lm->current_child)
          lm->current_child = NULL;
    }
    free(cc);

    if (child_list == NULL)
    {
      pmLoopUnregisterSignal(sigchld_tag);
      sigchld_tag = -1;
    }
}

static void
loop_dispatch_children(void)
{
    loop_child_t *cc, *nextcc;
    int status;
    int r;
    struct rusage rusage;

    memset (&rusage, 0, sizeof(rusage));

    child_pending = FALSE;

    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG,"loop_dispatch_children");

    /* We don't support callback on process groups.  Sorry */
    while ((r = wait3(&status, WNOHANG, &rusage)) > 0)
    {
      if (pmLoopDebug)
          __pmNotifyErr(LOG_DEBUG,"loop_dispatch_children: r=%d", r);

      for (cc = child_list ; cc != NULL ; cc = nextcc)
      {
          nextcc = cc->next;

          if (r == cc->pid) {
            loop_main_t *lm = main_stack;
            int isdone;

            assert(lm != NULL);
            lm->current_child = cc;
            isdone = (*cc->callback)((pid_t)r, status, &rusage,
                               cc->closure);

            if (isdone ||
                (lm->current_child != NULL &&
                 (WIFEXITED(status) || WIFSIGNALED(status)))) {
                /*
                 * This pid won't be coming back or we were told
                 * that callback has fulfilled its purpose, so
                 * unregister.
                 */
                pmLoopUnregisterChild(cc->tag);
            }
            assert(lm == main_stack);
            assert(lm->current_child == NULL || lm->current_child == cc);
            lm->current_child = NULL;
            break;
          }
      }
    }
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

int
pmLoopRegisterIdle(
    int (*callback)(void *closure),
    void *closure)
{
    loop_idle_t *ii;

    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG,"loop_register_idle: callback=%p closure=%p",
            callback, closure);

    if ((ii = (loop_idle_t *)malloc(sizeof(loop_idle_t))) == NULL) {
      return (-ENOMEM);
    }

    ii->tag = next_tag++;
    ii->callback = callback;
    ii->closure = closure;

    ii->next = idle_list;
    idle_list = ii;

    return ii->tag;
}

void
pmLoopUnregisterIdle(int tag)
{
    loop_idle_t *ii, *previi;

    if (pmLoopDebug)
      __pmNotifyErr(LOG_DEBUG, "loop_unregister_idle: tag=%d", tag);

    for (ii = idle_list, previi = NULL ;
       ii != NULL && ii->tag != tag ;
       previi = ii, ii = ii->next)
      ;
    if (ii == NULL)
      return;

    if (previi == NULL)
      idle_list = ii->next;
    else
      previi->next = ii->next;

    free(ii);
}

static void
loop_dispatch_idle(void)
{
    loop_idle_t *ii, *nextii;

    for (ii = idle_list ; ii != NULL ; ii = nextii) {
      nextii = ii->next;

      if ((*ii->callback)(ii->closure)) {
          pmLoopUnregisterIdle(ii->tag);
      }
    }
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

void
pmLoopStop(void)
{
    if (main_stack != NULL)
      main_stack->running = FALSE;
}

int
pmLoopMain(void)
{
    int r;
    int timeout;
    loop_main_t lmain;

    memset(&lmain, 0, sizeof(lmain));
    lmain.next = main_stack;
    main_stack = &lmain;

    lmain.running = TRUE;
    while (lmain.running)
    {
      int ee;

      if ((ee = loop_setup_inputs()) < 0) { 
            return (ee);
      }
      timeout = loop_setup_timeouts();
      loop_dispatch_idle();

      if ((ee == 0) && (timeout == -1) && (idle_list == NULL)) {
          return (0);
      }

      r = poll(pfd, num_inputs, timeout);

      if (r < 0)
      {
          if (errno == EINTR)
          {
            loop_dispatch_signals();
            if (child_pending)
                loop_dispatch_children();
            continue;
          }
          __pmNotifyErr(LOG_ERR,
                    "pmLoopMain: poll failed - %s", strerror(errno));
          break;
      }
      else if (r == 0)
      {
          if (timeout > 0)
            loop_dispatch_timeouts();
          else
            loop_adjust_timeout();
      }
      else
      {
          loop_dispatch_inputs();
          loop_adjust_timeout();
      }
    }

    assert(main_stack == &lmain);
    assert(lmain.current_child == NULL);
    assert(lmain.current_timeout == NULL);
    main_stack = lmain.next;
    return (0);
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
/*END*/

Generated by  Doxygen 1.6.0   Back to index