Over the last couple of weeks, I had the same question posed to me a couple of times, so I thought I would share this in a blog post. There are several times in programming that you are calling a set of function modules in succession that need the same lock object. This may be different ones in succession, such as doing an operation on a sales order, followed by a delivery or the same object several time, such as updating characteristics as discussed in this example BAPI_OBJCL_CHANGE Lock errors . In these circumstances, often by the time the second function is called, the lock object is not yet released by the first one, so you get an error.
There are several ways to address this issue that I will touch on, and why I like this solution the best.
1) Introduce a wait statement
This is by introducing a wait after the first function module to allow time for the locks from the first call to release using the statement –
WAIT UP TO N SECONDS. “Where N is the number of seconds to wait
This is not a very stable way to ensure that locks are released by the first function. There are many variables that can affect how long the locks take to be released. Not in the least, there will a difference between your development box and production system. Server and Database loads affect the time and so does the number of iterations the same objects are processed.
There is also additional time penalty here because you are always starting off with the forced wait time.
2) Use function module ENQUEUE_READ
In this case, you would use the function ENQUEUE_READ to see which locks are still active. While this is a much better way, because you are waiting for the specific action of a lock not being present. If the locks are still active, then implement a wait time and retry. While this is popular method and works well, I am not a fan for the following reasons.
- You are still implementing the wait and retry loop yourself
- You have to filter through the return to determine if the exact object you are looking for is still locked or not.
- For the above, you have to know the format of the lock object key
- The wait times that you implement are exactly on the second when using WAIT UP TO x SECONDS. This in rare circumstances when two users are trying to get to the same table, but also have other locks open, can cause deadlocks (See https://en.wikipedia.org/wiki/Deadlock and https://db.apache.org/derby/docs/10.0/manuals/develop/develop75.html for an explanation of deadlocks).
3) Use the DEQUEUE_XXXX function module
Simple… don’t do it. The locks are on for a reason. These function modules are for when you are in full control of the data and logic, not when you are calling an SAP function and not in full control of everything that is happening in the system.
4) Use the ENQUEUE_XXXX function module with modes U or V
When you use the appropriate function module and use the mode U, V (or W), it will check for lock collisions. This is detailed in SAP help here Example: Using Lock Modes U, V, and W – SAP Lock Concept – SAP Library. What is not mentioned here is the _WAIT parameter. Setting this parameter on (abap_true or ‘X’) will make the function call wait for a predetermined time for the lock to release. The beauty is this not only works with the standard modes where you are setting a lock, but it also works with the collision checks.
The code is really simple… in this example, I am checking and waiting for a lock on the sales order to release.
CALL FUNCTION ‘ENQUEUE_EVVBAKE’
mode_vbak = ‘V’ ” Lock mode for table VBAK
vbeln = l_sales_order ” 02th enqueue argument
_wait = abap_true
foreign_lock = 1
system_failure = 2
others = 3.
The benefits of this approach are…
- No need to manually implement a wait logic.
- You are already checking against the exact object of concern (here the sales order in the variable l_sales_order that I used in other parts of my program), so no need to filter through lock entries.
- You are passing individual key fields and don’t have to construct a properly formatted key in your own program.
- The final benefit of preventing deadlocks, I will explain in a little more detail below.
The maximum wait time is set to a default of 5 seconds with a retry of every one second by default. These settings are all controlled by system parameters that you can change if you need different behavior on your system. You can use transaction RZ11 to view and change these parameters and their documentation. I will give a quick overview of some of them here, but for more information, they are all pretty well documented right in RZ11.
enque/delay_max – This controls the maximum time to wait. Default is 5 seconds.
enque/delay_max_refine – How many times per second to retry. Default is 1, so with the default delay_max, it will retry 5 times, once a second for 5 seconds.
enque/delay_jitter – This is the magic that helps prevent deadlocks and two users repeating a request at the same time over and over. Default is 400ms. What this will do is vary the retry time by up to plus or minus 400ms on each try. So, even though the delay_max_refine says retry once a second, this setting will vary by a random duration up to ±400 ms each time. There is a more detailed example in the RZ11 documentation of this offset.
enque/deque_wait_answer – Default is off which makes the checks asynchronous. By turning this setting on, the check changes to a synchronous check for the dequeue to complete.