LCLS Controls

SLC-Aware IOC Design


 

Async Utilities Design

 

Quick links:

1.   Scope

This document describes the software design for the SLC-aware IOC Async Utilities. These utilities are for use by cstrAsync and other Async threads.

2.   Introduction

2.1 Background

This same functionality is implemented in utilities whose source lives in REF_RMX_ASYNC for the SLC Micros.

The following table shows the names of Functions on the SLC RMX micros and the Functions on the SLC IOC which accomplish roughly the same functionality.

SLC Micro Function(s)

SLC IOC Function

ASYNCDBINIT
INIT_CYC_GLBCOM
PROC_MICR_STS first_time stuff
TEST JOB CHECK FUNCTION first_time stuff
CYCLE_INIT

slcAsyncInit

INIT_MICR_STS

cstrInitMicrSts

GET_CYCLE_TIME

slcAsyncSleep

METER_DBSEND

slcAsyncMeterDbsend

PROC_MICR_STS

cstrProcMicrSts

LOCK_STATS

slcAsyncLockStats

UNLOCK_STATS

slcAsyncUnlockStats

SET_FCN_STATS

slcAsyncSetFcnStats

2.2 References

1.      SLC Asynch Database Update Design Spec by T Lahey, N Spencer 1989

2.      Improving Control of Auto-Checking Functions by T Lahey,N Spencer, R Hall 1990

2.3 Requirements

See the SLC-Aware IOC Functional Requirements by Stephanie Alison.

3.   Async Utilities Design

General sketch for All Async Threads

This is the general pattern followed by All Async Threads including (but not limited to) cstrAsync.
Prior to any ASYNC threads running, slcAsyncInit() is called by dbHdlr at SLC IOC initialization time to set up the Async Global area once for all ASYNC threads. These are the calls that Async jobs in the SLC IOC should make.

A local variable holds epicsTime which is set by slcAsyncSleep and passed to slcAsyncSetFcnStats.

LOOP:

·        Call slcAsyncSleep() to get an indication of which check function to perform.

·        Call that check function.

·        Call slcAsyncMeterDbSend()

·        Call slcAsyncSetFcnStats()


Access to the global area by async threads shall always be through an Async utility.

Semiphores and Flags Used by Async Tasks

Steph says: One issue is that I think Ron needs an event semaphore and an asyncExists flag similar to what we have for the database. My thought is that these should be global and that the slcExec should allocate and initialize them just like it does for the database ones. **TBD**

slcAsyncInit will Set an event that indicates that the jobFunc global table creation is complete to release tasks waiting on this event. All other async utilities will wait on this event before proceeding.

Message Logging

Messages to be logged are stated below under the individual utility descriptions.

Function Return Status

All utilities return unix-style status values (not vms style status).
Generally, a value of -1 indicates a failure. 0 indicates success.

Diagnostics

Write async function tables for an input job name (can be ALL*) to the console. **TBD**

Async Utility Function descriptions

The Async utilities all exist in a single file called ioc/util/slcAsync.c. Prototypes are in slcAsync.h

Utilities are thread-safe. This is accomplished by using a single mutex to protect access to the global data.

It is necessary to restart the SLC IOC in order to change any of the items (listed below) which are read by slcAsyncInit() at SLC IOC initialization time. These items include Cycle Function (CNAM) and Cycle Length (CYCL).

  1. slcAsyncInit()
    Called prior to any ASYNC treads running by dbHdlr at SLC IOC initialization time to set up the Async Global area for all ASYNC threads. The global area is actually a job/function table shared between tasks and is called slcJobFunc_ts. slcAsyncInit is not called from anywhere else.

    slcAsyncInit corresponds to several functions on the slc micros as shown in the table above.

slcJobFunc_ts is an array of structures in file scope, with one structure (array element) per cycling function across the entire IOC. Each Job may have zero or more cycling functions. The array is calloc'ed by this function to have one entry per cycling function across the current slc ioc.

It is important to note that in the CSTR database, all functions for a given Job are not gauranteed to be grouped together (example: CSTR:micr:1,CNAM may indicate a mixed grouping like this: KLYS-TRMP CRAT-WATC KLYS-FCHK...)
slcAsyncInit is responsible for grouping all cycling functions together in the array.

Two new entries will be added to slcJob_ts.

·        The first is "first cycling function index". This is an integer index into the array slcJobFunc_as. This integer is passed to async functions as an input argument.

·        The second is "number of cycling functions". This is also an integer.

 

The following table describes the association between slcJob_as and slcJobFunc_as.



 

 

Async Utilities below look at all cycling functions for a given job.
That is done by looking in slcJobs_as[jobIndex] for the current job, and picking up the first cycle function index and number_of_functions. Those two values are used to inspect the slcJobFunc_as and associated arrays of secondary values.

