Skip to Content
Author's profile photo David Halitsky

Do you practice safe BAdI Recursion? If not, WDAbap can show you how.

I was recently confronted with a case in which I had to call a BAdI recursively. To put this more precisely: within the BAdI I had to call the function module which calls the BAdI. Now, if you’re any kind of developer, this kind of recursion is really exciting precisely because if you code it, you yourself can bring an entire system to its knees. (And if your site is load-balancing, you can probably bring more than one system to its knees.) But because you’re public-spirited, you don’t want to do this. Yeah – you still want the excitement of the recursion – but you also want to practice safe recursion and figure out how to stop the loop. In the attached segment of code, I’m using shared buffer in ABAP exactly the way a controller class is used in WDAbap. As the recursion iterates, I decrement a counter in shared buffer and when this counter reads 0, the recursion stops. (Reason I’m using shared buffer instead of memory is because I already have stuff that has to be shared between dialog and update tasks, so I’m using shared buffer already.) A couple of notes before you look at the BAdI method below: 1) what made me think of this way to stop the recursion was the way the SAP pro’s did it when they wrote the WDAbap component WDR_TEST_UI_ELEMENTS (see my previous blogs on this component.) 2) each time the method is entered, the itab “s7_tab_mseg” has the same number of rows as the itab “gt_outtab”, and no rows are deleted from either of these itabs during the recursion; so the two relevant questions are – which row do we read in the current pass through the method and how do we know when we’re done?

Note: the itab index handling in this code is correct, but not the stop-recursion technique. See new code posted below (5/1/2007.) method IF_EX_INSPECTIONLOT_UPDATE~CREATE_IN_UPDATE. * for context id typing TYPE-POOLS: thfb. * ******************************************** DATA: s_qals TYPE qals, s7_tab_mseg TYPE STANDARD TABLE OF mseg, wa_mseg TYPE mseg, gt_outtab TYPE STANDARD TABLE OF zzmseg_qm_2007, wa_zzqm TYPE zzmseg_qm_2007. DATA: context_id TYPE THFB_CONTEXT_ID, v_memkey2(64) TYPE c, v_memkey3(65) TYPE c, v_memkey(60) TYPE c, v_stop_recursion TYPE i, v_itab_cnt TYPE i, v_index TYPE i. DATA: v_cnt_wyt3 TYPE i. ********************************************* CONCATENATE 'SRCSN' sy-uname sy-datum context_id INTO v_memkey3. IMPORT v_stop_recursion TO v_stop_recursion FROM SHARED BUFFER indx(st) ID v_memkey3. IF v_stop_recursion = 0. EXIT. ENDIF. CALL FUNCTION 'TH_GET_CONTEXT_ID' IMPORTING CONTEXT_ID = context_id . CONCATENATE 'MSEG' sy-uname sy-datum context_id INTO v_memkey2. IMPORT s7_tab_mseg TO s7_tab_mseg FROM SHARED BUFFER indx(st) ID v_memkey2. CONCATENATE sy-uname sy-datum context_id INTO v_memkey. IMPORT gt_outtab TO gt_outtab FROM SHARED BUFFER indx(st) ID v_memkey. DESCRIBE TABLE gt_outtab LINES v_itab_cnt. v_index = v_itab_cnt - v_stop_recursion + 1. READ TABLE gt_outtab INTO wa_zzqm INDEX v_index. READ TABLE s7_tab_mseg INTO wa_mseg INDEX v_index. IF wa_mseg-bwart <> '101' AND wa_mseg-bwart <> '103'. EXIT. ENDIF. IF wa_mseg-werks <> '2000'. EXIT. ENDIF. s_qals = insplot. CALL FUNCTION 'QPBU_LOT_DELETE' EXPORTING I_QALS = s_qals. s_qals-lifnr = wa_zzqm-zzlifnr. s_qals-pplverw = '5'. SELECT SINGLE plnty plnnr plnal zkriz zaehl FROM mapl INTO (s_qals-plnty, s_qals-plnnr, s_qals-plnal, s_qals-zkriz, s_qals-zaehl) WHERE matnr = wa_mseg-matnr AND werks = wa_mseg-werks AND lifnr = wa_zzqm-zzlifnr. v_stop_recursion = v_stop_recursion - 1. EXPORT v_stop_recursion FROM v_stop_recursion TO SHARED BUFFER indx(st) ID v_memkey3. CALL FUNCTION 'QPBU_LOT_INSERT' EXPORTING QALS_NEW = s_qals. . ENDMETHOD.

