smttime.c


/*  ----------------------------------------------------------------<Prolog>-
    Name:       smttime.c
    Title:      SMT timer agent
    Package:    Libero/SMT Kernel 2.x

    Written:    1996/06/23  iMatix SMT kernel team smt@imatix.com
    Revised:    1999/01/23

    Synopsis:   Generates one-off or repeated timing events.

    Copyright:  Copyright (c) 1991-2000 iMatix Corporation
    License:    This is free software; you can redistribute it and/or modify
                it under the terms of the SMT License Agreement as provided
                in the file LICENSE.TXT.  This software is distributed in
                the hope that it will be useful, but without any warranty.
 ------------------------------------------------------------------</Prolog>-*/

#include "smtdefn.h"                    /*  SMT definitions                  */


/*- Definitions -------------------------------------------------------------*/

#define AGENT_NAME      SMT_TIMER       /*  Our public name                  */
#define SINGLE_THREADED TRUE            /*  Single-threaded agent            */

typedef struct _TIMEREQ {               /*  Request descriptor               */
    struct _TIMEREQ                     /*                                   */
            *next, *prev;               /*    Doubly-linked list             */
    QID     reply_to;                   /*    Who sent the request           */
    qbyte   days;                       /*    Delay in days                  */
    qbyte   csecs;                      /*    Delay in centiseconds          */
    word    cycles;                     /*    How many cycles left           */
    long    exp_date;                   /*    Date and time                  */
    long    exp_time;                   /*      that the request expires     */
    dbyte   body_size;                  /*    Body to return with            */
    byte   *body_data;                  /*      reply event                  */
} TIMEREQ;


/*- Function prototypes -----------------------------------------------------*/

static TIMEREQ *absolute_request_create  (QID *reply_to,
                                          long date, long time,
                                          dbyte body_size, byte *body_data);
static TIMEREQ *relative_request_create  (QID *reply_to,
                                          qbyte days, qbyte csecs, word cycles,
                                          dbyte body_size, byte *body_data);
static void     request_destroy          (TIMEREQ *request);
static void     send_alarm_reply         (TIMEREQ *request);

#if (defined (__WINDOWS__))
VOID CALLBACK handle_timer (HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime);
#endif

/*- Global variables used in this source file only --------------------------*/

static NODE
    requests;                           /*  Request list header              */


#include "smttime.d"                    /*  Include dialog data              */

/********************   INITIALISE AGENT - ENTRY POINT   *********************/


/*  ---------------------------------------------------------------------[<]-
    Function: smttime_init

    Synopsis: Initialises the SMT timer agent.  Returns 0 if initialised
    okay, -1 if there was an error.  The timer agent provides timing events
    after a certain delay, at a specific time, or at a specific frequency.
    When you initialise the timer agent it creates an unnamed thread
    automatically.  Send events to this thread.  The timer accuracy is
    1/100th of a second, depending on the system capacity and speed.
    Supports these public methods:
    <Table>
    ALARM       Send an alarm after some delay (use SMT_TIME_ALARM).
    WAKEUP      Send an alarm at some time (use SMT_TIME_ALARM).
    CLOCK       Send an alarm at some frequency (use SMT_TIME_CLOCK).
    FLUSH       Cancel all timing events for a client thread.
    </Table>
    Sends errors to the SMTOPER agent; see doc for reply events.
    ---------------------------------------------------------------------[>]-*/

int
smttime_init (void)
{
    AGENT   *agent;                     /*  Handle for our agent             */
#   include "smttime.i"                 /*  Include dialog interpreter       */

    /*                      Method name      Event value     Priority        */
    /*  Shutdown event comes from Kernel                                     */
    method_declare (agent, "SHUTDOWN",       shutdown_event, SMT_PRIORITY_MAX);

    /*  Timer event sent by kernel to the timer agent (this program)         */
    method_declare (agent, "TIMER",          timer_event,    0);

    /*  Private event, used to loop if no system timers are available        */
    method_declare (agent, "_TIMER",         timer_event,    SMT_PRIORITY_LOW);

    /*  Public methods supported by this agent                               */
    method_declare (agent, "ALARM",          alarm_event,    0);
    method_declare (agent, "WAKEUP",         wakeup_event,   0);
    method_declare (agent, "CLOCK",          clock_event,    0);
    method_declare (agent, "FLUSH",          flush_event,    0);

    /*  Create initial, unnamed thread                                       */
    thread_create (AGENT_NAME, "");

    /*  Signal okay to caller that we initialised okay                       */
    return (0);
}


/*************************   INITIALISE THE THREAD   *************************/

MODULE initialise_the_thread (THREAD *thread)
{
    node_reset (&requests);             /*  Initialise requests list         */
    smt_set_timer (&thread-> queue-> qid);
    the_next_event = ok_event;
}


/**********************   CREATE SINGLE ALARM REQUEST   **********************/

