Tuesday 19 February 2013

Custom workflow timeout issue


Faced a problem in a custom workflow in time out node.
Have created a workflow with timeout node.


The workflow initiated from a custom form which registered in a custom application. Even if the time out node has been added in wf, still time out has not been worked. Process stuck at the node where timeout has been added. The reason I have found when workflow background process runs, the time out called from WF_ENGINE.ProcessTimeOut(itemtype in varchar2) package. And the package has only considered APPS schema or more precisely it checks the the owner of the workflow is in apps schema.

As the owner of the wf is custom application so each time wf process stuck at the timeout node and its wait for the response.


procedure ProcessTimeOut(itemtype in varchar2)
is
  resource_busy exception;
  pragma exception_init(resource_busy, -00054);

  l_itemtype      varchar2(8);
  l_itemkey       varchar2(240);
  l_actid         pls_integer;
  pntfstatus      varchar2(8);
  pntfresult      varchar2(30);

  -- Select one timeout activity that matches itemtype
  -- NOTE: Two separate cursors are used for itemtype and no-itemtype
  -- cases to get better execution plans.


  -- select everything but completed and error.
  -- avoid "not in" which disables index in RBO
  cursor curs_itype is
    select
         S.ROWID ROW_ID
    from WF_ITEM_ACTIVITY_STATUSES S
    where S.DUE_DATE < SYSDATE
    and S.ACTIVITY_STATUS in ('ACTIVE','WAITING','NOTIFIED',
                              'SUSPEND','DEFERRED')
    and S.ITEM_TYPE = itemtype;

  cursor curs_noitype is
    select
         S.ROWID ROW_ID
    from WF_ITEM_ACTIVITY_STATUSES S
    where S.DUE_DATE < SYSDATE
    and S.ACTIVITY_STATUS in ('ACTIVE','WAITING','NOTIFIED',
                              'SUSPEND','DEFERRED');

  idarr RowidArrayTyp;
  arrsize pls_integer;
  eligible boolean;
  schema   varchar2(30);

begin
  -- Fetch eligible rows into array
  arrsize := 0;
  if (itemtype is not null) then
    -- Fetch by itemtype
    for id in curs_itype loop
      arrsize := arrsize + 1;
      idarr(arrsize) := id.row_id;
    end loop;
  else
    -- Fetch all itemtypes
    for id in curs_noitype loop
      arrsize := arrsize + 1;
      idarr(arrsize) := id.row_id;
    end loop;
  end if;

  -- Process all eligible rows found
  for i in 1 .. arrsize loop
    -- Lock row, and check if still eligible for execution
    -- To check eligibility, do original select only add rowid condition.
    -- Note ok to use no-itemtype variant since itemtype can't change
    -- and was already filtered for in original select.
    -- select everything but completed and error. avoid "not in" which
    -- disables index in RBO.
    begin
      select
        S.ITEM_TYPE, S.ITEM_KEY, S.PROCESS_ACTIVITY
      into l_itemtype, l_itemkey, l_actid
      from WF_ITEM_ACTIVITY_STATUSES S , WF_ITEMS WI
      where S.DUE_DATE < SYSDATE
      and S.ACTIVITY_STATUS in ('WAITING','NOTIFIED','SUSPEND',
                                'DEFERRED','ACTIVE')
      and S.ROWID = idarr(i)
      and WI.item_type   = S.ITEM_TYPE
      and WI.item_key    = S.ITEM_KEY
      for update of S.ACTIVITY_STATUS, WI.item_type , wi.item_key NOWAIT;

      -- check if schema matched
        schema := Wf_Engine.GetItemAttrText(l_itemtype,l_itemkey,
                    wf_engine.eng_schema, ignore_notfound=>TRUE);
                   
        ---*** XX Added
        --- For time out feature wf only consider APPS schema.
        --- We have registered our custom packages in the XX schema
        --- So delibaretly we are bypassing schema value as APPS
        If Schema = 'XX'
        then
            schema :='APPS';
        End if;
        ---*** XX End
         
 
      if (schema is null or
          schema = Wf_Engine.Current_Schema) then
        eligible := TRUE;
      else
        eligible := FALSE;
      end if;
    exception
      when resource_busy or no_data_found then
        -- If row already locked, or no longer eligible to run,
        -- continue on to next item in list.
        eligible := FALSE;
    end;

    if (eligible) then
      -- Set the status to COMPLETE:#TIMEOUT.
      Wf_Item_Activity_Status.Create_Status(l_itemtype, l_itemkey, l_actid,
          wf_engine.eng_completed, wf_engine.eng_timedout);

      begin
       begin
        begin
          savepoint wf_savepoint;
          -- If there is a function attached, call it in timeout mode to
          -- give the function one last chance to complete and override
          -- the timeout.
          Wf_Engine_Util.Execute_Post_NTF_Function(l_itemtype, l_itemkey,
              l_actid, wf_engine.eng_timeout, pntfstatus, pntfresult);
          if (pntfstatus = wf_engine.eng_completed) then
            -- Post-notification function found and returned a completed
            -- status.
            -- Complete activity with result of post-notification function.
            Wf_Engine_Util.Complete_Activity(l_itemtype, l_itemkey, l_actid,
                pntfresult, FALSE);
          else
            -- Either had no post-notification function, or result was still
            -- not complete.
            -- In either case, complete activity with #TIMEOUT.
            Wf_Engine_Util.Complete_Activity(l_itemtype, l_itemkey, l_actid,
                wf_engine.eng_timedout);
          end if;
        exception
          when others then
            -- If anything in this process raises an exception:
            -- 1. rollback any work in this process thread
            -- Raise an exception for the next exception handler to finish
            -- remaining steps.
            rollback to wf_savepoint;
            raise;
        end;
       exception
         when NO_SAVEPOINT then
           -- Catch any savepoint error in case of a commit happened.
           Wf_Core.Token('ACTIVITY', Wf_Engine.GetActivityLabel(l_actid));
           Wf_Core.Raise('WFENG_COMMIT_IN_COMPLETE');
       end;
      exception
        when OTHERS then
          -- Remaining steps for completing activity raises an exception:
          -- 2. set this activity to error status
          -- 3. execute the error process (if any)
          -- 4. clear the error to continue with next activity
          Wf_Core.Context('Wf_Engine', 'ProcessTimeout', l_itemkey, l_itemtype,
              to_char(l_actid));
          Wf_Item_Activity_Status.Set_Error(l_itemtype, l_itemkey, l_actid,
              wf_engine.eng_exception, FALSE);
          Wf_Engine_Util.Execute_Error_Process(l_itemtype, l_itemkey,
              l_actid, wf_engine.eng_exception);
          Wf_Core.Clear;
      end;
    end if;

    -- bug 7828862 - Resynch apps context from cached values if it changed
    wfa_sec.Restore_Ctx();

    -- For eligible row: Commit work to insure this activity
    --   thread doesn't interfere with others.
    -- For non-eligible row: Commit to release the lock.
    commit;
    Fnd_Concurrent.Set_Preferred_RBS;

  end loop;

exception
  when others then
    Wf_Core.Context('Wf_Engine', 'ProcessTimeout', l_itemkey, l_itemtype,
                    to_char(l_actid));
    raise;
end ProcessTimeOut;