docs: @Override
      @FFDCIgnore(IOException.class)
      public void complete(VirtualConnection vc) {
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.entry(tc, "complete",  vc);
          }
          if (null == vc) {
              if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
                  Tr.exit(tc, "complete");
              }
              return;
          }
          //We don't need to do an initial seeding of the buffer here as HTTP Channel has already done that in their
          //complete callback from the TCP Channel. When we call an async read on them they will return indicating
          //there is data to read
          synchronized( this.in.getCompleteLockObj()){
  
              //This variable was introduced to prevent us from calling into Channel again when there is an outstanding ready
              //Once isReady returns false once, we don't want to change it back until the next call into onDataAvailable
              //This variable prevents isReady from returning true if there is an outstanding read           
              this.in.setAsyncReadOutstanding(false);
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Calling user's ReadListener onDataAvailable : " + this.in.getReadListener());
              }       
              
              SRTServletRequestThreadData.getInstance().init(_requestDataAsyncReadCallbackThread);
              
              //Push the original thread's context onto the current thread, also save off the current thread's context
              boolean localPushedThreadContext = false;
              try {
                  if (pushedThreadContext == false) {
                      this.threadContextManager.pushContextData();
                      this.pushedThreadContext = true;
                      localPushedThreadContext = true;
                  }
  
                  // Call into the user's ReadListener to indicate there is data available
                  try{
                      this.in.getReadListener().onDataAvailable();
  
                      if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Returned from user's ReadListener onDataAvailable : " + this.in.getReadListener());
                      } 
                  } catch (Throwable onDataAvailableException){
                      if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Exception occurred during ReadListener.onDataAvailable : " + onDataAvailableException + ", " + this.in.getReadListener());
                      }
                      error(vc, onDataAvailableException);
                      if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
                          Tr.exit(tc, "complete");
                      }
                      return;
                  }
              
                  if(this.in.getReadListener() != null){ // the stream may have been closed during onDataAvailable
                      // cannot call onAllDataRead()
  
                      //Determine if the message has been fully read. If so call the user's ReadListener to indicate all data has been read
                      //If the message isn't fully read then issue a forced async read to the channel
                      if(in.isFinished()){
                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Message is fully read, calling ReadListener onAllDataRead : " + this.in.getReadListener());
                          }
                          try{
                              this.in.getReadListener().onAllDataRead();
                              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                  Tr.debug(tc, "Returned from user's ReadListener onAllDataRead : " + this.in.getReadListener());
                              } 
                          } catch (Throwable onAllDataReadException){
                              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                  Tr.debug(tc, "Exception occurred during ReadListener.onAllDataRead : " + onAllDataReadException + ", " + this.in.getReadListener());
                              }
                              error(vc, onAllDataReadException);
                              if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
                                  Tr.exit(tc, "complete");
                              }
                              return;
                          }
                      } else {
                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Data hasn't been fully read yet. There should be an outstanding read at this point : " + this.in.getReadListener());
                          }
                      
                          // Need to call pre-join since the async read will occur on another
                          // thread.
                          if (onErrorDriven == false) {
                              this.in.prepareAsyncReadListener();
                          }
                      }  
                  }
                  else{
                      if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                          Tr.debug(tc, "ReadListener is not set , stream must be closed, cannot call onAllDataRead()");
                      }
                  }
              } finally {
                  // Revert back to the thread's current context
                  if (localPushedThreadContext == true) {
                      this.pushedThreadContext = false;
                      this.threadContextManager.popContextData();
                  }
              }
          }
          
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.exit(tc, "complete");
          }
      }