MODULE create_single_alarm_request (THREAD *thread)
{
    qbyte
        days,                           /*  Delay in days                    */
        csecs;                          /*  Delay in centiseconds            */
    dbyte
        body_size;                      /*  Arbitrary body size              */
    byte
        *body_data = NULL;              /*  Arbitrary body contents          */

    exdr_read (thread-> event-> body, SMT_TIME_ALARM,
               &days, &csecs, &body_size, &body_data);
    relative_request_create (&thread-> event-> sender, days, csecs, 1,
                             body_size, body_data);
}


/*  -------------------------------------------------------------------------
 *  relative_request_create
 *
 *  Creates a new request, and initialises it to empty.  If the request
 *  could not be created, sends a TIME_ERROR to the caller, and returns
 *  null.  Otherwise returns the address of the created request.  Sets
 *  the expiry time as specified by the delay.  Attaches the user-defined
 *  body to the request; this is returned with the alarm event.
 */

static TIMEREQ *
relative_request_create (
    QID   *reply_to,                    /*  Who sent this timing request     */
    qbyte  days,                        /*  Days to delay                    */
    qbyte  csecs,                       /*  Csecs to delay                   */
    word   cycles,                      /*  Number of alarm cycles           */
    dbyte  body_size,                   /*  Attachment size                  */
    byte  *body_data)                   /*  Attachment data                  */
{
    TIMEREQ
        *request;                       /*  Request we create                */

    if ((request = absolute_request_create (reply_to, date_now(), time_now(),
                                            body_size, body_data)) != NULL)
      {
        /*  A zero time is treated as the smallest possible unit             */
        if (days == 0 && csecs == 0)
            csecs = 1;

        /*  Fill-in request fields                                           */
        request-> days   = days;
        request-> csecs  = csecs;
        request-> cycles = cycles;

        /*  Set request expiry date and time                                 */
        future_date (&request-> exp_date, &request-> exp_time,
                      request-> days,      request-> csecs);
      }
    return (request);
}


/*  -------------------------------------------------------------------------
 *  absolute_request_create
 *
 *  Creates a new request, and initialises it to empty.  If the request
 *  could not be created, sends a TIME_ERROR to the caller, and returns
 *  null.  Otherwise returns the address of the created request.  Sets
 *  the expiry time as specified by the delay.  Attaches the user-defined
 *  body to the request; this is returned with the alarm event.
 */

static TIMEREQ *
absolute_request_create (QID *reply_to, long date, long time,
                         dbyte body_size, byte *body_data)
{
    TIMEREQ
        *request;                       /*  Request we create                */

    if ((request = node_create (requests.prev, sizeof (TIMEREQ))) == NULL)
        sendfmt (reply_to, "TIME_ERROR", "Out of memory");
    else
      {
        /*  Fill-in request fields                                           */
        request-> reply_to  = *reply_to;
        request-> body_size = body_size;
        request-> body_data = body_data;
        request-> cycles    = 1;
        request-> exp_date  = date;
        request-> exp_time  = time;
      }
    return (request);
}


/**********************   CREATE CYCLED CLOCK REQUEST   **********************/

MODULE create_cycled_clock_request (THREAD *thread)
{
    qbyte
        days,                           /*  Delay in days                    */
        csecs;                          /*  Delay in centiseconds            */
    word
        cycles;                         /*  Number of cycles; 0 = forever    */
    dbyte
        body_size;                      /*  Arbitrary body size              */
    byte
        *body_data = NULL;              /*  Arbitrary body contents          */

    exdr_read (thread-> event-> body, SMT_TIME_CLOCK,
               &days, &csecs, &cycles, &body_size, &body_data);
    relative_request_create (&thread-> event-> sender, days, csecs, cycles,
                             body_size, body_data);
}


/**********************   CREATE SINGLE WAKEUP REQUEST   *********************/

MODULE create_single_wakeup_request (THREAD *thread)
{
    long
        date,
        time;
    dbyte
        body_size;                      /*  Arbitrary body size              */
    byte
        *body_data = NULL;              /*  Arbitrary body contents          */

    exdr_read (thread-> event-> body, SMT_TIME_ALARM,
               &date, &time, &body_size, &body_data);
    absolute_request_create (&thread-> event-> sender, date, time,
                             body_size, body_data);
}


/************************   GENERATE RESPONSE EVENTS   ***********************/