The following secondaries values are read (one time only) from CSTR into slcJobFunc_as for all cycling functions.
Call dblistalloc(), dblist() and then dblget(CSTR,[this_micro],1,SECN) which, in one call (per secondary) returns lists (arrays) of each value of size MAX_NUM_FUNCS.:

§         CNAM - contains Job Name and Cycle Function.

§         CYCL - is the cycle length in seconds.

§         MTRL, MTRC, and MAXT - database write metering parameters.

Call dbListFree() to free the lists obtained above for CNAM, CYCL, and MTRx.

Call slcAsyncInitVerify() to check the values in the lists we just got and update the array for all job/functions. It also allocates and sets some entries in slcJobFunc_as.

 

The following structure is slcJobFunc_ts. 

To the right is the initial value for each element as set by slcAsyncInit.

 

                                                                                         INITIAL VALUE

                                                                                         --------------

/* Async cycling function information */

typedef struct {       

  char         jobName_a[5];         /* null terminated 4-character job name         */ READ FROM CSTR:CNAM

  char         funcName_a[5];        /* null terminated 4-character job name         */ READ FROM CSTR:CNAM

  int4u        cyclTimeSec;          /* cycle time from CSTR:CYCL                    */ READ FROM CSTR:CYCL

  int4u        scanTimeSec;          /* cycle time from CSTR:SCAN                    */ 0

  int4u        mtrlSec;              /* dpupdate meter length in seconds             */ READ FROM CSTR:MTRL

  int4u        mtrcSec;              /* dbupdate meter max # dbupdates in mrtl       */ READ FROM CSTR:MTRC

  int4u        mtrtSec;              /* dbupdate meter max time between dbupdates    */ READ FROM CSTR:MTRT

  epicsTimeStamp updateTimeStamp;    /* timestamp of last dbupdate                   */ current time

  epicsTimeStamp cycleTimeStamp;     /* last time this cycl function was executed    */ current time

  int4u        betweenCycle;         /* time between last 2 cycles                   */ 0

  int4u        numFuncExe;           /* total # function executions in calc interval */ 0

  int4u        numVmsMsg;            /* # of function executions from vms msg interv */ 0

  int4u        numDbUpdates;         /* # of dbupdates (for metering and %) interval */ 0

  int4u        numDbUpdateFail;      /* # of dbupdates that failed  in calc interval */ 0

  dbheader_ts  scanDblist_ps;        /* pointer to scan dblist                       */ Points to list.

  void         *next_ps;             /* next in linked list */

} slcJobFunc_ts;    

 

#endif /* slcAsync_H */

 

***TBD** DOES THE FOLLOWING GO IN CSTRASYNC?  IF HERE, WHERE TO STORE POINTERS???


           Call dblistalloc(),dblist() is called to set up lists for all cstrAsync Check Function Secondaries as follows.

Pointers to these lists are set in the Global Area *TBD*.:

§         CTIM, UTIM, and ELPS (one per cycling function) for use by CHK1 check function.

§         NRUN, FAIL, and PUPD (one per cycling function) for use by CHK2 check function

Call dblistalloc(), dblist() are called to set up lists for later access to HSTA, CMSK, MMSK, and FMSK (one value per ioc).  Pointers to lists are in file scope for use by other utilities.  Dblget() is called to get initial values which will be overwritten later.

 

Call dblistalloc(), dblist() are called to set a list for SCAN (one per function).  A pointer to the list is stored in slcJobFunc_ts.  Dblget() is called to get initial values which will be overwritten later.

 

The mutex is set up (by calling *TBD*) for use in accessing the Global Area. **MOVE THIS???***

Set the event (by calling TBD) that the creation is complete to release tasks waiting on this event. All other async utilities will wait on this event before proceeding.

 

  1. slcAsyncInitVerify(job_id,func_id)

    Check the values in the lists obtained by the caller. Update the slcJobFunc array for all job/functions.
    This is a static function (called only by another function within this .c file).

Loop through the arrays in the dblists and do the following steps

·        As explained above, the array is calloc'ed by this function to have one entry per cycling function across the current slc ioc.. Cycling functions for a given Job are grouped together in slcJobFunc_as even if they are not grouped in the database.
Check that the job name in CNAM is valid and matches a job name in the slcJob_ts array. Report a fatal error if there's no match and cause the image to exit.

·        Check CYCL, MTRC, MTRL, MAXT (dblist/dblget was already done by the caller ),
against reasonable limits. Any value which is outside reasonable limits is reset to an appropriate default (see the supertype I table in the requirements doc for defaults).
Log a message if the values are set to defaults stating DB value and default it was set to.

 

  1. int slcGetFuncIndexByName(const char * const name_pa)
    This public utility is used by Handlers and finds the slcJobFunc_as[] index that matches the first 4 chars of name_pa. Returns index into slcJobFunc_as if found, returns -1 (INVALID_FUNC) if no matching name is found.

 

