Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
former_member192665
Participant
A couple of days ago I was browsing in the IdM Forum on SAP Developer Network and I read Sequence of event tasks. I posted a reply since I had been confronted to the problem earlier, then I thought this would be a good topic for a blog post. There are several use cases where you want to make sure that a certain task waits until another has done its work. One example is as follows: You have two connected target system. In one you can only create accounts if the user already has an account in the second one. The provisioning jobs may be kicked off more or less simultaneously, however, this doesn't give you any guarantee about the order in which they'll be picked up by the dispatchers. What one wants here is certainty.

Another example is what I present here in this blog post. I've implemented a simple request handler.

Two words about what I mean with request handler: It's a workflow that handles a request, that is a workflow that operates on an instance of entrytype MX_ASYNC_REQUEST. A request handler architecture can be useful if you need to do complex computations before submitting an operation. In that case you don't want to directly write data to your target objects, be it persons or roles. You first want to plausi check the values, for instance. If the data is not ok, you make the request fail and the objects remain untouched.

A simple example for such a request handler is part of the SAP delivery, if you download the Identity Services package you'll get a very simple request handler.

For this post, I wrote a very very simple request handler. Its sole purpose is to modify the attribute MX_PHONE_ADDITIONAL of a user. In order to do this modification we create an instance of MX_ASYNC_REQUEST. The value of the attribute MX_ASYNC_MSKEYVALUE is the MSKEYVALUE of the object that's about to be modified. The attribute MX_PHONE_ADDITIONAL holds the phone number to be added. It's a multi value attribute so each request will add a phone number.

So much about the general setup. Now imagine 2 or more different requests are being sent into this workflow doing modifications on the same object. Then those operations might interfere with each other, overwrite values in an unintended way and so on. Readers who are familiar with common server programming know what I'm talking about: There are lots of situations where one must prohibit concurrent access of more than one thread to the data. Same here.

In the Java programming language, one way of limiting concurrent access is the synchronized statement. The request handler I've presented here implements such a mechanism. It prevents the workflow from continuing when there is already another item already in process by the workflow. See here the structure of the workflow task:

When a request enters the request handler, its status is first set to "BEING PROCESSED". This is useful information for end users. If they request something manually via Web UI which results in creation of an instance of MX_ASYNC_REQUEST and that request is then sent into a request handler you can build a UI that simply displays the request object to the end user so that they can see in which status the request currently is. In this example we have only 3 statuses: BEING PROCESSED, WAITING and COMPLETED. In a more complex scenario that is closer to real life we'd probably also have WAITING FOR APPROVAL, FAILED, DECLINED and so forth. After setting the status comes the central task of this exercise: Get the lock! I'll have to explain that later. If the request got the lock, the attribute BLG_LOCK is written (I added it to entrytype MX_ASYNC_REQUEST for this post) so that the following conditional task can easily check whether to wait (in case we did not get the lock) or to continue (otherwise).

In case we got the lock we simply continue. In case we don't get the lock, we need to go into some kind of "waiting state" until the lock is released. In SAP NetWeaver Identity Management, this can be implemented by an approval task. This is exactly what we do here. If the conditional Did I get it? returns false we set the status to WAITING and go into an approval task which halts execution of this particular audit.

On the other hand the lock holder must release the lock when done. This is done in the task "Release one of the waiting ones". If there are waiting tasks one of them will be selected and be sent into the request handler again so that another item will get the lock.

So much about the big picture. Now it is time to go into the details.

How does the locking work?

In order to answer this question let's look into the task "Get the lock!". It only executes the below script:
// check whether there is already another request
// which modifies the same item.
// The below query checks whether there is another request in the queue whose
// MSKEYVALUE is the same as the one of the request provided by the
// variable mskey and that has BLG_LOCK set.
// If not, I'm the first one executing this task and I get the lock
var uSQL = "select distinct mskey from mxp_provision where parentid=" +
parenttaskid + " and mskey in (
select mskey from mxiv_sentries where attrname='MX_ASYNC_MSKEYVALUE' and searchvalue = (
select searchvalue from mxiv_sentries where attrname='MX_ASYNC_MSKEYVALUE' and mskey = "
+ mskey + "
)
)
and mskey in (
select mskey from mxiv_sentries where attrname='BLG_LOCK'
)";
Util_LogValue ("SQL is " + uSQL);

var result = uSelect (uSQL);
Util_LogValue ("result = " + result);

