| 'use strict' |
| |
| var util = require('util') |
| var EventEmitter = require('events').EventEmitter |
| var debug = { |
| server: require('debug')('spdy:window:server'), |
| client: require('debug')('spdy:window:client') |
| } |
| |
| function Side (window, name, options) { |
| EventEmitter.call(this) |
| |
| this.name = name |
| this.window = window |
| this.current = options.size |
| this.max = options.size |
| this.limit = options.max |
| this.lowWaterMark = options.lowWaterMark === undefined |
| ? this.max / 2 |
| : options.lowWaterMark |
| |
| this._refilling = false |
| this._refillQueue = [] |
| } |
| util.inherits(Side, EventEmitter) |
| |
| Side.prototype.setMax = function setMax (max) { |
| this.window.debug('id=%d side=%s setMax=%d', |
| this.window.id, |
| this.name, |
| max) |
| this.max = max |
| this.lowWaterMark = this.max / 2 |
| } |
| |
| Side.prototype.updateMax = function updateMax (max) { |
| var delta = max - this.max |
| this.window.debug('id=%d side=%s updateMax=%d delta=%d', |
| this.window.id, |
| this.name, |
| max, |
| delta) |
| |
| this.max = max |
| this.lowWaterMark = max / 2 |
| |
| this.update(delta) |
| } |
| |
| Side.prototype.setLowWaterMark = function setLowWaterMark (lwm) { |
| this.lowWaterMark = lwm |
| } |
| |
| Side.prototype.update = function update (size, callback) { |
| // Not enough space for the update, wait for refill |
| if (size <= 0 && callback && this.isEmpty()) { |
| this.window.debug('id=%d side=%s wait for refill=%d [%d/%d]', |
| this.window.id, |
| this.name, |
| -size, |
| this.current, |
| this.max) |
| this._refillQueue.push({ |
| size: size, |
| callback: callback |
| }) |
| return |
| } |
| |
| this.current += size |
| |
| if (this.current > this.limit) { |
| this.emit('overflow') |
| return |
| } |
| |
| this.window.debug('id=%d side=%s update by=%d [%d/%d]', |
| this.window.id, |
| this.name, |
| size, |
| this.current, |
| this.max) |
| |
| // Time to send WINDOW_UPDATE |
| if (size < 0 && this.isDraining()) { |
| this.window.debug('id=%d side=%s drained', this.window.id, this.name) |
| this.emit('drain') |
| } |
| |
| // Time to write |
| if (size > 0 && this.current > 0 && this.current <= size) { |
| this.window.debug('id=%d side=%s full', this.window.id, this.name) |
| this.emit('full') |
| } |
| |
| this._processRefillQueue() |
| |
| if (callback) { process.nextTick(callback) } |
| } |
| |
| Side.prototype.getCurrent = function getCurrent () { |
| return this.current |
| } |
| |
| Side.prototype.getMax = function getMax () { |
| return this.max |
| } |
| |
| Side.prototype.getDelta = function getDelta () { |
| return this.max - this.current |
| } |
| |
| Side.prototype.isDraining = function isDraining () { |
| return this.current <= this.lowWaterMark |
| } |
| |
| Side.prototype.isEmpty = function isEmpty () { |
| return this.current <= 0 |
| } |
| |
| // Private |
| |
| Side.prototype._processRefillQueue = function _processRefillQueue () { |
| // Prevent recursion |
| if (this._refilling) { |
| return |
| } |
| this._refilling = true |
| |
| while (this._refillQueue.length > 0) { |
| var item = this._refillQueue[0] |
| |
| if (this.isEmpty()) { |
| break |
| } |
| |
| this.window.debug('id=%d side=%s refilled for size=%d', |
| this.window.id, |
| this.name, |
| -item.size) |
| |
| this._refillQueue.shift() |
| this.update(item.size, item.callback) |
| } |
| |
| this._refilling = false |
| } |
| |
| function Window (options) { |
| this.id = options.id |
| this.isServer = options.isServer |
| this.debug = this.isServer ? debug.server : debug.client |
| |
| this.recv = new Side(this, 'recv', options.recv) |
| this.send = new Side(this, 'send', options.send) |
| } |
| module.exports = Window |
| |
| Window.prototype.clone = function clone (id) { |
| return new Window({ |
| id: id, |
| isServer: this.isServer, |
| recv: { |
| size: this.recv.max, |
| max: this.recv.limit, |
| lowWaterMark: this.recv.lowWaterMark |
| }, |
| send: { |
| size: this.send.max, |
| max: this.send.limit, |
| lowWaterMark: this.send.lowWaterMark |
| } |
| }) |
| } |