4.     slcAsyncSleep (slcJob_te jobIndex, epicsTimeStamp *currentTime_ps)

§         jobIndex is an input parameter

§         *currentTime_ps is an output parameter (but the storage was allocated by the caller)
slcAsyncSleep gets current time. This time needs to be passed onto slcAsyncSetFcnStats by the caller so that the ELPS time can be calculated (current time - time from slcAsyncSleep (which is the time right before the function was executed)).

§         *slcJobFunc_ps is an output parameter telling the caller which cycling function to execute.**TBD**


This function returns -1 (error) or 0 for success.

Wait up to forever for a cycling function of the input job to be ready. Return when either the cycling function is ready or SLC exec has asked for the task to exit (async task stop flag), polled every second. Return with the function that is most past-due. To determine if a cycling function is ready, the following conditions must be true. Note that current values of supertype 2 CSTR secondaries (MMSK, CMSK, and HSTA) must be retrieved from the database first:

§         The current time exceeds the time of the previous action (ie. exceeds the last time this function was executed: stored in slcJobFunc_ts) by the cycling period (CTIM). The cycling period is either CYCL or SCAN, if the bit for the function is set in MMSK and SCAN is less than CYCL. Get SCAN from the database only if the MMSK bit is set (only if needed). See Steph's PDL below for more details.

§         The function must be enabled as determined by checking the appropriate bit in CMSK.

§         The job must be enabled as determined by checking the appropriate bit in HSTA.

 

LOOP:

·        Check if the "stop" flag for the async thread associated with the current job is set. This is accomplished by looking at the stop flag in the slcThread_as associated with the current entry in slcJob_as as indicated by the input jobIndex. If the "stop" flag is set, immediately exit with the proper status. The task then exits with the proper cleanup.

·        Using the latest values from the database, check CSTR: CMSK and HSTA for this function. Action TBD** if they have changed (probably just log a message).
Note: Some common cycling functions (like Magnets) can have CSTR:CMSK changed from the SCP Cluster Status Panel. However, there are other cycling functions that are not supported for toggling CSTR::CMSK on the Cluster Status Panel.
For the purposes of this code, we'll just always support it being changed for all cycling functions.

·        See if there an async function for this job that is ready to be executed. Return an indication as to which async function.

·        If there is nothing to do, then sleep for 1 second by calling epicsThreadSleep.

·        In the above two steps, it is necessary for the function which is called to look at all cycling functions for the current thread. That is done by looking in slcJobs_as[jobIndex] for the current thread, and picking up the first cycle function index and number_of_functions. Those two values are used to inspect the slcJobFunc_as and associated arrays of secondary values.

·        Go back to (1) and repeat.


Note: Steph suggested we might return the current timestamp in a 2nd argument (TBD).

Here is a PDL description from Stephanie (modified by ronm) for this function:

 
 
int slcAsyncSleep(slcJob_te jobIndex, epicsTimeStamp * currentTime_ps)
{
 if (jobIndex is bad) {
        slcCmlogLogMsg(...);
        return -1;
 }
 
        /* Cycle (polling) until stopped or we find a function to do */
 
 do {
        if (slcThread_as[slcJobs_as[jobIndex].async_e)].stop) {
                return -1;
        }
        status = epicsTimeGetCurrent(currentTime_ps);
        if (status < 0) {
                slcCmlogLogMsg(...);
                return -1;
        }
          
                  /* Get MMSK, CMSK, HSTA from database */   
        if (slcAsyncUpdST2() is bad) {
                return -1;
        }
 
                  /* For each function in this job */
        funcIndex = -1;
        largestTimeDiff = -1.0;
        for (fIdx = slcJobs_as[jobIndex].funcIdx; 
             fIdx << fIdx)) 
                  {
                      /* Use SCAN as cycle Period if MMSK is set and SCAN 
                            is less than cycl */
                      /* First, test if MMSK is set */
            if ((slcAsyncMMSK from dblget & (1 << fIdx)) &&
                       (slcAsyncSCAN[fIdx] from dblget < 
                                        slcJobFuncs_as[fIdx].cycl  ))
                    {
              cyclePeriod = slcAsyncSCAN[fIdx] from dblget;
                    }
                     
            else /* else use CYCL */
                    {                   
              cyclePeriod = slcJobFuncs_as[fIdx].cycl;
                    }
            timeDiff = epicsTimeDiffInSeconds(currentTime_ps, 
                                             &slcJobFuncs_as[fIdx].ctim_s;
            if ((timeDiff >= cyclePeriod) && (timeDiff > 
                                              largestTimeDiff)) 
                    {
                largestTimeDiff = timeDiff;
                funcIndex = fIdx;              /* found one to run!*/
                        &slcJobFuncs_as[fIdx].ctim_s = currentTime_ps
                                         /* *RONM MODIFICATION* Above Stored last cycle time */ 
            }
                  }                            /*endif CMSK */
          slcAsyncUnlockStats();
        }                             /*endfor*/
        if (funcIndex < 0) epicsThreadSleep(1.0);
 while (funcIndex < 0);
 
 return funcIndex;
 

 

  1. slcAsyncMeterDbsend(job_id, func_id, slcRequestType, force_update)
    Call dbupdate() to send lists that were previously filled with dblput().
    slcRequestType is an input argument enum indicating slcAsync or slcMsg.