Revised version of above:

method IF_EX_INSPECTIONLOT_UPDATE~CREATE_IN_UPDATE. * for context id typing TYPE-POOLS: thfb. * ******************************************** DATA: s_qals TYPE qals, s7_tab_mseg TYPE STANDARD TABLE OF mseg, wa_mseg TYPE mseg, gt_outtab TYPE STANDARD TABLE OF zzmseg_qm_2007, wa_zzqm TYPE zzmseg_qm_2007. DATA: context_id TYPE THFB_CONTEXT_ID, v_memkey(60) TYPE c, v_memkey2(64) TYPE c, v_memkey3(65) TYPE c, v_memkey4(65) TYPE c, v_do_exit TYPE c, v_cntr_recursion TYPE i, v_itab_cnt TYPE i, v_index TYPE i. DATA: v_cnt_wyt3 TYPE i. ********************************************* CONCATENATE 'DEXIT' sy-uname sy-datum context_id INTO v_memkey4. IMPORT v_do_exit TO v_do_exit FROM SHARED BUFFER indx(st) ID v_memkey4. IF v_do_exit = 'N'. v_do_exit = 'Y'. EXPORT v_do_exit FROM v_do_exit TO SHARED BUFFER indx(st) ID v_memkey4. EXIT. ENDIF. CONCATENATE 'CRCSN' sy-uname sy-datum context_id INTO v_memkey3. IMPORT v_cntr_recursion TO v_cntr_recursion FROM SHARED BUFFER indx(st) ID v_memkey3. CALL FUNCTION 'TH_GET_CONTEXT_ID' IMPORTING CONTEXT_ID = context_id . CONCATENATE 'MSEG' sy-uname sy-datum context_id INTO v_memkey2. IMPORT s7_tab_mseg TO s7_tab_mseg FROM SHARED BUFFER indx(st) ID v_memkey2. CONCATENATE sy-uname sy-datum context_id INTO v_memkey. IMPORT gt_outtab TO gt_outtab FROM SHARED BUFFER indx(st) ID v_memkey. DESCRIBE TABLE gt_outtab LINES v_itab_cnt. * v_index = v_itab_cnt - v_stop_recursion + 1. v_index = v_itab_cnt - v_cntr_recursion + 1. READ TABLE gt_outtab INTO wa_zzqm INDEX v_index. READ TABLE s7_tab_mseg INTO wa_mseg INDEX v_index. IF wa_mseg-bwart <> '101' AND wa_mseg-bwart <> '103'. EXIT. ENDIF. IF wa_mseg-werks <> '2000'. EXIT. ENDIF. s_qals = insplot. CALL FUNCTION 'QPBU_LOT_DELETE' EXPORTING I_QALS = s_qals. s_qals-lifnr = wa_zzqm-zzlifnr. s_qals-pplverw = '5'. SELECT SINGLE plnty plnnr plnal zkriz zaehl FROM mapl INTO (s_qals-plnty, s_qals-plnnr, s_qals-plnal, s_qals-zkriz, s_qals-zaehl) WHERE matnr = wa_mseg-matnr AND werks = wa_mseg-werks AND lifnr = wa_zzqm-zzlifnr. v_do_exit = 'N'. EXPORT v_do_exit FROM v_do_exit TO SHARED BUFFER indx(st) ID v_memkey4. v_cntr_recursion = v_cntr_recursion - 1. EXPORT v_cntr_recursion FROM v_cntr_recursion TO SHARED BUFFER indx(st) ID v_memkey3. CALL FUNCTION 'QPBU_LOT_INSERT' EXPORTING QALS_NEW = s_qals. . ENDMETHOD.

