How to ensure updates to Global Variables are not lost when multiple tasks are trying to access their contents?
search cancel

How to ensure updates to Global Variables are not lost when multiple tasks are trying to access their contents?

book

Article ID: 27993

calendar_today

Updated On:

Products

OPS/MVS Event Management & Automation

Issue/Introduction

When updating a Global Variable via a OPS/REXX program it is necessary to guarantee it has exclusive access to the variable, otherwise the results are unpredictable.

How to make sure in OPS/MVS that updates to Global Variables are not lost when multiple tasks may be trying to access their contents?

 

Environment

 OPS/MVS-Event Management & Automation-for JES2
 

Resolution

Suppose that we know that a message may be issued concurrently by multiple tasks running under a single address space, as well as by multiple address spaces. Also, suppose that we want to count the number of times that message is issued by any single address space.  An AOF address space-related variable such as GLVJOBID.#message is ideal for this purpose. The GLVJOBID variable stem is unique to each job that initiates an event in the system, and it exists only for the life of that job. We also want to count the global number of times the message issued on this system no matter the address space.

In this scenario, the code in the )PROC section of the )MSG rule that is used to safely increment the GLVJOBID.#message counter for each job will be virtually identical to the code that increments the GLOBAL.#message counter for the system. However, the code to initialize the job-specific GLVJOBID.#message counter will be different. The code to initialize the system-wide GLOBAL.#message counter is located in the )INIT section of the rule. The )INIT section of a rule gets called by AOF during the process of enabling the rule. The process of enabling the rule is a serialized operation within the OPS/MVS address space, from within which the )INIT section gets called. The rule is enabled only once, and the )INIT section is called only once for that one enablement. There is no possible way that OPS/MVS will allow a rule to be enabled more than once, even if it receives multiple concurrent requests (which will probably come from OPSVIEW users).

However, because an address space-related variable exists only for the life of a job, it cannot be referenced in the )INIT section of a rule (nor in the )TERM section called when the rule is disabled). Therefore, an address space-related variable must be initialized within the )PROC section of a rule, by the first (or initial) event to occur for each job. Because we have already determined that our message can be issued concurrently by multiple tasks that are attached to the same job, we have to allow for the possibility of multiple concurrent "initial" messages issued by a single job. We must serialize the instantiation of the address space-related GLVJOBID.#message counter. For this reason, the following code copied from the )INIT section and added to the )PROC section will not be sufficient:

)PROC

  IF ^OPSVALUE('GLVJOBID.#message','E') THEN /* If no job msg count  */

    GLVJOBID.#message = 0                   /*Then init job msg count*/

 

  temp= OPSVALUE('GLVJOBID.#message','A',1) /*Increment job msg count*/

This code does not ensure that multiple tasks will not concurrently execute and pass the above IF statement, resulting in the variable being initialized multiple times in an unserialized manner. If this happened, the value of the variable could be corrupted. Even if only one task passed the IF statement, you could not be sure that initialization in that task would be executed before the increment in another task. This situation would result in an execution error attempting the increment, because the Add function of OPSVALUE expects the supplied compound variable to already exist.

A solution to this problem is to find a way to request OPS/MVS to initialize the variable if it does not already exist, and have it do both the test for existence and the initialization in one serialized process. Again, the OPSVALUE function provides this solution. The following single "Compare-and-update" OPSVALUE function creates the variable GLVJOBID.#message and initializes it to 0 if it does not exist:

temp = OPSVALUE('GLVJOBID.#message','C',0,'GLVJOBID.#message')

The above example is somewhat different from the normal use of the Compare-and-update OPSVALUE function. Usually, the result of the Compare-and-update operation (1 for success or 0 for failure) will be tested. In this case, we do not care about the success of the operation; we are using it only to serialize the initialization process. The operation succeeds only the first time that it is executed by any particular job. In that case, since the GLVJOBID.#message variable that is associated with that particular job does not yet exist, OPSVALUE creates the variable and gives it a value of its own name ('GLVJOBID.#message'), causing the Compare to succeed and the value to be updated to 0. The Compare fails for all subsequent executions by the same job, since the variable already exists, and its value is not equal to its own name. In those cases, the variable may have been initialized days ago by a task that is no longer attached, or it may have been initialized a microsecond ago by a task that is executing this same rule concurrent to this task. It does not matter. The counter is initialized only once per job, and always before it is incremented by the Add OPSVALUE function.

To sum up, we can use the following example rule:

)MSG message

 )INIT

  IF ^OPSVALUE('GLOBAL.#message','E') THEN /* If no system msg count */

    GLOBAL.#message = 0                    /* Then init sys msg count*/

 )PROC

  temp = OPSVALUE('GLOBAL.#message','A',1) /* Increment sys msg count*/

/*-------------------------------------------------------------------*/

/* Initialize job msg count once-and-only-once if it does not exist  */

  temp = OPSVALUE('GLVJOBID.#message','C',0,'GLVJOBID.#message')

/*-------------------------------------------------------------------*/

 

  temp= OPSVALUE('GLVJOBID.#message','A',1) /*Increment job msg count*/

One question still remains, however. How do you know when serialization is necessary? How do you know if an event can happen concurrently? As mentioned earlier, this question can be difficult to answer. Yet, if you examine the sample code above that is used to solve the problem of concurrent update, you will see that it is not complex at all. In each situation, you can simply replace a single REXX arithmetic operation with a single OPS/REXX OPSVALUE() function. So, why not code for the possibility that serialization problems may be an issue? It is not expensive. Some of you may recognize this concept of reentrant coding. You can never go wrong by coding reentrantly, because it allows for the possibility that the code may be executed asynchronously by multiple tasks updating common data.