signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
111 lines • 4.27 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BackpressureManager = void 0;
exports.parseBackpressureThresholds = parseBackpressureThresholds;
const LatestValuesAccumulator_1 = require("./LatestValuesAccumulator");
const debug_1 = require("./debug");
const debug = (0, debug_1.createDebug)('signalk-server:backpressure');
const DEFAULT_ENTER_THRESHOLD = 512 * 1024;
const DEFAULT_EXIT_THRESHOLD = 1024;
const DEFAULT_MAX_BUFFER_SIZE = 4 * 512 * 1024;
const DEFAULT_MAX_BUFFER_CHECK_TIME = 30 * 1000;
function parseBackpressureThresholds(configFallbacks) {
return {
enterThreshold: process.env.BACKPRESSURE_ENTER
? parseInt(process.env.BACKPRESSURE_ENTER, 10)
: DEFAULT_ENTER_THRESHOLD,
exitThreshold: process.env.BACKPRESSURE_EXIT
? parseInt(process.env.BACKPRESSURE_EXIT, 10)
: DEFAULT_EXIT_THRESHOLD,
maxBufferSize: process.env.MAXSENDBUFFERSIZE
? parseInt(process.env.MAXSENDBUFFERSIZE, 10)
: (configFallbacks?.maxSendBufferSize ?? DEFAULT_MAX_BUFFER_SIZE),
maxBufferCheckTime: process.env.MAXSENDBUFFERCHECKTIME
? parseInt(process.env.MAXSENDBUFFERCHECKTIME, 10)
: (configFallbacks?.maxSendBufferCheckTime ??
DEFAULT_MAX_BUFFER_CHECK_TIME)
};
}
class BackpressureManager {
active = false;
accumulator = new Map();
since = null;
bufferSizeExceeded = undefined;
transport;
options;
constructor(transport, options) {
this.transport = transport;
this.options = options;
}
onDrain() {
if (this.active && this.accumulator.size > 0) {
if (this.transport.getBufferLength() <= this.options.exitThreshold) {
this.flush();
}
}
}
send(delta) {
const bufferLength = this.transport.getBufferLength();
if (this.active || bufferLength > this.options.enterThreshold) {
if (!this.active) {
this.active = true;
this.since = Date.now();
debug('Entering backpressure for %s (buffer: %d)', this.transport.id, bufferLength);
}
(0, LatestValuesAccumulator_1.accumulateLatestValue)(this.accumulator, delta);
}
else {
this.options.beforeWrite?.(delta);
this.transport.write(delta);
}
this.assertBufferSize(bufferLength);
}
flush() {
if (this.accumulator.size === 0)
return;
const countBefore = this.accumulator.size;
const duration = this.since ? Date.now() - this.since : 0;
const deltas = (0, LatestValuesAccumulator_1.buildFlushDeltas)(this.accumulator, duration);
for (const delta of deltas) {
this.options.beforeWrite?.(delta);
this.transport.write(delta);
}
this.accumulator.clear();
this.active = false;
this.since = null;
debug('Flushed %d accumulated values for %s', countBefore, this.transport.id);
}
assertBufferSize(knownBufferLength) {
if (this.options.maxBufferSize === 0)
return;
const bufferLength = knownBufferLength ?? this.transport.getBufferLength();
if (bufferLength > this.options.maxBufferSize) {
if (!this.bufferSizeExceeded) {
console.warn(`${this.transport.id} outgoing buffer > max:${bufferLength}`);
this.bufferSizeExceeded = Date.now();
}
if (Date.now() - this.bufferSizeExceeded >
this.options.maxBufferCheckTime) {
console.error('Send buffer overflow, terminating connection ' + this.transport.id);
this.transport.destroy();
}
}
else {
this.bufferSizeExceeded = undefined;
}
}
clear() {
this.accumulator.clear();
this.active = false;
this.since = null;
this.bufferSizeExceeded = undefined;
}
get isActive() {
return this.active;
}
get accumulatorSize() {
return this.accumulator.size;
}
}
exports.BackpressureManager = BackpressureManager;
//# sourceMappingURL=BackpressureManager.js.map