A server with periodic pulses

The first thing we should look at is a server that wants to get periodic messages. The most typical uses for this are:

Of course there are other, specialized uses for these things, such as network keep alive messages that need to be sent periodically, retry requests, and so on.

Server-maintained timeouts

In this scenario, a server is providing some kind of service to a client, and the client has the ability to specify a timeout. There are lots of places where this is used. For example, you may want to tell a server, get me 15 seconds' worth of data, or let me know when 10 seconds are up, or wait for data to show up, but if it doesn't show up within 2 minutes, time out.

These are all examples of server-maintained timeouts. The client sends a message to the server, and blocks. The server receives periodic messages from a timer (perhaps once per second, perhaps more or less often), and counts how many of those messages it's received. When the number of timeout messages exceeds the timeout specified by the client, the server replies to the client with some kind of timeout indication or perhaps with the data accumulated so far — it really depends on how the client/server relationship is structured.

Here's a complete example of a server that accepts one of two messages from clients and a timeout message from a pulse. The first client message type says, let me know if there's any data available, but don't block me for more than 5 seconds. The second client message type says, here's some data. The server should allow multiple clients to be blocked on it, waiting for data, and must therefore associate a timeout with the clients. This is where the pulse message comes in; it says, one second has elapsed.

To keep the code sample from being one overwhelming mass, there is some text before each of the major sections. You can find the complete version of time1.c in Sample programs.

Declarations

The first section of code here sets up the various manifest constants that we'll be using, the data structures, and includes all the header files required.

/*
 *  time1.c
 *
 *  Example of a server that receives periodic messages from
 *  a timer, and regular messages from a client.
 *
 *  Illustrates using the timer functions with a pulse.
*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/siginfo.h>
#include <sys/neutrino.h>

// message send definitions

// messages
#define MT_WAIT_DATA        2       // message from client
#define MT_SEND_DATA        3       // message from client

// pulses
#define CODE_TIMER          1       // pulse from timer

// message reply definitions
#define MT_OK               0       // message to client
#define MT_TIMEDOUT         1       // message to client

// message structure
typedef struct
{
    // contains both message to and from client
    int messageType;
    // optional data, depending upon message
    int messageData;
} ClientMessageT;

typedef union
{
    // a message can be either from a client, or a pulse
    ClientMessageT  msg;
    struct _pulse   pulse;
} MessageT;

// client table
#define MAX_CLIENT 16       // max # of simultaneous clients

struct
{
    int in_use;             // is this client entry in use?
    int rcvid;              // receive ID of client
    int timeout;            // timeout left for client
}   clients [MAX_CLIENT];   // client table

int     chid;               // channel ID (global)
int     debug = 1;          // set debug value, 1=on, 0=off
char    *progname = "time1.c";

// forward prototypes
static  void setupPulseAndTimer (void);
static  void gotAPulse (void);
static  void gotAMessage (int rcvid, ClientMessageT *msg);

main()

This next section of code is the mainline. It's responsible for:

  • Creating the channel (via ChannelCreate() ),
  • Calling the setupPulseAndTimer() routine (to set up a once-per-second timer, with a pulse as the event delivery method), and then
  • Sitting in a do-forever loop waiting for pulses or messages and processing them.

Notice the check against the return value from MsgReceive() ; a zero indicates it's a pulse (and we don't do any strong checking to ensure that it's our pulse), a non-zero indicates it's a message. The processing of the pulse or message is done by gotAPulse() and gotAMessage() .

int
main (void)                 // ignore command-line arguments
{
    int rcvid;              // process ID of the sender
    MessageT msg;           // the message itself

    if ((chid = ChannelCreate (0)) == -1) {
        fprintf (stderr, "%s:  couldn't create channel!\n",
                 progname);
        perror (NULL);
        exit (EXIT_FAILURE);
    }

    // set up the pulse and timer
    setupPulseAndTimer ();

    // receive messages
    for (;;) {
        rcvid = MsgReceive (chid, &msg, sizeof (msg), NULL);

        // determine who the message came from
        if (rcvid == 0) {
            // production code should check "code" field...
            gotAPulse ();
        } else {
            gotAMessage (rcvid, &msg.msg);
        }
    }

    // you won't get here
    return (EXIT_SUCCESS);
}

setupPulseAndTimer()

In setupPulseAndTimer() you see the code where we define the type of timer and notification scheme. When we talked about the timer function calls in the text above, we said that the timer can deliver a signal, a pulse, or cause a thread to be created. That decision is made here (in setupPulseAndTimer()).

Notice that we used the macro SIGEV_PULSE_INIT(). By using this macro, we're effectively assigning the value SIGEV_PULSE to the sigev_notify member. (Had we used one of the SIGEV_SIGNAL*_INIT() macros instead, it would have delivered the specified signal.) Notice that, for the pulse, we set the connection back to ourselves via the ConnectAttach() call, and give it a code that uniquely identifies it (we chose the manifest constant CODE_TIMER, something that we defined). The final parameter in the initialization of the event structure is the priority of the pulse; we chose SIGEV_PULSE_PRIO_INHERIT (the constant -1). This tells the kernel not to change the priority of the receiving thread when the pulse arrives.

Near the bottom of this function, we call timer_create() to create a timer object within the kernel, and then we fill it in with data saying that it should go off in one second (the it_value member) and that it should reload with one-second repeats (the it_interval member). Note that the timer is activated only when we call timer_settime() , not when we create it.

Note: The SIGEV_PULSE notification scheme is a BlackBerry 10 OS extension; POSIX has no concept of pulses.
/*
 *  setupPulseAndTimer
 *
 *  This routine is responsible for setting up a pulse so it
 *  sends a message with code MT_TIMER.  It then sets up a
 *  periodic timer that fires once per second.
*/

