docs: @Override
      public void close(VirtualConnection inVC, Exception e) {
          final boolean bTrace = TraceComponent.isAnyTracingEnabled();
          if (bTrace && tc.isDebugEnabled()) {
              Tr.debug(tc, "close() called: " + this + " " + inVC);
          }
  
          boolean errorState = (null != e);
          if (errorState && bTrace && tc.isDebugEnabled()) {
              Tr.debug(tc, "close() in error state, " + e);
          }
  
          final HttpInboundServiceContextImpl sc = getHTTPContext();
          final HttpChannelConfig config = sc.getHttpConfig();
          // valid responses as well as error responses all go through here so
          // this is where we tell the factory that any large response is complete
          if (sc.containsLargeMessage()) {
              getChannel().getFactory().releaseLargeMessage();
          }
  
          // check to see if the message has been fully sent (unless we
          // are in an error state)
  
          if (!errorState && !sc.isMessageSent()) {
              try {
                  VirtualConnection rc = sc.finishResponseMessage(null, this, false);
                  // if the finish is done already, then continue with code below
                  if (null == rc) {
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "finishing response msg async");
                      }
                      return;
                  }
              } catch (MessageSentException mse) {
                  // no FFDC required, nothing to do here
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Error: message sent exception when flag doesn't agree");
                  }
                  // shouldn't get here so just close the socket
                  getDeviceLink().close(inVC, mse);
                  return;
              }
          }
  
          // if the incoming request body was never read, we must purge the body
          // off the socket before we can continue with the read for the next
          // request... assuming we're not in an error condition
  
          if (!errorState
              // 346196.2 -- if we stopped, then don't purge the body
              && getChannel().isRunning() && !sc.isIncomingMessageFullyRead()) {
              if (bTrace && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Incoming request body never read.");
              }
              try {
                  // 346196.4 - read/release one buffer at a time to avoid
                  // unnecessary memory storage for large bodies
                  VirtualConnection rc = sc.getRequestBodyBuffer(HttpIgnoreBodyCallback.getRef(), false);
                  boolean reachedEnd = false;
                  while (null != rc) {
                      WsByteBuffer buffer = sc.getRequestBodyBuffer();
                      if (null != buffer) {
                          buffer.release();
                          rc = sc.getRequestBodyBuffer(HttpIgnoreBodyCallback.getRef(), false);
                      } else {
                          // end of body found
                          reachedEnd = true;
                          break;
                      }
                  }
                  if (!reachedEnd) {
                      // wait for callback
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "close() waiting for body purge callback");
                      }
                      return;
                  }
              } catch (Exception purgeException) {
                  // no FFDC required
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Exception purging request body: " + purgeException);
                  }
                  errorState = true;
              }
          }
  
          if (this.myInterface.getLink() instanceof H2HttpInboundLinkWrap) {
              if (bTrace && tc.isDebugEnabled()) {
                  Tr.debug(tc, "we're H2, calling that close");
              }
              this.myInterface.getLink().close(inVC, e);
  
              return;
          }
  
          // If servlet upgrade processing is being used, then don't close the socket here
          if (inVC != null) {
              String upgraded = (String) (inVC.getStateMap().get(TransportConstants.UPGRADED_CONNECTION));
              if (upgraded != null) {
                  if (!errorState && getChannel().isRunning() && (upgraded.compareToIgnoreCase("true") == 0)) {
  
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Connection Not closed because Servlet Upgrade detected.");
                      }
                      return;
                  }
              }
          }
  
          // now check to see if the connection should be kept open (unless
          // we are in an error state). Perform one last check against a channel
          // stop, close the socket if not running.
  
          if (!errorState && sc.isPersistent() && getChannel().isRunning()) {
  
              if (config.getDebugLog().isEnabled(DebugLog.Level.DEBUG)) {
                  config.getDebugLog().log(DebugLog.Level.DEBUG, HttpMessages.MSG_READ_PERSISTENT, sc);
              }
  
              sc.resetStartTime();
              this.myTSC.getWriteInterface().setBuffers(null);
  
              // check the last buffer from the previous request as
              // there might be the start of another request on it (pipelining)
  
              if (sc.isReadDataAvailable()) {
                  handlePipeLining();
                  return;
              }
              // no pipelined data, clear the SC and setup for a JIT read
              if (bTrace && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Reading for another request...");
              }
              if (getChannel().getHttpConfig().runningOnZOS()) {
                  // 342859 - add a marker indicating the first read of a subsequent
                  // request is starting. This is used for WLM type management on z/OS
                  // right now with request message priority handling and monitoring
                  // of "in-system" times
                  getVirtualConnection().getStateMap().put(HttpConstants.HTTPFirstRead, "true");
              }
  
              sc.clear();
              sc.setReadBuffer(null);
              sc.setupJITRead(config.getIncomingHdrBufferSize());
  
              // do a forced async read to free up the thread since there will
              // no doubt be a delay until the next request -- we've already
              // checked for pipelined ones
              this.myTSC.getReadInterface().read(1, HttpICLReadCallback.getRef(), true, config.getPersistTimeout());
  
          } else {
              if (bTrace && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Closing connection");
              }
  
              //PI11176 Begin
              if (getChannel().getHttpConfig().shouldAttemptPurgeData()) {
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "attemptPurgeData true, purging the data before closing the connection");
                  }
                  try {
                      //Setup for a JIT read. This will allocate a buffer for X bytes if a buffer hasn't already been allocated
                      //If there is nothing to read the buffer is automatically released
                      sc.setupJITRead(10);
                      //Set the buffer to null at the TCP layer before continuing. We will be doing this after anyway, so want to do it ahead of time so we are starting with out buffer.
                      this.myTSC.getReadInterface().setBuffer(null);
                      boolean processing = true;
  
                      while (processing) {
                          //Do the read
                          long numBytesRead = this.myTSC.getReadInterface().read(0, 1);
                          //Get the buffer associated with the read, could return null
                          WsByteBuffer indivBuffer = this.myTSC.getReadInterface().getBuffer();
                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Bytes read : " + numBytesRead);
                          }
                          //Check if the bytesRead are 0. If so then we are done. If not then we have to process the data(which in this case means throw it out
                          if (numBytesRead <= 0) {
                              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                  Tr.debug(tc, "No data read");
                              }
                              if (indivBuffer != null) {
                                  //Means we read more than once to set the buffer to null and release it
                                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                      Tr.debug(tc, "No data read, releasing any buffer that was allocated");
                                  }
                                  this.myTSC.getReadInterface().setBuffer(null);
                                  indivBuffer.release();
                              }
                              processing = false;
                          } else {
                              if (indivBuffer != null) {
                                  //Clear the buffer because we read some data
                                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                      Tr.debug(tc, "Found a buffer after the read : " + indivBuffer);
                                  }
                                  indivBuffer.clear();
                                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                      Tr.debug(tc, "Cleared the buffer, going back to read again, " + indivBuffer);
                                  }
                              } else {
                                  //We are done since there was no buffer
                                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                      Tr.debug(tc, "No buffer found, assuming we are done");
                                  }
                                  processing = false;
                              }
  
                          }
                      }
                  } catch (IOException purgeException) {
                      //Encountered an exception, if there is a buffer, just release and null out
                      WsByteBuffer indivBuffer = this.myTSC.getReadInterface().getBuffer();
                      if (indivBuffer != null) {
                          this.myTSC.getReadInterface().setBuffer(null);
                          indivBuffer.release();
                      }
                      if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                          Tr.debug(tc, "IOException during purge : " + purgeException);
                      }
                  }
              }
              //PI11176 End
  
              if (config.getDebugLog().isEnabled(DebugLog.Level.INFO)) {
                  config.getDebugLog().log(DebugLog.Level.INFO, HttpMessages.MSG_CONN_CLOSING, sc);
              }
  
              if (this.myTSC != null) {
                  TCPWriteRequestContext writeInterface = this.myTSC.getWriteInterface();
                  if (writeInterface != null)
                      writeInterface.setBuffer(null);
                  TCPReadRequestContext readInterface = this.myTSC.getReadInterface();
                  if (readInterface != null)
                      readInterface.setBuffer(null);
              }
              if (this.filterExceptions) {
                  getDeviceLink().close(inVC, null);
              } else {
                  getDeviceLink().close(inVC, e);
              }
          }
      }