docs: private <T extends StreamWithTimeout> T createStream(
  			final SupplierWithException<T, IOException> streamOpener,
  			final HashSet<T> openStreams,
  			final boolean output) throws IOException {
  
  		final int outputLimit = output && maxNumOpenOutputStreams > 0 ? maxNumOpenOutputStreams : Integer.MAX_VALUE;
  		final int inputLimit = !output && maxNumOpenInputStreams > 0 ? maxNumOpenInputStreams : Integer.MAX_VALUE;
  		final int totalLimit = maxNumOpenStreamsTotal > 0 ? maxNumOpenStreamsTotal : Integer.MAX_VALUE;
  		final int outputCredit = output ? 1 : 0;
  		final int inputCredit = output ? 0 : 1;
  
  		// because waiting for availability may take long, we need to be interruptible here
  		// and handle interrupted exceptions as I/O errors
  		// even though the code is written to make sure the lock is held for a short time only,
  		// making the lock acquisition interruptible helps to guard against the cases where
  		// a supposedly fast operation (like 'getPos()' on a stream) actually takes long.
  		try {
  			lock.lockInterruptibly();
  			try {
  				// some integrity checks
  				assert openOutputStreams.size() <= numReservedOutputStreams;
  				assert openInputStreams.size() <= numReservedInputStreams;
  
  				// wait until there are few enough streams so we can open another
  				waitForAvailability(totalLimit, outputLimit, inputLimit);
  
  				// We do not open the stream here in the locked scope because opening a stream
  				// could take a while. Holding the lock during that operation would block all concurrent
  				// attempts to try and open a stream, effectively serializing all calls to open the streams.
  				numReservedOutputStreams += outputCredit;
  				numReservedInputStreams += inputCredit;
  			}
  			finally {
  				lock.unlock();
  			}
  		}
  		catch (InterruptedException e) {
  			// restore interruption flag
  			Thread.currentThread().interrupt();
  			throw new IOException("interrupted before opening stream");
  		}
  
  		// open the stream outside the lock.
  		boolean success = false;
  		try {
  			final T out = streamOpener.get();
  
  			// add the stream to the set, need to re-acquire the lock
  			lock.lock();
  			try {
  				openStreams.add(out);
  			} finally {
  				lock.unlock();
  			}
  
  			// good, can now return cleanly
  			success = true;
  			return out;
  		}
  		finally {
  			if (!success) {
  				// remove the reserved credit
  				// we must open this non-interruptibly, because this must succeed!
  				lock.lock();
  				try {
  					numReservedOutputStreams -= outputCredit;
  					numReservedInputStreams -= inputCredit;
  					available.signalAll();
  				} finally {
  					lock.unlock();
  				}
  			}
  		}
  	}