void
setupPulseAndTimer (void)
{
    timer_t             timerid;    // timer ID for timer
    struct sigevent     event;      // event to deliver
    struct itimerspec   timer;      // the timer data structure
    int                 coid;       // connection back to ourselves

    // create a connection back to ourselves
    coid = ConnectAttach (0, 0, chid, 0, 0);
    if (coid == -1) {
        fprintf (stderr, "%s:  couldn't ConnectAttach to self!\n",
                 progname);
        perror (NULL);
        exit (EXIT_FAILURE);
    }

    // set up the kind of event that we want to deliver -- a pulse
    SIGEV_PULSE_INIT (&event, coid,
                      SIGEV_PULSE_PRIO_INHERIT, CODE_TIMER, 0);

    // create the timer, binding it to the event
    if (timer_create (CLOCK_REALTIME, &event, &timerid) == -1) {
        fprintf (stderr, "%s:  couldn't create a timer, errno %d\n", 
                 progname, errno);
        perror (NULL);
        exit (EXIT_FAILURE);
    }

    // setup the timer (1s delay, 1s reload)
    timer.it_value.tv_sec = 1;
    timer.it_value.tv_nsec = 0;
    timer.it_interval.tv_sec = 1;
    timer.it_interval.tv_nsec = 0;

    // and start it!
    timer_settime (timerid, 0, &timer, NULL);
}

gotAPulse()

In gotAPulse(), you can see how we implemented the server's ability to time out a client. We walk down the list of clients, and since we know that the pulse is being triggered once per second, we decrement the number of seconds that the client has left before a timeout. If this value reaches zero, we reply back to that client with a message saying, Sorry, timed out (the MT_TIMEDOUT message type). You can see that we prepare this message ahead of time (outside the for loop), and then send it as needed. This is just a style/usage issue—if you expect to be doing a lot of replies, then it might make sense to incur the setup overhead once. If you don't expect to do a lot of replies, then it might make more sense to set it up as needed.

If the timeout value hasn't yet reached zero, we don't do anything about it—the client is still blocked, waiting for a message to show up.

/*
 *  gotAPulse
 *
 *  This routine is responsible for handling the fact that a
 *  timeout has occurred.  It runs through the list of clients
 *  to see which client has timed out, and replies to it with
 *  a timed-out response.
 */

void
gotAPulse (void)
{
    ClientMessageT  msg;
    int             i;

    if (debug) {
        time_t  now;

        time (&now);
        printf ("Got a Pulse at %s", ctime (&now));
    }

    // prepare a response message
    msg.messageType = MT_TIMEDOUT;

    // walk down list of clients
    for (i = 0; i < MAX_CLIENT; i++) {

        // is this entry in use?
        if (clients [i].in_use) {

            // is it about to time out?
            if (--clients [i].timeout == 0) {

                // send a reply
                MsgReply (clients [i].rcvid, EOK, &msg,
                          sizeof (msg));

                // entry no longer used
                clients [i].in_use = 0;
            }
        }
    }
}

gotAMessage()

In gotAMessage(), you see the other half of the functionality, where we add a client to the list of clients waiting for data (if it's a MT_WAIT_DATA message), or we match up a client with the message that just arrived (if it's a MT_SEND_DATA message). Note that for simplicity we didn't add a queue of clients that are waiting to send data, but for which no receiver is yet available.

