private IStream createStream(
      SynStreamFrame frame, StreamFrameListener listener, boolean local, Promise<Stream> promise) {
    IStream associatedStream = streams.get(frame.getAssociatedStreamId());
    IStream stream =
        new StandardStream(
            frame.getStreamId(), frame.getPriority(), this, associatedStream, promise);
    flowControlStrategy.onNewStream(this, stream);

    stream.updateCloseState(frame.isClose(), local);
    stream.setStreamFrameListener(listener);

    if (stream.isUnidirectional()) {
      // Unidirectional streams are implicitly half closed
      stream.updateCloseState(true, !local);
      if (!stream.isClosed()) stream.getAssociatedStream().associate(stream);
    }

    int streamId = stream.getId();

    if (local) {
      while (true) {
        int oldStreamCountValue = localStreamCount.get();
        int maxConcurrentStreams = maxConcurrentLocalStreams;
        if (maxConcurrentStreams > -1 && oldStreamCountValue >= maxConcurrentStreams) {
          String message =
              String.format("Max concurrent local streams (%d) exceeded.", maxConcurrentStreams);
          LOG.debug(message);
          promise.failed(new SPDYException(message));
          return null;
        }
        if (localStreamCount.compareAndSet(oldStreamCountValue, oldStreamCountValue + 1)) break;
      }
    }

    if (streams.putIfAbsent(streamId, stream) != null) {
      String message = "Duplicate stream id " + streamId;
      IllegalStateException duplicateIdException = new IllegalStateException(message);
      promise.failed(duplicateIdException);
      if (local) {
        localStreamCount.decrementAndGet();
        throw duplicateIdException;
      }
      RstInfo rstInfo = new RstInfo(streamId, StreamStatus.PROTOCOL_ERROR);
      LOG.debug("Duplicate stream, {}", rstInfo);
      rst(rstInfo, new Callback.Adapter()); // We don't care (too much) if the reset fails.
      return null;
    } else {
      LOG.debug("Created {}", stream);
      notifyStreamCreated(stream);
      return stream;
    }
  }
 @Override
 public void complete() {
   bufferPool.release(buffer);
   IStream stream = getStream();
   dataInfo.consume(size);
   flowControlStrategy.updateWindow(StandardSession.this, stream, -size);
   if (dataInfo.available() > 0) {
     // We have written a frame out of this DataInfo, but there is more to write.
     // We need to keep the correct ordering of frames, to avoid that another
     // DataInfo for the same stream is written before this one is finished.
     prepend(this);
     flush();
   } else {
     super.complete();
     stream.updateCloseState(dataInfo.isClose(), true);
     if (stream.isClosed()) removeStream(stream);
   }
 }