public int read(byte[] inputBuffer, int inputBufferAvail, int bytesToRead) throws IOException {
        synchronized(this) {
            // If the thread hasn't finished opening the socket yet, wait for it.
            while(inputStream == null && error == null && !closed) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    throw new ClosedByInterruptException();
                }
            }

            // If we're closed, error may contain a "socket closed" exception from when
            // we cancelled the connection, which should be ignored.
            if(closed)
                throw new IOException("Read from a closed connection");

            // If a previous request failed, fail all future ones as well.
            if(error != null) {
                // We don't want to lose the stack trace here, but we also want to
                // retain the stack trace where the original error happened, so nest
                // this exception even though it's the same exception type.
                IOException e = new IOException();
                e.initCause(error);
                throw error;
            }
        }

        return inputStream.read(inputBuffer, inputBufferAvail, bytesToRead);
    }