ClearSCADA

What It Does

The NOCACHE keyword instructs a structured text program (or function blocks too, but the rest of the article focuses on structured text) to not load the contents of the variable at request time but instead wait until execution time before reading this value. To understand this better, the simple operation of a structured text program is detailed below.

  1. Execution is requested (manually, on schedule or on input processed). The request is stored in a stack along with the input variables, except for those marked as NOCACHE.
  2. Read variables in marked as NOCACHE and execute of code.
  3. Variables written back to database.

In the above order we see that in step 1 the variables are temporarily stored in a separate stage to the actual execution. Whilst the gap is usually measured in milliseconds it can be much larger and so the variables that were already read in may have changed. The NOCACHE keyword simply forces those declared variables to be read in at step 2 rather than at step 1.

Why Does It Matter?

In normal execution of structured text it doesn't, however there are two main cases where it matters quite importantly and both those cases are when multiple instances of the same structured text program are called at once.

On Input Processed

If you have a structured text program that is configured to run on input processed looking at a point that receives point update in batches (PSTN is the popular example, but certain direct protocols that also support logged data will be affected) then it's possible that the logic may use an external counter to total up the new values (such as a day total). When the multiple point updates come in then multiple instances of the program, one for each update, are requested. If they all reference the same source counter they will all use the same starting value and create an incorrect result. E.g.:

 PROGRAM TotaliserWithError

 VAR
 	NewValue AT %I(.PSTN Point.CurrentValue) : LREAL;
 	Count AT %M(.Count.CurrentValue) : LREAL;
 END_VAR

 	Count := Count + NewValue;

 END_PROGRAM

With a few point updates to the PSTN point coming into the system at once, for example four updates of values 1, 2, 3 and 4 and Count having previously been 0 the final result of the above logic program running four times will not be 10 as expected. This is because the four times it ran the first time it ran Count's result was 1 (which is correct), the next time Count would be 2 (that is 0 + 2), the next time 3 (0 <it is cached from before the first execution> + 3) and after the final time Count will be 4 (0 + 4). If historic data is enabled on the Count point, all the entries will be stored, however the CurrentValue will likely be 4.

Depending on how the queued updates happen, it is possible for executions 1 and 2 to complete before executions 3 and 4 get put on the stack, so the output to Count from execution 2 could be used at the Count input to executions 3 and 4, resulting in the final value of 6 (2 + 4).

The solution is to put the Count in a NOCACHE range, i.e.:

 PROGRAM Totaliser

 VAR
 	NewValue AT %I(.PSTN Point.CurrentValue) : LREAL;
 END_VAR

 VAR *NOCACHE*
 	Count AT %M(.Count.CurrentValue) : LREAL;
 END_VAR

 	Count := Count + NewValue;

 END_PROGRAM

So now, after execution 1 ends and updates the value of Count, when execution 2 starts (the logic thread is single threaded so only one program can execute at once) it will read in Count, with it's new value, and correctly add up. This will then be repeated for executions 3 and 4 resulting in the correct result of 10.

Logic Called From Other Logic

Similar to the batched point updates above, a structured text program can be called many times from within another structured text program (note that calling the Execute method from VB or other sources may not be "immediate enough" to trigger this condition).

E.g. A calling program of:

 PROGRAM CallingProgram

 METHOD
 	Execute AT %M(.Totaliser.Execute) : SINT;
 END_METHOD

 	FOR i:=1 TO 4 DO
 		Execute(i);
 	END_FOR;

 END_PROGRAM

and Totaliser's code being similar to the above PSTN example:

 PROGRAM Totaliser

 VAR_INPUT
 	NewValue : SINT;
 END_VAR

 VAR *NOCACHE*
 	Count AT %M(.Count.CurrentValue) : SINT;
 END_VAR

 	Count := Count + NewValue;

 END_PROGRAM

Set both programs to execute on interval, but with the interval of 0. Setting Count to be 0 in the database will also help to show the effect.

When the calling program runs, it calls the totaliser four times in quick succession. Because the NOCACHE is used the resultant value in Count will be 10, however removing the NOCACHE keyword from the totaliser, setting Count manually back to 0 and re-running the calling program will now set Count instead to be 4. This can easily be used to show the difference.

NOCACHE and Queries

The NOCACHE keyword can apply to database queries within Structured Text programs. NOCACHE will change the way queries work only for logic which is executed on point value change or triggered from another program. This is because this type of execution is started under a write lock, so the query to read data is under a write lock. This is less efficient because only read locks can be parallelised. There is no benefit or penalty for NOCACHE for time-executed logic, or update/insert queries. Note also that NOCACHE will cause the query to be executed when the logic actually runs, which may be later in time to when it is queued, so there are situations where NOCACHE is inappropriate. In summary, choose NOCACHE for a SQL SELECT query when the logic program is run on the change to input data and the results of the query do not vary between queue time and execution time.

  1. Jun 02, 2013

    Adam Woodland says:

    Could this be reviewed and updated as required and made public?

    Could this be reviewed and updated as required and made public?