MODULE generate_response_events (THREAD *thread)
{
    static long
        cur_date = 0,                   /*  Check if clock went backwards    */
        cur_time = 0;
    TIMEREQ
        *request;                       /*  Pointer to request in list       */
    long
        sig_date,                       /*  When do we signal                */
        sig_time,                       /*    the next alarm?                */
        delay_days,                     /*  Which is in how many days        */
        delay_csecs;                    /*    and how many centiseconds      */

    /*  Handle a backwards clock adjustment by estimating the difference     */
    /*  and applying this to all time requests.                              */
    smt_set_step ("handle clock adjustment");
    if (date_is_future (cur_date, cur_time))
      {
        date_diff (cur_date,    cur_time,
                   date_now (), time_now (),
                   &delay_days, &delay_csecs);
        for (request  = requests.next;
             request != (TIMEREQ *) &requests;
             request  = request-> next)
          {
            past_date (&request-> exp_date, &request-> exp_time,
                       delay_days, delay_csecs);
          }
      }
    cur_time = time_now ();
    cur_date = date_now ();
    sig_date = 0;                       /*  No alarm requests, yet           */
    sig_time = 0;

    /*  First pass - generate response events and expire old requests        */
    smt_set_step ("expire old requests");
    for (request  = requests.next;
         request != (TIMEREQ *) &requests;
         request  = request-> next)
      {
        if (request-> exp_date  < cur_date
        || (request-> exp_date == cur_date
        &&  request-> exp_time <= cur_time))
          {
            send_alarm_reply (request); /*  Send back an ALARM to caller     */
            if (request-> cycles == 1)  /*  Expire if single-shot            */
              {
                request = request-> prev;
                request_destroy (request-> next);
              }
            /*  Else set next expiry time to closest future point...  under
             *  normal circumstances we add the delay date and time.  If the
             *  delay is very short (too short), or the clock was moved
             *  forwards, this may not be enough.  We then take the current
             *  date and time and add the delay to that.
             */
            else
              {
                future_date (&request-> exp_date, &request-> exp_time,
                              request-> days, request-> csecs);

                if (request-> exp_date <  cur_date
                || (request-> exp_date == cur_date
                &&  request-> exp_time <  cur_time))
                  {
                    request-> exp_date = cur_date;
                    request-> exp_time = cur_time;
                    future_date (&request-> exp_date, &request-> exp_time,
                                  request-> days, request-> csecs);
                  }
                if (request-> cycles > 1)
                    request-> cycles--;
              }
          }
      }
    /*  Second pass - calculate time for next signal                         */
    smt_set_step ("calculate next signal");
    for (request  = requests.next;
         request != (TIMEREQ *) &requests;
         request  = request-> next)
      {
        /*  Keep track of the oldest expiry time, for next timer signal      */
        if (sig_date >  request-> exp_date
        || (sig_date == request-> exp_date
        &&  sig_time >  request-> exp_time)
        ||  sig_date == 0)
          {
            sig_date = request-> exp_date;
            sig_time = request-> exp_time;
          }
      }
    /*  If we had an alarm request, go set it off                            */
    smt_set_step ("set alarm request");
    if (sig_date)
      {
        date_diff (sig_date, sig_time,
                   cur_date, cur_time,
                   &delay_days, &delay_csecs);
        if (delay_days > 24)            /*  24 days are less than 2^31       */
            delay_days = 24;

        /*  Try to set a process alarm (SIGALRM signal) after the desired
         *  delay.  If this fails -- usually because we're on a crippleOS
         *  -- then we substitute a real timer by a slow SMT loop.  This is
         *  okay on crippleOS'es since these usually don't worry too much
         *  if one process hogs the CPU.
         */
        if (!process_alarm (delay_days * 86400000L + delay_csecs * 10))
            SEND (&thread-> queue-> qid, "_TIMER", "");
      }
}

static void
send_alarm_reply (TIMEREQ *request)
{
    byte
        *msg_body;                      /*  Message returned to caller       */
    int
        msg_size;                       /*  Size of formatted msg_body       */

    /*  Calculate size required for message body                             */
    msg_size = exdr_write (NULL, SMT_TIME_REPLY,
                           request-> body_size, request-> body_data);

    /*  Now allocate and format the buffer                                   */
    msg_body = mem_alloc  (msg_size);
    msg_size = exdr_write (msg_body, SMT_TIME_REPLY,
                           request-> body_size, request-> body_data);
    event_send (
        &request-> reply_to,            /*  Send to specified queue          */
        NULL,                           /*  No queue for reply               */
        "TIME_ALARM",                   /*  Name of event to send            */
        msg_body,                       /*  Event body contents              */
        msg_size,                       /*  Event body size                  */
        NULL, NULL, NULL,               /*  No response events               */
        0);                             /*  No timeout                       */

    mem_free (msg_body);                /*  And we can free the buffer       */
}


/*  -------------------------------------------------------------------------
 *  request_destroy
 *
 *  Destroys the specified request.
 */

static void
request_destroy (TIMEREQ *request)
{
    mem_free (request-> body_data);
    node_destroy (request);
}


/***********************   FLUSH REQUESTS FOR CLIENT   ***********************/

MODULE flush_requests_for_client (THREAD *thread)
{
    TIMEREQ
        *request;                       /*  Pointer to request in list       */

    /*  Destroy any requests originally created by the sending thread        */
    for (request  = requests.next;
         request != (TIMEREQ *) &requests;
         request  = request-> next)
      {
        if (memcmp (&request-> reply_to,
                    &thread-> event-> sender, sizeof (QID)) == 0)
          {
            request = request-> prev;
            request_destroy (request-> next);
          }
      }
}


/**************************   DESTROY ALL REQUESTS   *************************/

MODULE destroy_all_requests (THREAD *thread)
{
    while (requests.next != &requests)
        request_destroy (requests.next);
}


/*************************   TERMINATE THE THREAD   **************************/

MODULE terminate_the_thread (THREAD *thread)
{
    the_next_event = terminate_event;
}

Generated by Framer 1.0 © 1997 iMatix