1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const ArrayUtil_1 = require("../Utility/ArrayUtil");
|
4 | const moment = require("moment");
|
5 | const CurrentIndexAndNode_1 = require("../Http/CurrentIndexAndNode");
|
6 | const Timer_1 = require("../Primitives/Timer");
|
7 | const Exceptions_1 = require("../Exceptions");
|
8 | class NodeSelectorState {
|
9 | constructor(topology) {
|
10 | this.speedTestMode = 1;
|
11 | this.topology = topology;
|
12 | this.nodes = topology.nodes;
|
13 | this.failures = ArrayUtil_1.ArrayUtil.range(topology.nodes.length, () => 0);
|
14 | this.fastestRecords = ArrayUtil_1.ArrayUtil.range(topology.nodes.length, () => 0);
|
15 | }
|
16 | }
|
17 | class NodeSelector {
|
18 | constructor(topology) {
|
19 | this._state = new NodeSelectorState(topology);
|
20 | }
|
21 | getTopology() {
|
22 | return this._state.topology;
|
23 | }
|
24 | onFailedRequest(nodeIndex) {
|
25 | const state = this._state;
|
26 | if (nodeIndex < 0 || nodeIndex >= state.failures.length) {
|
27 | return;
|
28 | }
|
29 | state.failures[nodeIndex]++;
|
30 | }
|
31 | onUpdateTopology(topology, forceUpdate = false) {
|
32 | if (!topology) {
|
33 | return false;
|
34 | }
|
35 | const stateEtag = this._state.topology.etag || 0;
|
36 | const topologyEtag = this._state.topology.etag || 0;
|
37 | if (stateEtag >= topologyEtag && !forceUpdate) {
|
38 | return false;
|
39 | }
|
40 | this._state = new NodeSelectorState(topology);
|
41 | return true;
|
42 | }
|
43 | getNodeBySessionId(sessionId) {
|
44 | const state = this._state;
|
45 | const index = sessionId % state.topology.nodes.length;
|
46 | for (let i = index; i < state.failures.length; i++) {
|
47 | if (state.failures[i] === 0
|
48 | && state.nodes[i].serverRole === "Member") {
|
49 | return new CurrentIndexAndNode_1.default(i, state.nodes[i]);
|
50 | }
|
51 | }
|
52 | for (let i = 0; i < index; i++) {
|
53 | if (state.failures[i] === 0
|
54 | && state.nodes[i].serverRole === "Member") {
|
55 | return new CurrentIndexAndNode_1.default(i, state.nodes[i]);
|
56 | }
|
57 | }
|
58 | return this.getPreferredNode();
|
59 | }
|
60 | getPreferredNode() {
|
61 | const state = this._state;
|
62 | const stateFailures = state.failures;
|
63 | const serverNodes = state.nodes;
|
64 | const len = Math.min(serverNodes.length, stateFailures.length);
|
65 | for (let i = 0; i < len; i++) {
|
66 | if (stateFailures[i] === 0 && serverNodes[i].url) {
|
67 | return new CurrentIndexAndNode_1.default(i, serverNodes[i]);
|
68 | }
|
69 | }
|
70 | return NodeSelector._unlikelyEveryoneFaultedChoice(state);
|
71 | }
|
72 | static _unlikelyEveryoneFaultedChoice(state) {
|
73 | if (state.nodes.length === 0) {
|
74 | Exceptions_1.throwError("AllTopologyNodesDownException", "There are no nodes in the topology at all.");
|
75 | }
|
76 | return new CurrentIndexAndNode_1.default(0, state.nodes[0]);
|
77 | }
|
78 | getFastestNode() {
|
79 | const state = this._state;
|
80 | if (state.failures[state.fastest] === 0
|
81 | && state.nodes[state.fastest].serverRole === "Member") {
|
82 | return new CurrentIndexAndNode_1.default(state.fastest, state.nodes[state.fastest]);
|
83 | }
|
84 | this._switchToSpeedTestPhase();
|
85 | return this.getPreferredNode();
|
86 | }
|
87 | restoreNodeIndex(nodeIndex) {
|
88 | const state = this._state;
|
89 | if (state.failures.length < nodeIndex) {
|
90 | return;
|
91 | }
|
92 | state.failures[nodeIndex] = 0;
|
93 | }
|
94 | _throwEmptyTopology() {
|
95 | Exceptions_1.throwError("InvalidOperationException", "Empty database topology, this shouldn't happen.");
|
96 | }
|
97 | _switchToSpeedTestPhase() {
|
98 | const state = this._state;
|
99 | if (state.speedTestMode === 0) {
|
100 | state.speedTestMode = 1;
|
101 | }
|
102 | else {
|
103 | return;
|
104 | }
|
105 | state.fastestRecords.fill(0);
|
106 | state.speedTestMode++;
|
107 | }
|
108 | inSpeedTestPhase() {
|
109 | return this._state.speedTestMode > 1;
|
110 | }
|
111 | recordFastest(index, node) {
|
112 | const state = this._state;
|
113 | const stateFastest = state.fastestRecords;
|
114 | if (index < 0 || index >= stateFastest.length) {
|
115 | return;
|
116 | }
|
117 | if (node !== state.nodes[index]) {
|
118 | return;
|
119 | }
|
120 | if (++stateFastest[index] >= 10) {
|
121 | this._selectFastest(state, index);
|
122 | }
|
123 | if (++state.speedTestMode <= state.nodes.length * 10) {
|
124 | return;
|
125 | }
|
126 | const maxIndex = NodeSelector._findMaxIndex(state);
|
127 | this._selectFastest(state, maxIndex);
|
128 | }
|
129 | static _findMaxIndex(state) {
|
130 | const stateFastest = state.fastestRecords;
|
131 | let maxIndex = 0;
|
132 | let maxValue = 0;
|
133 | for (let i = 0; i < stateFastest.length; i++) {
|
134 | if (maxValue >= stateFastest[i]) {
|
135 | continue;
|
136 | }
|
137 | maxIndex = i;
|
138 | maxValue = stateFastest[i];
|
139 | }
|
140 | return maxIndex;
|
141 | }
|
142 | _selectFastest(state, index) {
|
143 | state.fastest = index;
|
144 | state.speedTestMode = 0;
|
145 | const minuteMs = moment.duration(1, "m").asMilliseconds();
|
146 | if (this._updateFastestNodeTimer !== null) {
|
147 | this._updateFastestNodeTimer.change(minuteMs);
|
148 | }
|
149 | else {
|
150 | this._updateFastestNodeTimer = new Timer_1.Timer(() => {
|
151 | this._switchToSpeedTestPhase();
|
152 | return Promise.resolve();
|
153 | }, minuteMs);
|
154 | }
|
155 | }
|
156 | scheduleSpeedTest() {
|
157 | this._switchToSpeedTestPhase();
|
158 | }
|
159 | }
|
160 | exports.NodeSelector = NodeSelector;
|