docs: @Override
      public VirtualConnection read(long numBytes, TCPReadCompletedCallback readCallback, boolean forceQueue, int timeout) {
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.entry(tc, "read(" + numBytes + ",..," + forceQueue + "," + timeout + ")");
          }
          getTCPConnLink().incrementNumReads();
          if (getConfig().getDumpStatsInterval() > 0) {
              getTCPConnLink().getTCPChannel().totalAsyncReads.incrementAndGet();
          }
          // always check external call parms
          checkForErrors(numBytes, true, timeout);
          if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
              Socket socket = getTCPConnLink().getSocketIOChannel().getSocket();
              Tr.event(tc, "read (async) requested for local: " + socket.getLocalSocketAddress() + " remote: " + socket.getRemoteSocketAddress());
          }
  
          VirtualConnection vc = null;
  
          if (isAborted()) {
              if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
                  Tr.event(tc, "Previously aborted, unable to perform read");
              }
              // async errors can only be passed through the callback, which means
              // if they didn't provide one then we have no way of reporting the
              // failure
              if (null != readCallback) {
                  IOException ioe = new IOException("Connection aborted by program");
                  readCallback.error(getTCPConnLink().getVirtualConnection(), this, ioe);
              }
          } else {
              vc = readInternal(numBytes, readCallback, forceQueue, timeout);
          }
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.exit(tc, "read: " + vc);
          }
          return vc;
      }