blob: 909fa2bb8139609ae317a026fd793796f3769f6a [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library http2.src.window_handler;
import '../async_utils/async_utils.dart';
import '../frames/frames.dart';
import '../sync_errors.dart';
import 'window.dart';
abstract class AbstractOutgoingWindowHandler {
/// The connection flow control window.
final Window _peerWindow;
/// Indicates when the outgoing connection window turned positive and we can
/// send data frames again.
final BufferIndicator positiveWindow = new BufferIndicator();
AbstractOutgoingWindowHandler(this._peerWindow) {
if (_peerWindow.size > 0) {
positiveWindow.markUnBuffered();
}
}
/// The flow control window size we use for sending data. We are not allowed
/// to let this window be negative.
int get peerWindowSize => _peerWindow.size;
/// Process a window update frame received from the remote end.
void processWindowUpdate(WindowUpdateFrame frame) {
int increment = frame.windowSizeIncrement;
if ((_peerWindow.size + increment) > Window.MAX_WINDOW_SIZE) {
throw new FlowControlException(
'Window update received from remote peer would make flow control '
'window too large.');
} else {
_peerWindow.modify(increment);
}
// If we transitioned from an negative/empty window to a positive window
// we'll fire an event that more data frames can be sent now.
if (positiveWindow.wouldBuffer && _peerWindow.size > 0) {
positiveWindow.markUnBuffered();
}
}
/// Update the peer window by subtracting [numberOfBytes].
///
/// The remote peer will send us [WindowUpdateFrame]s which will increase
/// the window again at a later point in time.
void decreaseWindow(int numberOfBytes) {
_peerWindow.modify(-numberOfBytes);
if (_peerWindow.size <= 0) {
positiveWindow.markBuffered();
}
}
}
/// Handles the connection window for outgoing data frames.
class OutgoingConnectionWindowHandler extends AbstractOutgoingWindowHandler {
OutgoingConnectionWindowHandler(Window window) : super(window);
}
/// Handles the window for outgoing messages to the peer.
class OutgoingStreamWindowHandler extends AbstractOutgoingWindowHandler {
OutgoingStreamWindowHandler(Window window) : super(window);
/// Update the peer window by adding [difference] to it.
///
///
/// The remote peer has send a new [SettingsFrame] which updated the default
/// stream level [Setting.SETTINGS_INITIAL_WINDOW_SIZE]. This causes all
/// existing streams to update the flow stream-level flow control window.
void processInitialWindowSizeSettingChange(int difference) {
if ((_peerWindow.size + difference) > Window.MAX_WINDOW_SIZE) {
throw new FlowControlException(
'Window update received from remote peer would make flow control '
'window too large.');
} else {
_peerWindow.modify(difference);
if (_peerWindow.size <= 0) {
positiveWindow.markBuffered();
} else if (positiveWindow.wouldBuffer) {
positiveWindow.markUnBuffered();
}
}
}
}
/// Mirrors the flow control window the remote end is using.
class IncomingWindowHandler {
/// The [FrameWriter] used for writing [WindowUpdateFrame]s to the wire.
final FrameWriter _frameWriter;
/// The mirror of the [Window] the remote end sees.
///
/// If [_localWindow ] turns negative, it means the remote peer sent us more
/// data than we allowed it to send.
final Window _localWindow;
/// The stream id this window handler is for (is `0` for connection level).
final int _streamId;
IncomingWindowHandler.stream(
this._frameWriter, this._localWindow, this._streamId);
IncomingWindowHandler.connection(this._frameWriter, this._localWindow)
: _streamId = 0;
/// The current size for the incoming data window.
///
/// (This should never get negative, otherwise the peer send us more data
/// than we told it to send.)
int get localWindowSize => _localWindow.size;
/// Signals that we received [numberOfBytes] from the remote peer.
void gotData(int numberOfBytes) {
_localWindow.modify(-numberOfBytes);
// If this turns negative, it means the remote end send us more data
// then we announced we can handle (i.e. the remote window size must be
// negative).
//
// NOTE: [_localWindow.size] tracks the amount of data we advertised that we
// can handle. The value can change in three situations:
//
// a) We received data from the remote end (we can handle now less data)
// => This is handled by [gotData].
//
// b) We processed data from the remote end (we can handle now more data)
// => This is handled by [dataProcessed].
//
// c) We increase/decrease the initial stream window size after the
// stream was created (newer streams will start with the changed
// initial stream window size).
// => This is not an issue, because we don't support changing the
// initial window size later on -- only during the initial
// settings exchange. Since streams (and therefore instances
// of [IncomingWindowHandler]) are only created after sending out
// our initial settings.
//
if (_localWindow.size < 0) {
throw new FlowControlException(
'Connection level flow control window became negative.');
}
}
/// Tell the peer we received [numberOfBytes] bytes. It will increase it's
/// sending window then.
///
// TODO/FIXME: If we pause and don't want to get more data, we have to
// - either stop sending window update frames
// - or decreasing the window size
void dataProcessed(int numberOfBytes) {
_localWindow.modify(numberOfBytes);
// TODO: This can be optimized by delaying the window update to
// send one update with a bigger difference than multiple small update
// frames.
_frameWriter.writeWindowUpdate(numberOfBytes, streamId: _streamId);
}
}