/*===========================================================================*
* *
* smttime.c - *
* *
* Copyright (c) 1991-2003 iMatix Corporation *
* *
* ------------------ GPL Licensed Source Code ------------------ *
* iMatix makes this software available under the GNU General *
* Public License (GPL) license for open source projects. For *
* details of the GPL license please see www.gnu.org or read the *
* file license.gpl provided in this package. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public *
* License along with this program in the file 'license.gpl'; if *
* not, write to the Free Software Foundation, Inc., 59 Temple *
* Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* You can also license this software under iMatix's General Terms *
* of Business (GTB) for commercial projects. If you have not *
* explicitly licensed this software under the iMatix GTB you may *
* only use it under the terms of the GNU General Public License. *
* *
* For more information, send an email to info@imatix.com. *
* -------------------------------------------------------------- *
*===========================================================================*/
/*
Synopsis: Generates one-off or repeated timing events.
*/
#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:
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.
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;
}