This is the continuation of Concurrent Manager internals (Part 1), where I described how concurrent manager selects a list of requests that have to be run. It was also discovered, that there is no special queue of requests for each concurrent manager and that there are some settings that are obtained only at the time of startup of a concurrent manager. At the moment it is clear that all the processes of the same concurrent manager (e.g. Standard Manager) use the same select to query the requests for execution, therefore it is possible that more than one concurrent manager process will have the same request_id’s to run. In this post I’ll describe the mechanism that’s used to assign the request to a particular concurrent manager process.

The main idea is simple - after getting the concurrent request rowids using the select shown in Concurrent Manager internals (Part 1) the concurrent manager starts processing the list one by one. Each processing case starts with attempt to lock the status_code of particular request using the select ... for update nowait statement below.

SELECT  ':)' -- ~150 fields selected here. Skipped them to shorten up the select
  FROM fnd_concurrent_requests    R,
       fnd_concurrent_programs    P,
       fnd_application            A,
       fnd_user                   U,
       fnd_oracle_userid          O,
       fnd_conflicts_domain       C,
       fnd_concurrent_queues      Q,
       fnd_application            A2,
       fnd_executables            E,
       fnd_conc_request_arguments X
 WHERE R.Status_code = 'I'
   And ((R.OPS_INSTANCE is null) or (R.OPS_INSTANCE = -1) or
       (R.OPS_INSTANCE =
       decode(:dcp_on, 1, FND_CONC_GLOBAL.OPS_INST_NUM, R.OPS_INSTANCE)))
   And R.Request_ID = X.Request_ID(+)
   And R.Program_Application_Id = P.Application_Id(+)
   And R.Concurrent_Program_Id = P.Concurrent_Program_Id(+)
   And R.Program_Application_Id = A.Application_Id(+)
   And P.Executable_Application_Id = E.Application_Id(+)
   And P.Executable_Id = E.Executable_Id(+)
   And P.Executable_Application_Id = A2.Application_Id(+)
   And R.Requested_By = U.User_Id(+)
   And R.Cd_Id = C.Cd_Id(+)
   And R.Oracle_Id = O.Oracle_Id(+)
   And Q.Application_Id = :q_applid
   And Q.Concurrent_Queue_Id = :queue_id
   And Q.Running_Processes <= Q.Max_Processes
   And (P.Enabled_Flag is NULL OR P.Enabled_Flag = 'Y')
   And R.Hold_Flag = 'N'
   And R.Requested_Start_Date <= Sysdate
   And (R.Enforce_Seriality_Flag = 'N' OR
       (C.RunAlone_Flag = P.Run_Alone_Flag And
       (P.Run_Alone_Flag = 'N' OR Not Exists
        (Select Null
             From Fnd_Concurrent_Requests Sr
            Where Sr.Status_Code In ('R', 'T')
              And Sr.Enforce_Seriality_Flag = 'Y'
              And Sr.CD_id = C.CD_Id))))
   And R.Rowid = :reqname
   And ((P.Execution_Method_Code != 'S' OR
       ((0, 98), (0, 100), (0, 31721), (0, 31722), (0, 31757))) AND
       ((510, 40112), (510, 40113), (510, 41497), (510, 41498),
        (530, 41859), (530, 41860), (535, 41492), (535, 41493),
        (535, 41494))))
   FOR UPDATE OF R.status_code NoWait

If we take a closer look at the query, it’s very similar to the one we saw before (all the main criteria are repeated into this query to make sure nothing has changed with the request after reading it into concurrent manager’s cache), only this one joins the fnd_concurrent_requests table to other tables to select additional data or add some additional checks. For example, this part of the query:

And ((R.OPS_INSTANCE is null) or (R.OPS_INSTANCE = -1) or
       (R.OPS_INSTANCE =
       decode(:dcp_on, 1, FND_CONC_GLOBAL.OPS_INST_NUM, R.OPS_INSTANCE)))

