package com.firefly.codec.http2.stream; import com.firefly.codec.http2.frame.WindowUpdateFrame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; public abstract class AbstractFlowControlStrategy implements FlowControlStrategy { protected static Logger log = LoggerFactory.getLogger("firefly-system"); private final AtomicLong sessionStall = new AtomicLong(); private final AtomicLong sessionStallTime = new AtomicLong(); private final Map<StreamSPI, Long> streamsStalls = new ConcurrentHashMap<>(); private final AtomicLong streamsStallTime = new AtomicLong(); private int initialStreamSendWindow; private int initialStreamRecvWindow; public AbstractFlowControlStrategy(int initialStreamSendWindow) { this.initialStreamSendWindow = initialStreamSendWindow; this.initialStreamRecvWindow = DEFAULT_WINDOW_SIZE; } public int getInitialStreamSendWindow() { return initialStreamSendWindow; } public int getInitialStreamRecvWindow() { return initialStreamRecvWindow; } @Override public void onStreamCreated(StreamSPI stream) { stream.updateSendWindow(initialStreamSendWindow); stream.updateRecvWindow(initialStreamRecvWindow); } @Override public void onStreamDestroyed(StreamSPI stream) { } @Override public void updateInitialStreamWindow(SessionSPI session, int initialStreamWindow, boolean local) { int previousInitialStreamWindow; if (local) { previousInitialStreamWindow = getInitialStreamRecvWindow(); this.initialStreamRecvWindow = initialStreamWindow; } else { previousInitialStreamWindow = getInitialStreamSendWindow(); this.initialStreamSendWindow = initialStreamWindow; } int delta = initialStreamWindow - previousInitialStreamWindow; // SPEC: updates of the initial window size only affect stream windows, // not session's. for (Stream stream : session.getStreams()) { if (local) { ((StreamSPI) stream).updateRecvWindow(delta); if (log.isDebugEnabled()) log.debug("Updated initial stream recv window {} -> {} for {}", previousInitialStreamWindow, initialStreamWindow, stream); } else { session.onWindowUpdate((StreamSPI) stream, new WindowUpdateFrame(stream.getId(), delta)); } } } @Override public void onWindowUpdate(SessionSPI session, StreamSPI stream, WindowUpdateFrame frame) { int delta = frame.getWindowDelta(); if (frame.getStreamId() > 0) { // The stream may have been removed concurrently. if (stream != null) { int oldSize = stream.updateSendWindow(delta); if (log.isDebugEnabled()) log.debug("Updated stream send window {} -> {} for {}", oldSize, oldSize + delta, stream); if (oldSize <= 0) onStreamUnstalled(stream); } } else { int oldSize = session.updateSendWindow(delta); if (log.isDebugEnabled()) log.debug("Updated session send window {} -> {} for {}", oldSize, oldSize + delta, session); if (oldSize <= 0) onSessionUnstalled(session); } } @Override public void onDataReceived(SessionSPI session, StreamSPI stream, int length) { int oldSize = session.updateRecvWindow(-length); if (log.isDebugEnabled()) log.debug("Data received, {} bytes, updated session recv window {} -> {} for {}", length, oldSize, oldSize - length, session); if (stream != null) { oldSize = stream.updateRecvWindow(-length); if (log.isDebugEnabled()) log.debug("Data received, {} bytes, updated stream recv window {} -> {} for {}", length, oldSize, oldSize - length, stream); } } @Override public void windowUpdate(SessionSPI session, StreamSPI stream, WindowUpdateFrame frame) { } @Override public void onDataSending(StreamSPI stream, int length) { if (length == 0) return; SessionSPI session = stream.getSession(); int oldSessionWindow = session.updateSendWindow(-length); int newSessionWindow = oldSessionWindow - length; if (log.isDebugEnabled()) log.debug("Sending, session send window {} -> {} for {}", oldSessionWindow, newSessionWindow, session); if (newSessionWindow <= 0) onSessionStalled(session); int oldStreamWindow = stream.updateSendWindow(-length); int newStreamWindow = oldStreamWindow - length; if (log.isDebugEnabled()) log.debug("Sending, stream send window {} -> {} for {}", oldStreamWindow, newStreamWindow, stream); if (newStreamWindow <= 0) onStreamStalled(stream); } @Override public void onDataSent(StreamSPI stream, int length) { } protected void onSessionStalled(SessionSPI session) { sessionStall.set(System.nanoTime()); if (log.isDebugEnabled()) log.debug("Session stalled {}", session); } protected void onStreamStalled(StreamSPI stream) { streamsStalls.put(stream, System.nanoTime()); if (log.isDebugEnabled()) log.debug("Stream stalled {}", stream); } protected void onSessionUnstalled(SessionSPI session) { sessionStallTime.addAndGet(System.nanoTime() - sessionStall.getAndSet(0)); if (log.isDebugEnabled()) log.debug("Session unstalled {}", session); } protected void onStreamUnstalled(StreamSPI stream) { Long time = streamsStalls.remove(stream); if (time != null) streamsStallTime.addAndGet(System.nanoTime() - time); if (log.isDebugEnabled()) log.debug("Stream unstalled {}", stream); } public long getSessionStallTime() { return TimeUnit.NANOSECONDS.toMillis(sessionStallTime.get()); } public long getStreamsStallTime() { return TimeUnit.NANOSECONDS.toMillis(streamsStallTime.get()); } public void reset() { sessionStallTime.set(0); streamsStallTime.set(0); } }