/* * Copyright (c) 2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eclipse.jetty.spdy; import java.nio.ByteBuffer; import java.nio.channels.InterruptedByTimeoutException; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.spdy.api.DataInfo; import org.eclipse.jetty.spdy.api.GoAwayInfo; import org.eclipse.jetty.spdy.api.Handler; import org.eclipse.jetty.spdy.api.PingInfo; import org.eclipse.jetty.spdy.api.RstInfo; import org.eclipse.jetty.spdy.api.SPDYException; import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.api.SessionFrameListener; import org.eclipse.jetty.spdy.api.SessionStatus; import org.eclipse.jetty.spdy.api.Settings; import org.eclipse.jetty.spdy.api.SettingsInfo; import org.eclipse.jetty.spdy.api.Stream; import org.eclipse.jetty.spdy.api.StreamFrameListener; import org.eclipse.jetty.spdy.api.StreamStatus; import org.eclipse.jetty.spdy.api.SynInfo; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.ControlFrameType; import org.eclipse.jetty.spdy.frames.DataFrame; import org.eclipse.jetty.spdy.frames.GoAwayFrame; import org.eclipse.jetty.spdy.frames.HeadersFrame; import org.eclipse.jetty.spdy.frames.PingFrame; import org.eclipse.jetty.spdy.frames.RstStreamFrame; import org.eclipse.jetty.spdy.frames.SettingsFrame; import org.eclipse.jetty.spdy.frames.SynReplyFrame; import org.eclipse.jetty.spdy.frames.SynStreamFrame; import org.eclipse.jetty.spdy.frames.WindowUpdateFrame; import org.eclipse.jetty.spdy.generator.Generator; import org.eclipse.jetty.spdy.parser.Parser; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; public class StandardSession implements ISession, Parser.Listener, Handler<StandardSession.FrameBytes> { private static final Logger logger = Log.getLogger(Session.class); private static final ThreadLocal<Integer> handlerInvocations = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; private final List<Listener> listeners = new CopyOnWriteArrayList<>(); private final ConcurrentMap<Integer, IStream> streams = new ConcurrentHashMap<>(); private final LinkedList<FrameBytes> queue = new LinkedList<>(); private final ByteBufferPool bufferPool; private final Executor threadPool; private final ScheduledExecutorService scheduler; private final short version; private final Controller<FrameBytes> controller; private final IdleListener idleListener; private final AtomicInteger streamIds; private final AtomicInteger pingIds; private final SessionFrameListener listener; private final Generator generator; private final AtomicBoolean goAwaySent = new AtomicBoolean(); private final AtomicBoolean goAwayReceived = new AtomicBoolean(); private final AtomicInteger lastStreamId = new AtomicInteger(); private boolean flushing; private volatile int windowSize = 65536; public StandardSession(short version, ByteBufferPool bufferPool, Executor threadPool, ScheduledExecutorService scheduler, Controller<FrameBytes> controller, IdleListener idleListener, int initialStreamId, SessionFrameListener listener, Generator generator) { this.version = version; this.bufferPool = bufferPool; this.threadPool = threadPool; this.scheduler = scheduler; this.controller = controller; this.idleListener = idleListener; this.streamIds = new AtomicInteger(initialStreamId); this.pingIds = new AtomicInteger(initialStreamId); this.listener = listener; this.generator = generator; } @Override public short getVersion() { return version; } @Override public void addListener(Listener listener) { listeners.add(listener); } @Override public void removeListener(Listener listener) { listeners.remove(listener); } @Override public Future<Stream> syn(SynInfo synInfo, StreamFrameListener listener) { Promise<Stream> result = new Promise<>(); syn(synInfo,listener,0,TimeUnit.MILLISECONDS,result); return result; } @Override public void syn(SynInfo synInfo, StreamFrameListener listener, long timeout, TimeUnit unit, Handler<Stream> handler) { // Synchronization is necessary. // SPEC v3, 2.3.1 requires that the stream creation be monotonically crescent // so we cannot allow thread1 to create stream1 and thread2 create stream3 and // have stream3 hit the network before stream1, not only to comply with the spec // but also because the compression context for the headers would be wrong, as the // frame with a compression history will come before the first compressed frame. int associatedStreamId = 0; if (synInfo instanceof PushSynInfo) { associatedStreamId = ((PushSynInfo)synInfo).getAssociatedStreamId(); } synchronized (this) { int streamId = streamIds.getAndAdd(2); SynStreamFrame synStream = new SynStreamFrame(version,synInfo.getFlags(),streamId,associatedStreamId,synInfo.getPriority(),synInfo.getHeaders()); IStream stream = createStream(synStream,listener); control(stream,synStream,timeout,unit,handler,stream); } } @Override public Future<Void> rst(RstInfo rstInfo) { Promise<Void> result = new Promise<>(); rst(rstInfo,0,TimeUnit.MILLISECONDS,result); return result; } @Override public void rst(RstInfo rstInfo, long timeout, TimeUnit unit, Handler<Void> handler) { // SPEC v3, 2.2.2 if (goAwaySent.get()) { complete(handler,null); } else { int streamId = rstInfo.getStreamId(); IStream stream = streams.get(streamId); RstStreamFrame frame = new RstStreamFrame(version,streamId,rstInfo.getStreamStatus().getCode(version)); control(stream,frame,timeout,unit,handler,null); if (stream != null) { stream.process(frame); removeStream(stream); } } } @Override public Future<Void> settings(SettingsInfo settingsInfo) { Promise<Void> result = new Promise<>(); settings(settingsInfo,0,TimeUnit.MILLISECONDS,result); return result; } @Override public void settings(SettingsInfo settingsInfo, long timeout, TimeUnit unit, Handler<Void> handler) { SettingsFrame frame = new SettingsFrame(version,settingsInfo.getFlags(),settingsInfo.getSettings()); control(null,frame,timeout,unit,handler,null); } @Override public Future<PingInfo> ping() { Promise<PingInfo> result = new Promise<>(); ping(0,TimeUnit.MILLISECONDS,result); return result; } @Override public void ping(long timeout, TimeUnit unit, Handler<PingInfo> handler) { int pingId = pingIds.getAndAdd(2); PingInfo pingInfo = new PingInfo(pingId); PingFrame frame = new PingFrame(version,pingId); control(null,frame,timeout,unit,handler,pingInfo); } @Override public Future<Void> goAway() { return goAway(SessionStatus.OK); } private Future<Void> goAway(SessionStatus sessionStatus) { Promise<Void> result = new Promise<>(); goAway(sessionStatus,0,TimeUnit.MILLISECONDS,result); return result; } @Override public void goAway(long timeout, TimeUnit unit, Handler<Void> handler) { goAway(SessionStatus.OK,timeout,unit,handler); } private void goAway(SessionStatus sessionStatus, long timeout, TimeUnit unit, Handler<Void> handler) { if (goAwaySent.compareAndSet(false,true)) { if (!goAwayReceived.get()) { GoAwayFrame frame = new GoAwayFrame(version,lastStreamId.get(),sessionStatus.getCode()); control(null,frame,timeout,unit,handler,null); return; } } complete(handler,null); } @Override public Set<Stream> getStreams() { Set<Stream> result = new HashSet<>(); result.addAll(streams.values()); return result; } @Override public void onControlFrame(ControlFrame frame) { notifyIdle(idleListener,false); try { logger.debug("Processing {}",frame); if (goAwaySent.get()) { logger.debug("Skipped processing of {}",frame); return; } switch (frame.getType()) { case SYN_STREAM: { onSyn((SynStreamFrame)frame); break; } case SYN_REPLY: { onReply((SynReplyFrame)frame); break; } case RST_STREAM: { onRst((RstStreamFrame)frame); break; } case SETTINGS: { onSettings((SettingsFrame)frame); break; } case NOOP: { // Just ignore it break; } case PING: { onPing((PingFrame)frame); break; } case GO_AWAY: { onGoAway((GoAwayFrame)frame); break; } case HEADERS: { onHeaders((HeadersFrame)frame); break; } case WINDOW_UPDATE: { onWindowUpdate((WindowUpdateFrame)frame); break; } default: { throw new IllegalStateException(); } } } finally { notifyIdle(idleListener,true); } } @Override public void onDataFrame(DataFrame frame, ByteBuffer data) { notifyIdle(idleListener,false); try { logger.debug("Processing {}, {} data bytes",frame,data.remaining()); if (goAwaySent.get()) { logger.debug("Skipped processing of {}",frame); return; } int streamId = frame.getStreamId(); IStream stream = streams.get(streamId); if (stream == null) { RstInfo rstInfo = new RstInfo(streamId,StreamStatus.INVALID_STREAM); logger.debug("Unknown stream {}",rstInfo); rst(rstInfo); } else { processData(stream,frame,data); } } finally { notifyIdle(idleListener,true); } } private void notifyIdle(IdleListener listener, boolean idle) { if (listener != null) listener.onIdle(idle); } private void processData(IStream stream, DataFrame frame, ByteBuffer data) { stream.process(frame,data); updateLastStreamId(stream); if (stream.isClosed()) removeStream(stream); } @Override public void onStreamException(StreamException x) { notifyOnException(listener,x); rst(new RstInfo(x.getStreamId(),x.getStreamStatus())); } @Override public void onSessionException(SessionException x) { Throwable cause = x.getCause(); notifyOnException(listener,cause == null?x:cause); goAway(x.getSessionStatus()); } private void onSyn(SynStreamFrame frame) { IStream stream = newStream(frame,null); stream.updateCloseState(frame.isClose(),false); logger.debug("Opening {}",stream); int streamId = stream.getId(); IStream existing = streams.putIfAbsent(streamId,stream); if (existing != null) { RstInfo rstInfo = new RstInfo(streamId,StreamStatus.PROTOCOL_ERROR); logger.debug("Duplicate stream, {}",rstInfo); rst(rstInfo); } else { processSyn(listener,stream,frame); } } private void processSyn(SessionFrameListener listener, IStream stream, SynStreamFrame frame) { stream.process(frame); SynInfo synInfo = new SynInfo(frame.getHeaders(),frame.isClose(),frame.getPriority()); StreamFrameListener 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); } private IStream createStream(SynStreamFrame synStream, StreamFrameListener listener) { IStream parentStream = streams.get(synStream.getAssociatedStreamId()); IStream stream = newStream(synStream,parentStream); stream.updateCloseState(synStream.isClose(),true); stream.setStreamFrameListener(listener); if (synStream.isUnidirectional()) { // unidirectional streams are implicitly half closed for the client stream.updateCloseState(true,false); if (!stream.isClosed()) parentStream.associate(stream); } if (streams.putIfAbsent(synStream.getStreamId(),stream) != null) { // If this happens we have a bug since we did not check that the peer's streamId was valid // (if we're on server, then the client sent an odd streamId and we did not check that) throw new IllegalStateException("StreamId: " + synStream.getStreamId() + " invalid."); } logger.debug("Created {}",stream); notifyStreamCreated(stream); return stream; } private IStream newStream(SynStreamFrame frame, IStream parentStream) { return new StandardStream(frame,this,windowSize,parentStream); } private void notifyStreamCreated(IStream stream) { for (Listener listener : listeners) { if (listener instanceof StreamListener) { try { ((StreamListener)listener).onStreamCreated(stream); } catch (Exception x) { logger.info("Exception while notifying listener " + listener,x); } } } } private void removeStream(IStream stream) { if (stream.isUnidirectional()) { stream.getAssociatedStream().disassociate(stream); } IStream removed = streams.remove(stream.getId()); if (removed != null) assert removed == stream; logger.debug("Removed {}",stream); notifyStreamClosed(stream); } private void notifyStreamClosed(IStream stream) { for (Listener listener : listeners) { if (listener instanceof StreamListener) { try { ((StreamListener)listener).onStreamClosed(stream); } catch (Exception x) { logger.info("Exception while notifying listener " + listener,x); } } } } private void onReply(SynReplyFrame frame) { int streamId = frame.getStreamId(); IStream stream = streams.get(streamId); if (stream == null) { RstInfo rstInfo = new RstInfo(streamId,StreamStatus.INVALID_STREAM); logger.debug("Unknown stream {}",rstInfo); rst(rstInfo); } else { processReply(stream,frame); } } private void processReply(IStream stream, SynReplyFrame frame) { stream.process(frame); if (stream.isClosed()) removeStream(stream); } private void onRst(RstStreamFrame frame) { IStream stream = streams.get(frame.getStreamId()); if (stream != null) stream.process(frame); RstInfo rstInfo = new RstInfo(frame.getStreamId(),StreamStatus.from(frame.getVersion(),frame.getStatusCode())); notifyOnRst(listener,rstInfo); flush(); if (stream != null) removeStream(stream); } private void onSettings(SettingsFrame frame) { Settings.Setting windowSizeSetting = frame.getSettings().get(Settings.ID.INITIAL_WINDOW_SIZE); if (windowSizeSetting != null) { int prevWindowSize = windowSize; windowSize = windowSizeSetting.value(); for (IStream stream : streams.values()) stream.updateWindowSize(windowSize - prevWindowSize); logger.debug("Updated window size to {}",windowSize); } SettingsInfo settingsInfo = new SettingsInfo(frame.getSettings(),frame.isClearPersisted()); notifyOnSettings(listener,settingsInfo); flush(); } private void onPing(PingFrame frame) { int pingId = frame.getPingId(); if (pingId % 2 == pingIds.get() % 2) { PingInfo pingInfo = new PingInfo(frame.getPingId()); notifyOnPing(listener,pingInfo); flush(); } else { control(null,frame,0,TimeUnit.MILLISECONDS,null,null); } } private void onGoAway(GoAwayFrame frame) { if (goAwayReceived.compareAndSet(false,true)) { GoAwayInfo goAwayInfo = new GoAwayInfo(frame.getLastStreamId(),SessionStatus.from(frame.getStatusCode())); notifyOnGoAway(listener,goAwayInfo); flush(); // SPDY does not require to send back a response to a GO_AWAY. // We notified the application of the last good stream id, // tried our best to flush remaining data, and close. close(); } } private void onHeaders(HeadersFrame frame) { int streamId = frame.getStreamId(); IStream stream = streams.get(streamId); if (stream == null) { RstInfo rstInfo = new RstInfo(streamId,StreamStatus.INVALID_STREAM); logger.debug("Unknown stream, {}",rstInfo); rst(rstInfo); } else { processHeaders(stream,frame); } } private void processHeaders(IStream stream, HeadersFrame frame) { stream.process(frame); if (stream.isClosed()) removeStream(stream); } private void onWindowUpdate(WindowUpdateFrame frame) { int streamId = frame.getStreamId(); IStream stream = streams.get(streamId); if (stream != null) stream.process(frame); } protected void close() { // Check for null to support tests if (controller != null) controller.close(false); } private void notifyOnException(SessionFrameListener listener, Throwable x) { try { if (listener != null) { logger.debug("Invoking callback with {} on listener {}",x,listener); listener.onException(x); } } catch (Exception xx) { logger.info("Exception while notifying listener " + listener,xx); } } private StreamFrameListener notifyOnSyn(SessionFrameListener listener, Stream stream, SynInfo synInfo) { try { if (listener != null) { logger.debug("Invoking callback with {} on listener {}",synInfo,listener); return listener.onSyn(stream,synInfo); } } catch (Exception x) { logger.info("Exception while notifying listener " + listener,x); } return null; } private void notifyOnRst(SessionFrameListener listener, RstInfo rstInfo) { try { if (listener != null) { logger.debug("Invoking callback with {} on listener {}",rstInfo,listener); listener.onRst(this,rstInfo); } } catch (Exception x) { logger.info("Exception while notifying listener " + listener,x); } } private void notifyOnSettings(SessionFrameListener listener, SettingsInfo settingsInfo) { try { if (listener != null) { logger.debug("Invoking callback with {} on listener {}",settingsInfo,listener); listener.onSettings(this,settingsInfo); } } catch (Exception x) { logger.info("Exception while notifying listener " + listener,x); } } private void notifyOnPing(SessionFrameListener listener, PingInfo pingInfo) { try { if (listener != null) { logger.debug("Invoking callback with {} on listener {}",pingInfo,listener); listener.onPing(this,pingInfo); } } catch (Exception x) { logger.info("Exception while notifying listener " + listener,x); } } private void notifyOnGoAway(SessionFrameListener listener, GoAwayInfo goAwayInfo) { try { if (listener != null) { logger.debug("Invoking callback with {} on listener {}",goAwayInfo,listener); listener.onGoAway(this,goAwayInfo); } } catch (Exception x) { logger.info("Exception while notifying listener " + listener,x); } } @Override public <C> void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Handler<C> handler, C context) { try { if (stream != null) { updateLastStreamId(stream); if (stream.isClosed()) removeStream(stream); } // Synchronization is necessary, since we may have concurrent replies // and those needs to be generated and enqueued atomically in order // to maintain a correct compression context synchronized (this) { ByteBuffer buffer = generator.control(frame); logger.debug("Queuing {} on {}",frame,stream); ControlFrameBytes<C> frameBytes = new ControlFrameBytes<>(stream,handler,context,frame,buffer); if (timeout > 0) frameBytes.task = scheduler.schedule(frameBytes,timeout,unit); // Special handling for PING frames, they must be sent as soon as possible if (ControlFrameType.PING == frame.getType()) prepend(frameBytes); else append(frameBytes); } flush(); } catch (Throwable x) { notifyHandlerFailed(handler,x); } } private void updateLastStreamId(IStream stream) { int streamId = stream.getId(); if (stream.isClosed() && streamId % 2 != streamIds.get() % 2) { // Non-blocking atomic update int oldValue = lastStreamId.get(); while (streamId > oldValue) { if (lastStreamId.compareAndSet(oldValue,streamId)) break; oldValue = lastStreamId.get(); } } } @Override public <C> void data(IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Handler<C> handler, C context) { logger.debug("Queuing {} on {}",dataInfo,stream); DataFrameBytes<C> frameBytes = new DataFrameBytes<>(stream,handler,context,dataInfo); if (timeout > 0) { frameBytes.task = scheduler.schedule(frameBytes,timeout,unit); } append(frameBytes); flush(); } private void execute(Runnable task) { threadPool.execute(task); } @Override public void flush() { FrameBytes frameBytes = null; ByteBuffer buffer = null; synchronized (queue) { if (flushing || queue.isEmpty()) return; Set<IStream> stalledStreams = null; for (int i = 0; i < queue.size(); ++i) { frameBytes = queue.get(i); IStream stream = frameBytes.getStream(); if (stream != null && stalledStreams != null && stalledStreams.contains(stream)) continue; buffer = frameBytes.getByteBuffer(); if (buffer != null) { queue.remove(i); // TODO: stream.isUniDirectional() check here is only needed for pushStreams which send a syn with close=true --> find a better solution if (stream != null && !streams.containsValue(stream) && !stream.isUnidirectional()) frameBytes.fail(new StreamException(stream.getId(),StreamStatus.INVALID_STREAM)); break; } if (stalledStreams == null) stalledStreams = new HashSet<>(); if (stream != null) stalledStreams.add(stream); logger.debug("Flush stalled for {}, {} frame(s) in queue",frameBytes,queue.size()); } if (buffer == null) return; flushing = true; logger.debug("Flushing {}, {} frame(s) in queue",frameBytes,queue.size()); } write(buffer,this,frameBytes); } private void append(FrameBytes frameBytes) { synchronized (queue) { int index = queue.size(); while (index > 0) { FrameBytes element = queue.get(index - 1); if (element.compareTo(frameBytes) >= 0) break; --index; } queue.add(index,frameBytes); } } private void prepend(FrameBytes frameBytes) { synchronized (queue) { int index = 0; while (index < queue.size()) { FrameBytes element = queue.get(index); if (element.compareTo(frameBytes) <= 0) break; ++index; } queue.add(index,frameBytes); } } @Override public void completed(FrameBytes frameBytes) { synchronized (queue) { logger.debug("Completed write of {}, {} frame(s) in queue",frameBytes,queue.size()); flushing = false; } frameBytes.complete(); } @Override public void failed(Throwable x) { throw new SPDYException(x); } protected void write(ByteBuffer buffer, Handler<FrameBytes> handler, FrameBytes frameBytes) { if (controller != null) { logger.debug("Writing {} frame bytes of {}",buffer.remaining(),frameBytes); controller.write(buffer,handler,frameBytes); } } private <C> void complete(final Handler<C> handler, final C context) { // Applications may send and queue up a lot of frames and // if we call Handler.completed() only synchronously we risk // starvation (for the last frames sent) and stack overflow. // Therefore every some invocation, we dispatch to a new thread Integer invocations = handlerInvocations.get(); if (invocations >= 4) { execute(new Runnable() { @Override public void run() { if (handler != null) notifyHandlerCompleted(handler,context); flush(); } }); } else { handlerInvocations.set(invocations + 1); try { if (handler != null) notifyHandlerCompleted(handler,context); flush(); } finally { handlerInvocations.set(invocations); } } } private <C> void notifyHandlerCompleted(Handler<C> handler, C context) { try { handler.completed(context); } catch (Exception x) { logger.info("Exception while notifying handler " + handler,x); } } private <C> void notifyHandlerFailed(Handler<C> handler, Throwable x) { try { if (handler != null) handler.failed(x); } catch (Exception xx) { logger.info("Exception while notifying handler " + handler,xx); } } public interface FrameBytes extends Comparable<FrameBytes> { public IStream getStream(); public abstract ByteBuffer getByteBuffer(); public abstract void complete(); public abstract void fail(Throwable throwable); } private abstract class AbstractFrameBytes<C> implements FrameBytes, Runnable { private final IStream stream; private final Handler<C> handler; private final C context; protected volatile ScheduledFuture<?> task; protected AbstractFrameBytes(IStream stream, Handler<C> handler, C context) { this.stream = stream; this.handler = handler; this.context = context; } @Override public IStream getStream() { return stream; } @Override public int compareTo(FrameBytes that) { // If this.stream.priority > that.stream.priority => -1 (this.stream has less priority than that.stream) return that.getStream().getPriority() - getStream().getPriority(); } @Override public void complete() { cancelTask(); StandardSession.this.complete(handler,context); } @Override public void fail(Throwable x) { cancelTask(); notifyHandlerFailed(handler,x); } private void cancelTask() { ScheduledFuture<?> task = this.task; if (task != null) task.cancel(false); } @Override public void run() { close(); fail(new InterruptedByTimeoutException()); } } private class ControlFrameBytes<C> extends AbstractFrameBytes<C> { private final ControlFrame frame; private final ByteBuffer buffer; private ControlFrameBytes(IStream stream, Handler<C> handler, C context, ControlFrame frame, ByteBuffer buffer) { super(stream,handler,context); this.frame = frame; this.buffer = buffer; } @Override public ByteBuffer getByteBuffer() { return buffer; } @Override public void complete() { bufferPool.release(buffer); super.complete(); if (frame.getType() == ControlFrameType.GO_AWAY) { // After sending a GO_AWAY we need to hard close the connection. // Recipients will know the last good stream id and act accordingly. close(); } } @Override public String toString() { return frame.toString(); } } private class DataFrameBytes<C> extends AbstractFrameBytes<C> { private final DataInfo dataInfo; private int size; private volatile ByteBuffer buffer; private DataFrameBytes(IStream stream, Handler<C> handler, C context, DataInfo dataInfo) { super(stream,handler,context); this.dataInfo = dataInfo; } @Override public ByteBuffer getByteBuffer() { try { IStream stream = getStream(); int windowSize = stream.getWindowSize(); if (windowSize <= 0) return null; size = dataInfo.available(); if (size > windowSize) size = windowSize; buffer = generator.data(stream.getId(),size,dataInfo); return buffer; } catch (Throwable x) { fail(x); return null; } } @Override public void complete() { bufferPool.release(buffer); IStream stream = getStream(); stream.updateWindowSize(-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); } else { super.complete(); stream.updateCloseState(dataInfo.isClose(),true); if (stream.isClosed()) removeStream(stream); } } @Override public String toString() { return String.format("DATA bytes @%x available=%d consumed=%d on %s",dataInfo.hashCode(),dataInfo.available(),dataInfo.consumed(),getStream()); } } }