Assigned tags

      5 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Thomas Jung
      Thomas Jung
      >you yourself can bring an entire system to its knees. (And if your site is load-balancing, you can probably bring more than one system to its knees.)

      Of course every piece of recursive logic should have a built-in escape hatch, but with recursion in ABAP  it is difficult to bring even a single system to its knees. About the only way that would happen is if you have a widespread problem that occurs every time a popular transaction is ran - thereby activating the recursive loop in many instances in parallel.  But of course something that widespread would have been caught in testing - right. 🙂

      A single program caught in a recursive loop would be isolated to a single work process.  This allows other work on the same application server to go on about its business.  The way this is implemented, you can still disrupt execution from the SAPGui using the Stop Transaction function or attach a remote debugger to the looping process.  My experience is that applications running in such a loop quickly exhaust their memory thresholds and are shutdown by the virtual machine before they do any really serious damage. 

      This has changed a bit over the years.  I remember my first day in BC400 class (introduction to ABAP)  11 years ago.  We were working on a 3.0B system and before the end of the day I had managed to crash the entire training system with the following code:
        data isflight type standard table of sflight with header line.
        select * from sflight into table isflight.
        loop at isflight.
          append isflight.
        endloop.

      Note the "old" syntax of a table with a header line was done to simulate the original example from so long ago - certainly not good form and almost painful to write after years of spent breaking the habit.  This simple example could kill an entire system on 3.0B - but when I execute it now on NetWeaver 7.0 it runs for a few seconds and then is gracefully terminated by the virtual machine once it exhausts its memory boundaries.  My point being that the virtual machine, ABAP runtime, and dialog work process model are very efficient monitors doing a good job of watching out for bad ABAP coding and protecting other users of the system from the runaway code. 😉

      Load-balancing really doesn't play into the picture either.  If you have a problem in your application that hits on every occurrence then you have a widespread problem regardless of load-balancing.  However a single looping application can't consume more than one work process and certainly can't jump application servers via load-balancing.  At the time of logon, a session is assigned an application server via load-balancing and its stuck with that assignment.  Only when calling an update work process or implicitly initiating parallel processing via RFC can it really cross application servers. 

      By the way, in NetWeaver 7.0 there is a function module called SYSTEM_CALLSTACK.  It can be used to read the depth of the call stack at runtime.  You could use this to determine your recursion threshold and cancel instead of placing a variable in shared memory or passing a counter through the recursion interface. 

      Author's profile photo David Halitsky
      David Halitsky
      The JAVA side of the house was working as late as SDN/Labs 2005 to achieve the same "isolation" effect in the JVM - I forget what the project name was and what the product name is - "robust JAVA?" - something like that.

      Hey - thanks for the tip about system_callstack.  In the old days, I always enjoyed having to make the occasional system call to get Big Blue goodies - it made me feel like a real pro.  So if I ever get a chance to use this one, I'm sure it will bring back the thrills of yesteryear.

      Best regards
      djh

      Author's profile photo Former Member
      Former Member
      In customer-exits, we can pass the values between the calls by declaring a flag/variable as STATICS.

      I guess in BADI method you can declare the static attribute in the implemented class and it might allow you to pass data between the method calls.
      Not sure if this will work in your situation.

      STATICS declaration is not allowed in methods as it has similar purpose as class attribute [ static ].Only difference is that static variables in classical ABAP will have local visibility while static attribute in the class will have class level visibility. But i think both should retain previous call value.

      Author's profile photo David Halitsky
      David Halitsky
      Hi Ram -

      Thanks very much for taking the time to reply.

      I thought of something like that, but my assumption is that the implementing class will be re-instantiated each time the function QPBU_LOT_INSERT is called (this is the function that calls the BAdI.  When I have a chance, I'll check to see if it is or isn't, and if it isn't, then your idea is very slick.

      Best regards
      djh

      Author's profile photo David Halitsky
      David Halitsky
      Today's unit-testing showed me that the logic of the above code-segment works fine to pick the right row of each itab to read, but is completely wrong when it comes to stopping the recursion.

      You'll notice that QPBU_LOT_INSERT receives a structure from MIGO, not an itab - even when the GR posting is multi-line, not single-line.

      Therefore, the "insplot" that QPBU_LOT_INSERT passes to the exit is also a structure, not a table.

      But the code as written above assumes that insplot is a table - which is a really stupid oversight.

      So although the recursion counter is still needed to pick the right row of the each itab to read, the recursion has to be stopped by a simple on-off switch that's also stored in shared buffer. 

      To keep this response short, the revised code will be added to the original blog post momentarily.

      djh