Waiting for lock objects to release – using lock modes U and V
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’
EXPORTING
mode_vbak = ‘V’ ” Lock mode for table VBAK
vbeln = l_sales_order ” 02th enqueue argument
_wait = abap_true
EXCEPTIONS
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.
Notes:
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.
Hi,
what people always often do forget is that the wait command is invoking a database commit. If there is a rollback afterwards, maybe by a dump, not all changes are taken back leaving the database in an inconsistent state.
Good point... Do you have any idea what the exact impact would be if the WAIT is called after a COMMIT WORK or a COMMIT WORK AND WAIT statement? Would the first COMMIT complete before the second is attempted? What exactly happens to LUW in this case?
Any way you look at it, the WAIT seems like a bad idea!
As Rainer has mentioned the WAIT triggers a database commit. On the otherhand COMMIT WORK[AND WAIT] ends the current SAP-LUW.
I don't think the standalone WAIT has any impact on the LUW.
Suhas,
Sorry, I should have been more explicit in specifically asking "what happens to the database LUW" in this case. My concern is something that I have seen ABAPers do many, many times, and even I was guilty of earlier in my career. Where the sequence is something like this...
CALL FUNCTION 'XYZ'.
.... sy-subrc error handling...
COMMIT WORK AND WAIT.
WAIT UP 2 SECONDS.
CALL FUNCTION 'ABC'.
Here a common scenario is that the function XYZ has its own error handling and rollbacks and calls to update functions. While I have not seen any issues so far, that doesn't mean there isn't something else I am not missing.
Logically, it seems to make sense that if there is a rollback and a proper exception raised in function XYZ, the subsequent COMMIT WORK AND WAIT and WAIT UP TO 2 SECONDS should not really do anything to the database. But, I could always use a second opinion to make sure I am not missing something.
Rainer's case in my understanding would a self-inflicted wound if one is handling their own database LUWs, such as (pardon the pseudo-code)
OPEN CURSOR abc.
... db operations INSERT, UPDATE, etc.
WAIT UP 2 SECONDS. "<-- This is going to cause problems.
... db operations INSERT, UPDATE, etc.
IF all_is_good = abap_true.
db_commit.
ELSE.
db_rollback.
ENDIF.
Thanks for the second opinion here!
Raghu
If the FM 'XYZ' has its own COMMIT/ROLLBACK mechanism, them IMHO the expliciti COMMIT WORK[AND WAIT] is redundant.
On the other hand, if you consider this code snippet:
The WAIT after the BAPI_TRANSACTION_COMMIT stops the code execution for n seconds. IMO it is a workaround to give the DB server a breather to complete the processing, clear the buffers et al. In this case the implicit DB commit is also triggered but doesn't really harm, because the COMMIT has already closed the LUW and the DB is in a consistent state,
Yes, correct! I have very rarely used OPEN-FETCH-CLOSE CURSOR, but afaik they should be used for "read accesses" only.
Anyway to prevent the DB-cursor(opened for read access) from being closed by implicit DB-commits, one can use the addition WITH HOLD.
In theory yes, I agree with you. But, as Raymond Giuseppi indicates here (
), if there was no [AND WAIT] in the original function module, the secondary COMMIT WORK AND WAIT does appear to do the wait correctly. In my personal experience, I can second this behavior and I have seen it improve the lock handling & timing issues significantly - but not perfectly. This is what scares me, that the WAIT was acting on first LUW.Thank you! This is what I will hold as gospel 😀
As per Raymond,
If i understand correctly, he's suggesting not to let the corresponding FM handle the commit logic. Instead let the caller should handle it.
IMHO, (B)APIs should not handle any DB commit, the caller should be responsible for it. Whether to issue an asynchronous (COMMIT WORK) or a synchronous(COMMIT WORK AND WAIT) depends on the application.
Which WAIT are you referring to?
Sorry, misread that. However, my point which I will explain below still stands.
Totally agree.
In my experience (I do a lot of custom WM-RF dialog programming), there are several common sequences of WM posting that I do. The function modules all have their own 'CONFIRM_WORK' that I (used to) leave in place. Using the COMMIT WORK AND WAIT between the function modules made a significant difference in number of failures.
An example would be, when you pick from a different location than is suggested in the TO, you may have to make all these calls...
CALL FUNCTION 'L_TO_CANCEL'
EXPORTING
....
i_commit_work = 'X'
COMMIT WORK AND WAIT.
CALL FUNCTION 'L_TO_CREATE_DN'
EXPORTING
....
i_commit_work = 'X'
COMMIT WORK AND WAIT.
CALL FUNCTION 'L_TO_CONFIRM'
EXPORTING
....
i_commit_work = 'X'
COMMIT WORK AND WAIT.
Since it has been several years since I made the change to using the methods I describe in this blog, I don't have the actual data to show the difference, so you will have to take my word for it. Without the explicit COMMIT WORK AND WAIT, there would be errors every day - some 5% of all transactions would fail. With the COMMIT WORK AND WAIT the issues would be down to once every few weeks.
So, this is the WAIT that I am referring to... I was letting the function module handle its own COMMIT and then following it up with a COMMIT WORK AND WAIT. In hind-sight, I have a hard time understanding why it works, all I can say is it did! My only ham-handed thought at that time a decade and half ago, was if one COMMIT is good, two is better 😛
I am no expert in WM and have no idea how these FMs work in tandem.
Do these FMs issue asynch or synch updates?
Based on my theoretical knowledge i think that you could have used WAIT UP TO as well. Because when the FM trigger a COMMIT WORK, the SAP-LUW(as well as the DB-LUWs) should be closed.
As i have mentioned, this is "theory" speaking. Maybe the ground reality is totally different 😐
They don't really work in tandem... one has to complete its database commits before the next can work. These FMs all correspond to different T-Codes, and are often chained together to achieve what the end user thinks is a single step - "I am going to pick from here instead of there".
Technically the built in commits are asynch .
It does work. and I have used it as last resort in the past. However, in my little world of barcode scanning a 3 second wait between scans is considered unacceptable. This is actually the 1st method that I describe in the blog post. I will edit and clarify that this exactly what I am talking about.
The duration that you need to wait also has system to system (client to client, and DEV-QA-PROD) inconsistencies. Thus using the 'ENQUEUE' method that I describe in the blog takes away all the guess work.
It doesn't make any sense.
COMMIT WORK AND WAIT after COMMIT WORK shouldn't have any effect as it doesn't have anything to do.
What you should have done in this case was setting parameter i_commit_work = space and executing COMMIT WORK AND WAIT afterwards.
Yep, If you read the entire thread you will see that I wholeheartedly agree with you in theory. However, just as Suhas mentions below... what I have seen in practice in a live productive environment is different from theory. It changed a customer from saying that I delivered a piece of **** and threatening not to pay to loving the solution -- and me left scratching my head saying "That shouldn't have worked, but oh, well, they're happy, I'm getting paid, I'm happy".
And, yes, SAP - at least some groups - were made aware of this through official channels. And we got the official response of - that is a custom program 😐 . I would love to have an official answer, or at least a solid theory of the why's and hows.
Well, I'll be happy to see this example.
It sounds more like an episode of X-Files 😛
In such case, the WAIT UP TO n SECONDS is redundant (Actually, WAIT UP TO n SECONDS is redundant and a bad solution in most cases).
The COMMIT WORK AND WAIT should wait until all updates end, like it says...
Synchronous updates should "theoritically" wait for all the update activities to end before resuming the processing.
In the SDN forums you'll see a lot of posts which read like, "Sales Order not available inspite of using synchronous update"
The general theory is that the DB buffers have not been updated. The ususal workaround is to WAIT for these DB-tasks to be over before resuming with the next step. Hence i had mentioned in my post,
I am not a fan of such a solution either and totally agree with you that it is a bad solution. Tbh, i have never faced such a situtation and tend to avoid using WAIT UP TO.
I think that this is one of these cases in which theory must match reality 😆
I'm not familiar with the sales orders problems, but in case there is a problem, it should be reported to SAP and they should fix it (On the other hand, in case that CRM is involved, these might be synchronization of different systems issues).
Hello Suhas, this topic interests me a lot, do you know some reference materials about that symptom? Is it due to the fact there are two different workprocesses (one which updates, one which reads), and does a local update task (SET UPDATE TASK LOCAL) solve the issue?
I have a scenario to test that I could try it out on. It might take me a couple of days to iron out other things first. My first check will be to see if I can replicate the problem... a new customer and I usually just put in the ENQUEUE lock checks, so I don't know if their setup will cause issues in the first place.
I will let you know what I can.
Sorry, I tried replicating the issue and I could not. Of course it works like a charm now that I want it to fail!
This customer is running an Oracle DB... I know that at least one of the places that had problems in the past was running SQL Server. I wonder if there is any connection. In my past - pre-SAP days - I had routinely been able generate deadlocks or insert duplicate keys in SQL Server, but not with Oracle in volume testing. Oracle would just slow down but never do anything unexpected.
Yeah, speculation - or a question for the X-files.... Shai, get on it 😆
I am not a fan of it either.
A few weeks ago, the end users as well as the Basis-Team were complaining that the payment run(F110) was running longer than usual.
Runtime analysis showed that someone has written a WAIT UP TO 30s in one of the BTE 😡
Having said that WAIT is not bad after all, it is really useful in the programming of aRFC and ABAP Messaging Channel 😘
I will give it a try next time i encounter similar situation. Btw, how do you identify which lock object to check? Did you use ST01/05?
Ha, ha... sorry, this is funny and sad all at the same time! If I don't laugh at these things, I would be a grumpy, grumpy man.
I have never used ST05 to check lock objects. I will have to try that out. I personally use SM12. Most often open the standard GUI transaction that has the same functionality in change mode - for example VL02N for a delivery or LT12 for a Transfer Order.
If that doesn't work, I put some relatively random (yeah, very scientific 😛 ) breakpoint some where in last half of the function module and then check SM12.
I guess another option, that I have never tried before is temporarily inserting a call to ENQUEUE_READ right after the FM to try to determine what the lock object is.
Update... I just tried ST05 with Enqueue trace on. I like it, I think it is potentially more comprehensive and easier than my methods, especially if you are not sure about the t-code to exactly mimic the function module.
Thank you for the stimulating conversation!
You saved my (holi)day!
Thank you 😀
Thank you Raghu!
I love your solution and it works great for my need, even considering it does a COMMIT.
It took me a while to understand the whole solution you described in part 4), but from what I understand, it seems to be this:
Hope this is right and thank you again!