Do an update if any of the following are true:

§         If slcRequestType indicates the call is as a result of a message from VMS (slcMsg), then, the dbupdate() is always done (forced) even if the metering counters indicate it's not time to do so.

§         Do an update if the last Alpha update request for this job/function failed.

§         Do an update if no update has happened in the last MAXT seconds.

§         Do an update if the number of successful database updates is less than the allowed number (MTRC) in the last MTRL seconds.

§         Do an update if the bit for the function is set in FMSK. The value of FMSK must be retrieved from the database first using dblget().


Note: It is not necessary to process OMSK as is done on the slc micros. That functionality was never implemented.

Update the following items in the async function table for the input job and function if an Alpha update was done:

§         Metering-started timestamp set to current time if metering has just started.

§         Increment the metering counter if metering is in effect.

§         Increment either the database-update-failed counter or database-update-success counter for this job/function depending on status from the update request.

§         Set the last-database-update timestamp to current time.

§         Set the last-database-update status.

 

  1. slcAsyncChk1Update(args *TBD*).
    This is only called by slcAsyncChk1(). Update CSTR UTIM, CTIM, and ELPS in the local database (call dblput). Copy all values from the async function tables to the database. The timestamps are first converted to VMS format. Values for UTIM and CTIM will always be different so there is no need to check for changes. Instead, always cause dbupdate to be performed (return parameter?)

 

  1. slcAsyncChk2Update(args *TBD*).
    This is only called by slcAsyncChk2(). Update CSTR PUPD, PVAX, NRUN, and FAIL in the local database(call dblput). Calculate percent of NRUN resulting in a successful DB update (PUPD) and percent of NRUN triggered by Alpha message requests (PVAX). Update the database on change only (could set return parameter to cause dbupdate to be perfommed). Zero applicable counters after update.

 

  1. slcAsyncLockStats(args *TBD*).
    This static function (only called from functions within this .c file) calls *TBD* to take out mutex on Global Area.

 

  1. slcAsyncUnlockStats(args *TBD*).
    This static function (only called from functions within this .c file) calls *TBD* to release mutex on Global Area.

 

10. slcAsyncSetFcnStats(slcJob_te *Function_enum, epicsTimeStamp *start_time, requestType).

§         Function_enum is an input parameter
It indicates the entry in slcJobFunc_as that is being operated on.

§         start_time is an input parameter
Passed by the caller so that the ELPS time can be calculated (current time - start_time (which is the time right before the function was executed)).

§         requestType is an input parameter
It indicates if this is as a result of a vms message request or an async request.
It's an enum with two possible values: slcAsync or slcMsg.

This function sets statistics in the Global Area for the specified cycling function.
Input start_time is time right before function began and is set by caller (used in calculating func execution elapsed time (ELPS)).
Call slcAsyncLockStats() to take out mutex on Global Area
Call some epics call to get current time.
Store current time into in Global Area for this function (set last execution time to current time).
Increment NRUNS (execution counter) in Global Area for this function.
If slcRequestType indicates the call is from a Handler as a result of VMS message (slcMsg), then, increment number of hndlr functions in last calculation interval (and calculate PVAX).
If slcRequestType indicates the call is from a Handler as a result of Async call (slcAsync), then, increment number of async functions in last calculation interval.
Calculate elapsed time (ELPS) and store in Global Area
Call slcAsyncUnlockStats() to release the mutex on the Global Area.

 

  1. slcAsyncExit(void)
    All resources allocated by Async Utilites are deallocated. This includes memory (slcJobFunc_as and db lists), mutexes, and events as needed. Set slcAsyncInit flag to failed. It zeros the number of async functions and the first function index in the slcJob_as job array.

    Called by dbHdlr when it exits. dbHdlr calls it BEFORE it frees the database.

 


SLC-Aware IOC Home Page | LCLS Controls | EPICS at SLAC | SLAC Computing | SLAC Networking | SLAC Home

Contact: Ron MacKenzie,
Last Modified: March 1, 2005. by RONM.