Invoking response_handler even after seq_h.start completes

Hi Moderators,
In my AXI4 Tb there exists a sequence ‘seq’ which is created once and whenever there is a call to write or read API, seq_h.start is called
Within it’s body() task ::

 use_response_handler(1);
 req = user_seq_item::create("req",this);
 start_item(req);
 // req is randomized
 finish_item(req);
endtask

virtual function void response_handler( uvm_sequence_item response);
 // Some logic here
endfunction

Note that during each call to seq.start it doesn’t wait for response

The issue with above code is even when the response is sent by Driver (via the sequencer) the response_handler is never called
On debugging the UVM Base Class Library I notice

function void uvm_sequencer_param_base::put_response (RSP t);
  ....
  sequence_ptr = m_find_sequence(t.get_sequence_id());

  if (sequence_ptr != null) begin
    // If the response_handler is enabled for this sequence, then call the response handler
    if (sequence_ptr.get_use_response_handler() == 1) begin
      sequence_ptr.response_handler(t);
      return;
    end
    
    sequence_ptr.put_response(t);
  end

Response handler is called only if m_find_sequence returns non-null
On further debugging I observe the implementation of task ‘start’ is as follows

// Within uvm_sequence_base::start
        ........................
        m_sequence_state = UVM_BODY;
        #0;
        body();

        m_sequence_state = UVM_ENDED;
        #0;

        if (parent_sequence != null) begin
          parent_sequence.post_do(this);
        end

        if (call_pre_post == 1) begin
          m_sequence_state = UVM_POST_BODY;
          #0;
          post_body();
        end

        m_sequence_state = UVM_POST_START;
        #0;
        post_start();

        // Drop the objection if enabled
        if (get_automatic_phase_objection()) begin
           m_safe_drop_starting_phase("automatic phase objection");
        end
         
        m_sequence_state = UVM_FINISHED;
        #0;

      end
    join

    if (m_sequencer != null) begin      
      m_sequencer.end_tr(this);
    end
        
    // Clean up any sequencer queues after exiting; if we
    // were forcibly stoped, this step has already taken place
    if (m_sequence_state != UVM_STOPPED) begin
      if (m_sequencer != null)
        m_sequencer.m_sequence_exiting(this);
    end

    #0; // allow stopped and finish waiters to resume

    if ((m_parent_sequence != null) && (m_parent_sequence.children_array.exists(this))) begin
       m_parent_sequence.children_array.delete(this);
    end

    old_automatic_phase_objection = get_automatic_phase_objection();
    m_init_phase_daps(1);
    set_automatic_phase_objection(old_automatic_phase_objection);
  endtask

The call to function ‘m_sequence_exiting’ internally calls function ‘m_unregister_sequence’ which unregisters the sequence from the Sequencer.

Since the goal is to invoke the reponse_handler when the response is returned by the driver ( even after task ‘start’ ends ), one solution that I am thinking of is to override the virtual start task in my sequence.
In the overridden task ‘start’ I am thinking of ending the task once fork join completes

 //  Within the overriden task 'start'
        ..........  
        m_sequence_state = UVM_FINISHED;
        #0;

      end
    join
   
 // Doesn't call the  following APIs :: 
 //  m_sequencer.end_tr(this) , 
 //  m_sequencer.m_sequence_exiting(this); etc ...
    endtask:start

[1] Would this be a correct approach ?
[2] Any alternate suggestions are welcome

I suggest creating a thread using get_response() instead of ever using the response handler.

Could you please elaborate ?
Is usage of response_handler discouraged ( similar to uvm_field macros ) ?

Once the start task ends, it would end up un-registering the sequence due to following code
(irrespective of whether response_handler is active or not)

 if(m_sequence_state != UVM_STOPPED) begin
   if(m_sequencer != null)
   m_sequencer.m_sequence_exiting(this); // Called with or w/o response handler !!
 end

Even for a case where response_handler isn’t activated
get_response unblocks when sequencer calls put_response ( via driver calling put(rsp) / item_done(rsp) )

function void uvm_sequencer_param_base::put_response (RSP t);
  ....
  sequence_ptr = m_find_sequence(t.get_sequence_id());

  if(sequence_ptr != null) begin // False due to m_sequencer.m_sequence_exiting(this);

    if (sequence_ptr.get_use_response_handler() == 1) begin
      sequence_ptr.response_handler(t);
      return;
    end
    
    sequence_ptr.put_response(t);
  end

This would mean that function ‘m_find_sequence’ would return null (due to previous call to seq_h.start ending) and never call sequence_ptr.put_response ( thereby get_response(rsp) would remain blocked )

Also unlike response_handler, the start task calls the following ( b4 calling body() )

 // Check that the response queue is empty from earlier runs
 clear_response_queue(); // Results in response_queue.delete()

I would need to override this virtual task ‘clear_response_queue’ as well to not delete it (during latter calls to seq_h.start)

What if I were to assign null to m_sequencer at the end of each body() task invocation ?

if (m_sequence_state != UVM_STOPPED) begin
  if (m_sequencer != null) // This condition would be false now
      m_sequencer.m_sequence_exiting(this);
end

The UVM offers various ways to achieve the same result. Unless there’s a distinct advantage, I recommend adopting a single approach and enforcing it as a guideline.

It seems that both techniques face problems when the body() method returns prematurely. Do you have a good reason for wanting the body() method to return before receiving a response? Can you delay it until it gets a response?