if (result.length==0) {
// No other element in the queue. I got the lock
uIS_SetValue (mskey, "%$glb.SAP_MASTER_IDS_ID%", "BLG_LOCK", 1);
}
mskey is the mskey of the request being processed and parenttaskid is the task id of the "Event Handler" task. The crucial point is the SQL statement executed. Here it is again in a more readable form:
select distinct mskey from mxp_provision where parentid=297 and mskey in (
select mskey from mxiv_sentries where attrname='MX_ASYNC_MSKEYVALUE' and searchvalue = (
select searchvalue from mxiv_sentries where attrname='MX_ASYNC_MSKEYVALUE'
and mskey = 1080
)
) and mskey in (
select mskey from mxiv_sentries where attrname='BLG_LOCK'
)
In my Identity Center the Event Handler has id 297 and the mskey of a request could be 1080. What does this statement do? Let's recap what we want to do. We want to avoid that two requests are processed which will modify the same object. The object being modifed is pointed to by the request's attribute MX_ASYNC_MSKEYVALUE. The first part of the query checks whether there are other requests currently worked on by the request handler which have the same MX_ASYNC_MSKEYVALUE as the given request. The second part checks whether those requests have the attribute BLG_LOCK set. Translated into human language this means "Is there someone editing the same object and has this someone the lock?" Why do we need the lookup in the provisioning queue (table mxp_provision)? Why can't simply the lock holder get the BLG_LOCK attribute? The reason is safety. During request processing something might fail and the request handler stops processing but does not remove the BLG_LOCK. Then no request would ever be executed until someone manually handles this. Our check not only takes into consideration that the BLG_LOCK is set but also whether the request is still in the request handler and if it is not then it cannot block other requests and someone else can get the lock.

Another good question is why there cannot be two requests which enter the above script at the same time. The answer is simple: Jobs are executed sequentially. Even if there are two objects in the queue and waiting for execution of "Get the lock!" they'll never be executed in parallel. This means that the first one will get the lock and the second one will have to wait.

Let's get back to the script. The first one entering the script will now get the lock, BLG_LOCK will be written. For every following request the SQL query will return at one entry at least as long the first one is still being processed. In this case the second and all other following requests will run into the approval task and wait. This brings us to the next question

How is the lock released?

In a similar fashion we can now identify which requests are waiting in the approval task. We simply do a similar check: Look into the provisioning queue and filter out those requests that are waiting for an approval (when running into an approval, the runtime writes an MX_APPROVALS attribute to an entry, see also the first blog post). 302 is the id of the approval task in our example. If there are such requests, we pick the first one and approve it with the built-in function uIS_SetApproval.
var mskey = Par.get ("MSKEY");
var parenttaskid = Par.get ("PARENTTASKID");

// get all the requests whose MX_ASYNC_MSKEYVALUE
// is the same as the one of the given request and
// who are waiting to get the lock. This means who
// are waiting for an approval
var result = uSelect ("select mskey from mxp_provision where parentid=" +
parenttaskid + " and mskey in ( 
select mskey from mxiv_sentries where attrname='MX_ASYNC_MSKEYVALUE' and searchvalue = (
select searchvalue from mxiv_sentries where attrname='MX_ASYNC_MSKEYVALUE' and mskey = "
+ mskey + "
) and mskey in (
select mskey from mxiv_sentries where attrname='MX_APPROVALS' and
searchvalue like 'STATUS=WAIT!!TASK=<302>%' 
)
)");

if (result.length==0)
// no one else is waiting, there is nothing to do
return;

// so at least one request is waiting.
// release the first one.
var arr = result.split ("!!");

var mskey_to_release = arr [0];
// get the MX_APPROVALS
// in order to release this one
// we need to know in which audit
// it hangs
var approval_attr = uIS_GetValue (mskey_to_release, "%$glb.SAP_MASTER_IDS_ID%",
"MX_APPROVALS");
var auditid = Util_GetApprovalsAttribute (approval_attr + "|AUDITID");

var out = uIS_SetApproval (mskey_to_release, 302, auditid, 0, 1);
Util_LogValue ("Approval returned " + out);

return;
In the approve branch of the approval task the request is simply sent into the request handler again and get a new chance to get the lock this time.

Some more comments about this:

  • Of course you would never build a request handler if you only want to modify a phone number. This is just an example.
  • I hardcoded task ids in this example, in real life you would use global constants and during installation of the solution those constants would have to be set.
  • When we unlock on of the waiting requests we pick the first one of the hit list. This is not precise. Theoretically speaking there could be requests which wait forever. To make it smart, we'd have to sort the hit list by modifytime so that the oldest entry comes first in the hit list. This way the oldest request would be unlocked first and we get some kind of a "fifo" handling.

I tested this. I wrote a job which kicks off 10 request more or less at the same time. All of them are going more or less simultaneously into the request handler. Please see an excerpt from the log: You can see at the bottom that 10 requests are created and then 2 log entries later 10 items to into the "Get the lock!" task. But only one execution of "Set attribute on user" and 9 go into the waiting state. And so on.

I've created a query for the DB how the attributes BLG_LOCK and BLG_REQ_STATUS evolve. See the result here:

This allows you pretty well to follow what has happened. Please note the different mskeys in the first column.

Thanks for reading this! Stay tuned until the next post.

4 Comments
Labels in this area