/*
 *  gotAMessage
 *
 *  This routine is called whenever a message arrives.  We
 *  look at the type of message (either a "wait for data"
 *  message, or a "here's some data" message), and act
 *  accordingly.  For simplicity, we'll assume that there is
 *  never any data waiting.  See the text for more discussion
 *  about this.
*/

void
gotAMessage (int rcvid, ClientMessageT *msg)
{
    int i;

    // determine the kind of message that it is
    switch (msg -> messageType) {

    // client wants to wait for data
    case    MT_WAIT_DATA:

        // see if we can find a blank spot in the client table
        for (i = 0; i < MAX_CLIENT; i++) {

            if (!clients [i].in_use) {

                // found one -- mark as in use, save rcvid, set timeout
                clients [i].in_use = 1;
                clients [i].rcvid = rcvid;
                clients [i].timeout = 5;
                return;
            }
        }

        fprintf (stderr, "Table full, message from rcvid %d ignored, "
                         "client blocked\n", rcvid);
        break;

    // client with data
    case    MT_SEND_DATA:

        // see if we can find another client to reply to with
        // this client's data
        for (i = 0; i < MAX_CLIENT; i++) {

            if (clients [i].in_use) {

                // found one -- reuse the incoming message
                // as an outgoing message
                msg -> messageType = MT_OK;

                // reply to BOTH CLIENTS!
                MsgReply (clients [i].rcvid, EOK, msg,
                          sizeof (*msg));
                MsgReply (rcvid, EOK, msg, sizeof (*msg));

                clients [i].in_use = 0;
                return;
            }
        }

        fprintf (stderr, "Table empty, message from rcvid %d ignored, "
                         "client blocked\n", rcvid);
        break;
    }
}

Notes

Some general notes about the code:

  • If there's no one waiting and a data message arrives, or there's no room in the list for a new waiter client, we print a message to standard error, but never reply to the client. This means that some clients can be sitting there, REPLY-blocked forever—we've lost their receive ID, so we have no way to reply to them later.

    This is intentional in the design. You can modify this to add MT_NO_WAITERS and MT_NO_SPACE messages, respectively, which can be returned whenever these errors were detected.

  • When a waiter client is waiting, and a data-supplying client sends to it, we reply to both clients. This is crucial, because we want both clients to unblock.
  • We reused the data-supplying client's buffer for both replies. This again is a style issue — in a larger application you probably have to have multiple types of return values, in which case you may not want to reuse the same buffer.
  • The implementation shown here uses a cheesy fixed-length array with an in use flag (clients[i].in_use). Since the goal here isn't to demonstrate owner-list tricks and techniques for singly linked list management, we've shown the version that's the easiest to understand. Of course, in your production code, you probably use a linked list of dynamically managed storage blocks.
  • When the message arrives in the MsgReceive(), our decision as to whether it was in fact our pulse is done on weak checking—we assume (as per the comments) that all pulses are the CODE_TIMER pulse. Again, in your production code you want to check the pulse's code value and report on any anomalies.

Note that the example above shows just one way of implementing timeouts for clients. In Kernel timeouts, we'll talk about kernel timeouts, which are another way of implementing almost the exact same thing, except that it's driven by the client, rather than a timer.

Periodic server maintenance cycles

Here we have a slightly different use for the periodic timeout messages. The messages are purely for the internal use of the server and generally have nothing to do with the client at all. For example, some hardware might require that the server poll it periodically, as might be the case with a network connection — the server should see if the connection is still up, regardless of any instructions from clients.

Another case could occur if the hardware has some kind of inactivity shutdown timer. For example, since keeping a piece of hardware powered up for long periods of time may waste power, if no one has used that hardware for, say, 10 seconds, the hardware could be powered down. Again, this has nothing to do with the client (except that a client request cancels this inactivity powerdown) — it's just something that the server has to be able to provide for its hardware.

Code-wise, this is very similar to the example above, except that instead of having a list of clients that are waiting, you have only one timeout variable. Whenever a timer event arrives, this variable is decremented; if zero, it causes the hardware to shut down (or whatever other activity you want to perform at that point). If it's still greater than zero, nothing happens.

The only twist in the design is that whenever a message comes in from a client that uses the hardware, you have to reset that timeout variable back to its full value — having someone use that resource resets the countdown. Conversely, the hardware may take a certain warm-up time to recover from being powered down. In this case, once the hardware has been powered down, you have to set a different timer once a request arrives from a client. The purpose of this timer is to delay the client's request from going to the hardware until the hardware has been powered up again.