259 lines
9.6 KiB
JavaScript
259 lines
9.6 KiB
JavaScript
"use strict";
|
|
/*
|
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
* license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright
|
|
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
|
*/
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const tslib_1 = require("tslib");
|
|
const BaseConnectionPool_1 = tslib_1.__importDefault(require("./BaseConnectionPool"));
|
|
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
const debug_1 = tslib_1.__importDefault(require("debug"));
|
|
const connection_1 = require("../connection");
|
|
const debug = (0, debug_1.default)('elasticsearch');
|
|
class ClusterConnectionPool extends BaseConnectionPool_1.default {
|
|
constructor(opts) {
|
|
var _a, _b;
|
|
super(opts);
|
|
Object.defineProperty(this, "dead", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "resurrectTimeout", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "resurrectTimeoutCutoff", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "pingTimeout", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "resurrectStrategy", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
this.dead = [];
|
|
// the resurrect timeout is 60s
|
|
this.resurrectTimeout = 1000 * 60;
|
|
// number of consecutive failures after which
|
|
// the timeout doesn't increase
|
|
this.resurrectTimeoutCutoff = 5;
|
|
this.pingTimeout = (_a = opts.pingTimeout) !== null && _a !== void 0 ? _a : 3000;
|
|
const resurrectStrategy = (_b = opts.resurrectStrategy) !== null && _b !== void 0 ? _b : 'ping';
|
|
this.resurrectStrategy = ClusterConnectionPool.resurrectStrategies[resurrectStrategy];
|
|
(0, assert_1.default)(this.resurrectStrategy != null, `Invalid resurrection strategy: '${resurrectStrategy}'`);
|
|
}
|
|
/**
|
|
* Marks a connection as 'alive'.
|
|
* If needed removes the connection from the dead list
|
|
* and then resets the `deadCount`.
|
|
*
|
|
* @param {object} connection
|
|
*/
|
|
markAlive(connection) {
|
|
const { id } = connection;
|
|
debug(`Marking as 'alive' connection '${id}'`);
|
|
const index = this.dead.indexOf(id);
|
|
if (index > -1)
|
|
this.dead.splice(index, 1);
|
|
connection.status = connection_1.BaseConnection.statuses.ALIVE;
|
|
connection.deadCount = 0;
|
|
connection.resurrectTimeout = 0;
|
|
return this;
|
|
}
|
|
/**
|
|
* Marks a connection as 'dead'.
|
|
* If needed adds the connection to the dead list
|
|
* and then increments the `deadCount`.
|
|
*
|
|
* @param {object} connection
|
|
*/
|
|
markDead(connection) {
|
|
const { id } = connection;
|
|
debug(`Marking as 'dead' connection '${id}'`);
|
|
if (!this.dead.includes(id)) {
|
|
// It might happen that `markDead` is called jsut after
|
|
// a pool update, and in such case we will add to the dead
|
|
// list a node that no longer exist. The following check verify
|
|
// that the connection is still part of the pool before
|
|
// marking it as dead.
|
|
for (let i = 0; i < this.size; i++) {
|
|
if (this.connections[i].id === id) {
|
|
this.dead.push(id);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
connection.status = connection_1.BaseConnection.statuses.DEAD;
|
|
connection.deadCount++;
|
|
// resurrectTimeout formula:
|
|
// `resurrectTimeout * 2 ** min(deadCount - 1, resurrectTimeoutCutoff)`
|
|
connection.resurrectTimeout = Date.now() + this.resurrectTimeout * Math.pow(2, Math.min(connection.deadCount - 1, this.resurrectTimeoutCutoff));
|
|
// sort the dead list in ascending order
|
|
// based on the resurrectTimeout
|
|
this.dead.sort((a, b) => {
|
|
const conn1 = this.connections.find(c => c.id === a);
|
|
const conn2 = this.connections.find(c => c.id === b);
|
|
return conn1.resurrectTimeout - conn2.resurrectTimeout;
|
|
});
|
|
return this;
|
|
}
|
|
/**
|
|
* If enabled, tries to resurrect a connection with the given
|
|
* resurrect strategy ('ping', 'optimistic', 'none').
|
|
*
|
|
* @param {object} { now, requestId }
|
|
*/
|
|
resurrect(opts) {
|
|
if (this.resurrectStrategy === 0 || this.dead.length === 0) {
|
|
debug('Nothing to resurrect');
|
|
return;
|
|
}
|
|
// the dead list is sorted in ascending order based on the timeout
|
|
// so the first element will always be the one with the smaller timeout
|
|
const connection = this.connections.find(c => c.id === this.dead[0]);
|
|
if (opts.now < connection.resurrectTimeout) {
|
|
debug('Nothing to resurrect');
|
|
return;
|
|
}
|
|
const { id } = connection;
|
|
// ping strategy
|
|
if (this.resurrectStrategy === 1) {
|
|
connection.request({ method: 'HEAD', path: '/' }, { timeout: this.pingTimeout, requestId: opts.requestId, name: opts.name, context: opts.context })
|
|
.then(({ statusCode }) => {
|
|
let isAlive = true;
|
|
if (statusCode === 502 || statusCode === 503 || statusCode === 504) {
|
|
debug(`Resurrect: connection '${id}' is still dead`);
|
|
this.markDead(connection);
|
|
isAlive = false;
|
|
}
|
|
else {
|
|
debug(`Resurrect: connection '${id}' is now alive`);
|
|
this.markAlive(connection);
|
|
}
|
|
this.diagnostic.emit('resurrect', null, {
|
|
strategy: 'ping',
|
|
name: opts.name,
|
|
request: { id: opts.requestId },
|
|
isAlive,
|
|
connection
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
this.markDead(connection);
|
|
this.diagnostic.emit('resurrect', err, {
|
|
strategy: 'ping',
|
|
name: opts.name,
|
|
request: { id: opts.requestId },
|
|
isAlive: false,
|
|
connection
|
|
});
|
|
});
|
|
// optimistic strategy
|
|
}
|
|
else {
|
|
debug(`Resurrect: optimistic resurrection for connection '${id}'`);
|
|
this.dead.splice(this.dead.indexOf(id), 1);
|
|
connection.status = connection_1.BaseConnection.statuses.ALIVE;
|
|
this.diagnostic.emit('resurrect', null, {
|
|
strategy: 'optimistic',
|
|
name: opts.name,
|
|
request: { id: opts.requestId },
|
|
isAlive: true,
|
|
connection
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Returns an alive connection if present,
|
|
* otherwise returns a dead connection.
|
|
* By default it filters the `master` only nodes.
|
|
* It uses the selector to choose which
|
|
* connection return.
|
|
*
|
|
* @param {object} options (filter and selector)
|
|
* @returns {object|null} connection
|
|
*/
|
|
getConnection(opts) {
|
|
const filter = opts.filter != null ? opts.filter : () => true;
|
|
const selector = opts.selector != null ? opts.selector : (c) => c[0];
|
|
this.resurrect({
|
|
now: opts.now,
|
|
requestId: opts.requestId,
|
|
name: opts.name,
|
|
context: opts.context
|
|
});
|
|
const noAliveConnections = this.size === this.dead.length;
|
|
// TODO: can we cache this?
|
|
const connections = [];
|
|
for (let i = 0; i < this.size; i++) {
|
|
const connection = this.connections[i];
|
|
if (noAliveConnections || connection.status === connection_1.BaseConnection.statuses.ALIVE) {
|
|
if (filter(connection)) {
|
|
connections.push(connection);
|
|
}
|
|
}
|
|
}
|
|
if (connections.length === 0)
|
|
return null;
|
|
return selector(connections);
|
|
}
|
|
/**
|
|
* Empties the connection pool.
|
|
*
|
|
* @returns {ConnectionPool}
|
|
*/
|
|
async empty() {
|
|
await super.empty();
|
|
this.dead = [];
|
|
}
|
|
/**
|
|
* Update the ConnectionPool with new connections.
|
|
*
|
|
* @param {array} array of connections
|
|
* @returns {ConnectionPool}
|
|
*/
|
|
update(connections) {
|
|
super.update(connections);
|
|
this.dead = [];
|
|
return this;
|
|
}
|
|
}
|
|
exports.default = ClusterConnectionPool;
|
|
Object.defineProperty(ClusterConnectionPool, "resurrectStrategies", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: {
|
|
none: 0,
|
|
ping: 1,
|
|
optimistic: 2
|
|
}
|
|
});
|
|
//# sourceMappingURL=ClusterConnectionPool.js.map
|