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;
    }
  }
 private void processSyn(SessionFrameListener listener, IStream stream, SynStreamFrame frame) {
   stream.process(frame);
   // Update the last stream id before calling the application (which may send a GO_AWAY)
   updateLastStreamId(stream);
   StreamFrameListener streamListener;
   if (stream.isUnidirectional()) {
     PushInfo pushInfo = new PushInfo(frame.getHeaders(), frame.isClose());
     streamListener =
         notifyOnPush(stream.getAssociatedStream().getStreamFrameListener(), stream, pushInfo);
   } else {
     SynInfo synInfo = new SynInfo(frame.getHeaders(), frame.isClose(), frame.getPriority());
     streamListener = notifyOnSyn(listener, stream, synInfo);
   }
   stream.setStreamFrameListener(streamListener);
   flush();
   // The onSyn() listener may have sent a frame that closed the stream
   if (stream.isClosed()) removeStream(stream);
 }