THis is included to implement the Parallel Concurrent Processing with RAC support feature that allows to assign concurrent requests to be executed on a particular RAC node. (If the feature is set up, one can use “Database Instance” profile option settable at Application or Responsibility level to define which RAC instance has to be used for requests submitted from that Application or Responsibility).

Also a part that implements the “Run Alone” option check for a concurrent program is little bit changed:

And (R.Enforce_Seriality_Flag = 'N' OR
       (C.RunAlone_Flag = P.Run_Alone_Flag And
       (P.Run_Alone_Flag = 'N' OR Not Exists
        (Select Null
             From Fnd_Concurrent_Requests Sr
            Where Sr.Status_Code In ('R', 'T')
              And Sr.Enforce_Seriality_Flag = 'Y'
              And Sr.CD_id = C.CD_Id))))

here we can see that it checks that P.Run_Alone_Flag = ‘N’ (“Run Alone” is not set for that concurrent program) or there are no other requests in phase/status “Running/Normal” or “Running/Terminating” - so it actually makes sure no other requests are running if “Run Alone” is set.

The execution of the select above normally will end in one of 3 ways:

  1. It executes sucessfuly - returns 1 record and the lock on R.status_code is obtained;
  2. It errors with ORA-00054: resource busy and acquire with NOWAIT specified;
  3. It returns 0 records.

Cases 2 and 3 mean that either another concurrent manager has locked the request already (and another process of Concurrent Manager will be responsible for running this request) or the the request did not satisfy all the query conditions (e.g. it’s status_code is already ‘R’ - running, it was put on hold, etc) meaning that something has happened to this requests after placing it into concurrent managers cache. If manager gets the lock on request’s status_code, it will be running this request and an update statement followed by COMMIT is being executed to update the requests details in fnd_concurrent_requests table:

   set PHASE_CODE          = 'R',
       STATUS_CODE         = 'R',
       ACTUAL_START_DATE   = nvl(actual_start_date, SYSDATE),
       LOGFILE_NAME        = :b1,
       OUTFILE_NAME        = :b2,
       LOGFILE_NODE_NAME   = :b3,
       OUTFILE_NODE_NAME   = :b3,
       NLS_CodeSet         = substr(userenv('LANGUAGE'),
                                    (instr(userenv('LANGUAGE'), '.') + 1)),
       LAST_UPDATED_BY     = :b5
 where ROWID = :b6;

Now, when we know how concurrent managers process the concurrent request “queue”, lets have a look at concurrent manager’s Cache Size (set in Concurrent -> Managers -> Define form) and Sleep Seconds (set in Concurrent -> Managers -> Define -> Work Shifts form) settings:

  • Cache size - defines a number of requests concurrent manager remembers (fetches) from fnd_concurrent_requests table, so that it would not have to re-query the table after each execution of a concurrent request;
  • Sleep Seconds - number of seconds your manager waits between checking the list of pending concurrent requests.

The trace file of concurrent manager revealed that it was requerying the fnd_concurrent_requests table stright after it had processed all the requests it knew of. It was waiting the “Sleep seconds” time only if there were no requests for execution queried.

Now imagine what happens if you have configured many concurrent manager processes (e.g. - 6), a small cache size (e.g. - 3) in a busy environment where there are many short-time executing requests.

Each process will remember 3 requests for execution (meaning: fetch 3 records from fnd_concurrent_requests table), but as we know all the concurrent manager processes use the same statement to query the list of pending requests, so it’s very likely that there will be several processes having same requests in their caches, which leads to high probability that, while particular concurrent process is executing the 1st request in it’s cache, all or most of other cached requests will be executed by other concurrent processes. This in turn will force the concurrent manager process to re-query the fnd_concurrent_requests table after executing just 1 request instead of expected 3, and that is not what we want if we set the cache size to 3.

To minimize this effect I would recommend setting “cache size” to a number of desired cache size multiplied by count of concurrent manager processes. In case described above, it would be 6 processes * cahe size of 3 = 18.