spice-couchbase 2.0.63 → 2.0.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/connectionHealth.js +114 -149
- package/build/connectionHealth.test.js +301 -316
- package/package.json +1 -1
- package/src/connectionHealth.js +48 -7
- package/src/connectionHealth.test.js +53 -4
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
var
|
|
3
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
4
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
5
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
6
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
7
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
8
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
9
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
10
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
11
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
12
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
13
|
+
function _toPrimitive(t, e) { if ("object" != typeof t || !t) return t; var r; if ("undefined" != typeof Symbol && void 0 !== (r = t[Symbol.toPrimitive])) { var i = r.call(t, e || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === e ? String : Number)(t); }
|
|
14
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
15
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
16
|
+
var couchbase = require("couchbase");
|
|
17
|
+
|
|
18
|
+
// Connection health monitor and self-healing reconnect.
|
|
14
19
|
//
|
|
15
20
|
// The cluster object is created once at boot and stored on the `spice.database`
|
|
16
21
|
// globals. When the SDK wedges (every operation times out, but the SDK never
|
|
@@ -28,18 +33,14 @@ var couchbase = require("couchbase"); // Connection health monitor and self-heal
|
|
|
28
33
|
// 4. Escalation: if the connection stays unhealthy for too long, terminate
|
|
29
34
|
// the process so the supervisor (PM2) restarts it.
|
|
30
35
|
|
|
31
|
-
|
|
32
36
|
function envNumber(name, fallback) {
|
|
33
37
|
var raw = process.env[name];
|
|
34
|
-
|
|
35
38
|
if (raw === undefined || raw === null || String(raw).trim() === "") {
|
|
36
39
|
return fallback;
|
|
37
40
|
}
|
|
38
|
-
|
|
39
41
|
var value = Number(raw);
|
|
40
42
|
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
41
43
|
}
|
|
42
|
-
|
|
43
44
|
function defaultSettings() {
|
|
44
45
|
return {
|
|
45
46
|
// 0 disables the interval probe (the passive signal still works).
|
|
@@ -59,94 +60,77 @@ function defaultSettings() {
|
|
|
59
60
|
exitAfterMs: envNumber("SPICE_COUCHBASE_EXIT_AFTER_MS", 300000)
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
|
-
|
|
63
63
|
var CONNECTIVITY_ERRORS = [couchbase.TimeoutError, couchbase.RequestCanceledError, couchbase.ServiceNotAvailableError].filter(type => typeof type === "function");
|
|
64
|
-
|
|
65
64
|
function isConnectivityError(error) {
|
|
66
65
|
if (!error || typeof error !== "object") return false;
|
|
67
|
-
if (CONNECTIVITY_ERRORS.some(type => error instanceof type)) return true;
|
|
68
|
-
|
|
66
|
+
if (CONNECTIVITY_ERRORS.some(type => error instanceof type)) return true;
|
|
67
|
+
// Our own deadlines, and SDK errors that crossed a module boundary.
|
|
69
68
|
return error.spiceConnectivity === true || ["TimeoutError", "AmbiguousTimeoutError", "UnambiguousTimeoutError", "RequestCanceledError", "ServiceNotAvailableError"].includes(error.name);
|
|
70
69
|
}
|
|
71
|
-
|
|
72
70
|
function deadline(promise, ms, label) {
|
|
73
71
|
var timer;
|
|
74
72
|
return Promise.race([promise, new Promise((_resolve, reject) => {
|
|
75
73
|
var _timer$unref, _timer;
|
|
76
|
-
|
|
77
74
|
timer = setTimeout(() => {
|
|
78
75
|
var error = new Error("".concat(label, " timed out after ").concat(ms, "ms"));
|
|
79
76
|
error.spiceConnectivity = true;
|
|
80
77
|
reject(error);
|
|
81
78
|
}, ms);
|
|
82
|
-
(_timer$unref = (_timer = timer).unref) === null || _timer$unref === void 0
|
|
79
|
+
(_timer$unref = (_timer = timer).unref) === null || _timer$unref === void 0 || _timer$unref.call(_timer);
|
|
83
80
|
})]).finally(() => clearTimeout(timer));
|
|
84
81
|
}
|
|
85
|
-
|
|
86
82
|
function describe(error) {
|
|
87
83
|
return (error === null || error === void 0 ? void 0 : error.message) || String(error);
|
|
88
84
|
}
|
|
89
|
-
|
|
90
85
|
function closeQuietly(_x, _x2, _x3) {
|
|
91
86
|
return _closeQuietly.apply(this, arguments);
|
|
92
87
|
}
|
|
93
|
-
|
|
94
88
|
function _closeQuietly() {
|
|
95
89
|
_closeQuietly = _asyncToGenerator(function* (cluster, log, label) {
|
|
96
90
|
try {
|
|
97
91
|
var _cluster$close;
|
|
98
|
-
|
|
99
|
-
yield cluster === null || cluster === void 0 ? void 0 : (_cluster$close = cluster.close) === null || _cluster$close === void 0 ? void 0 : _cluster$close.call(cluster);
|
|
92
|
+
yield cluster === null || cluster === void 0 || (_cluster$close = cluster.close) === null || _cluster$close === void 0 ? void 0 : _cluster$close.call(cluster);
|
|
100
93
|
} catch (error) {
|
|
101
94
|
log.warn("[spice-couchbase] Closing ".concat(label, " failed: ").concat(describe(error)));
|
|
102
95
|
}
|
|
103
96
|
});
|
|
104
97
|
return _closeQuietly.apply(this, arguments);
|
|
105
98
|
}
|
|
106
|
-
|
|
107
99
|
function buildConnectOptions() {
|
|
108
100
|
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
109
101
|
var options = {
|
|
110
102
|
username: config.username,
|
|
111
103
|
password: config.password
|
|
112
104
|
};
|
|
113
|
-
|
|
114
105
|
if (config.configProfile) {
|
|
115
106
|
options.configProfile = config.configProfile;
|
|
116
107
|
}
|
|
117
|
-
|
|
118
108
|
var timeouts = {};
|
|
119
|
-
|
|
120
109
|
for (var key of ["kvTimeout", "kvDurableTimeout", "queryTimeout", "searchTimeout", "analyticsTimeout", "managementTimeout", "connectTimeout", "bootstrapTimeout", "resolveTimeout"]) {
|
|
121
110
|
var _config$timeouts;
|
|
122
|
-
|
|
123
111
|
var raw = (_config$timeouts = config.timeouts) === null || _config$timeouts === void 0 ? void 0 : _config$timeouts[key];
|
|
124
112
|
if (raw === undefined || raw === null || raw === "") continue;
|
|
125
113
|
var value = Number(raw);
|
|
126
114
|
if (Number.isFinite(value) && value > 0) timeouts[key] = value;
|
|
127
115
|
}
|
|
128
|
-
|
|
129
116
|
if (Object.keys(timeouts).length > 0) {
|
|
130
117
|
options.timeouts = timeouts;
|
|
131
118
|
}
|
|
132
|
-
|
|
133
119
|
return options;
|
|
134
120
|
}
|
|
121
|
+
|
|
135
122
|
/**
|
|
136
123
|
* Opens a cluster and resolves the bucket, scope and collection handles for a
|
|
137
124
|
* connection config. Shared by the boot-time connect and the reconnect path.
|
|
138
125
|
*/
|
|
139
|
-
|
|
140
|
-
|
|
141
126
|
function openHandles(_x4) {
|
|
142
127
|
return _openHandles.apply(this, arguments);
|
|
143
128
|
}
|
|
144
|
-
|
|
145
129
|
function _openHandles() {
|
|
146
130
|
_openHandles = _asyncToGenerator(function* (config) {
|
|
147
|
-
var {
|
|
148
|
-
connect =
|
|
149
|
-
|
|
131
|
+
var _ref6 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
132
|
+
_ref6$connect = _ref6.connect,
|
|
133
|
+
connect = _ref6$connect === void 0 ? couchbase.connect : _ref6$connect;
|
|
150
134
|
var cluster = yield connect(config.nodes, buildConnectOptions(config));
|
|
151
135
|
var bucket = cluster.bucket(config.bucket);
|
|
152
136
|
var scope = bucket.scope(config.scope || "_default");
|
|
@@ -160,38 +144,36 @@ function _openHandles() {
|
|
|
160
144
|
});
|
|
161
145
|
return _openHandles.apply(this, arguments);
|
|
162
146
|
}
|
|
163
|
-
|
|
164
147
|
function createHealthMonitor() {
|
|
165
|
-
var {
|
|
166
|
-
settings
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
148
|
+
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
149
|
+
_ref$settings = _ref.settings,
|
|
150
|
+
overrides = _ref$settings === void 0 ? {} : _ref$settings,
|
|
151
|
+
_ref$connect = _ref.connect,
|
|
152
|
+
connect = _ref$connect === void 0 ? couchbase.connect : _ref$connect,
|
|
153
|
+
_ref$now = _ref.now,
|
|
154
|
+
now = _ref$now === void 0 ? () => Date.now() : _ref$now,
|
|
155
|
+
_ref$random = _ref.random,
|
|
156
|
+
random = _ref$random === void 0 ? Math.random : _ref$random,
|
|
157
|
+
_ref$log = _ref.log,
|
|
158
|
+
log = _ref$log === void 0 ? console : _ref$log,
|
|
159
|
+
_ref$getGlobals = _ref.getGlobals,
|
|
160
|
+
getGlobals = _ref$getGlobals === void 0 ? () => typeof spice !== "undefined" ? spice : undefined : _ref$getGlobals,
|
|
161
|
+
terminate = _ref.terminate;
|
|
162
|
+
var settings = _objectSpread(_objectSpread({}, defaultSettings()), overrides);
|
|
177
163
|
var states = new Map();
|
|
178
164
|
var stopped = false;
|
|
179
|
-
|
|
180
165
|
var terminateProcess = terminate || (() => {
|
|
181
166
|
var _fallback$unref;
|
|
182
|
-
|
|
183
167
|
// SIGTERM runs the application's graceful shutdown when it has one,
|
|
184
168
|
// and terminates the process when it does not. The fallback covers a
|
|
185
169
|
// shutdown that hangs on the very connection that is stuck.
|
|
186
170
|
process.exitCode = 1;
|
|
187
171
|
var fallback = setTimeout(() => process.exit(1), 15000);
|
|
188
|
-
(_fallback$unref = fallback.unref) === null || _fallback$unref === void 0
|
|
172
|
+
(_fallback$unref = fallback.unref) === null || _fallback$unref === void 0 || _fallback$unref.call(fallback);
|
|
189
173
|
process.kill(process.pid, "SIGTERM");
|
|
190
174
|
});
|
|
191
|
-
|
|
192
175
|
function stateFor(name) {
|
|
193
176
|
var state = states.get(name);
|
|
194
|
-
|
|
195
177
|
if (!state) {
|
|
196
178
|
state = {
|
|
197
179
|
name,
|
|
@@ -213,13 +195,10 @@ function createHealthMonitor() {
|
|
|
213
195
|
};
|
|
214
196
|
states.set(name, state);
|
|
215
197
|
}
|
|
216
|
-
|
|
217
198
|
return state;
|
|
218
199
|
}
|
|
219
|
-
|
|
220
200
|
function currentHandles(name) {
|
|
221
201
|
var _getGlobals, _database$clusters, _database$buckets, _database$scopes, _database$connections;
|
|
222
|
-
|
|
223
202
|
var database = (_getGlobals = getGlobals()) === null || _getGlobals === void 0 ? void 0 : _getGlobals.database;
|
|
224
203
|
if (!database) return null;
|
|
225
204
|
return {
|
|
@@ -230,118 +209,138 @@ function createHealthMonitor() {
|
|
|
230
209
|
};
|
|
231
210
|
}
|
|
232
211
|
|
|
233
|
-
|
|
212
|
+
// KV is checked with a bucket ping, not a document operation: the boot-time
|
|
213
|
+
// collection handle can name a collection that does not exist (a named
|
|
214
|
+
// scope has no `_default` collection), and KV operations against it retry
|
|
215
|
+
// until they time out even though every real request succeeds.
|
|
216
|
+
function probeKv(_x5, _x6) {
|
|
217
|
+
return _probeKv.apply(this, arguments);
|
|
218
|
+
}
|
|
219
|
+
function _probeKv() {
|
|
220
|
+
_probeKv = _asyncToGenerator(function* (bucket, timeout) {
|
|
221
|
+
var _report;
|
|
222
|
+
var report;
|
|
223
|
+
try {
|
|
224
|
+
report = yield bucket.ping({
|
|
225
|
+
serviceTypes: [couchbase.ServiceType.KeyValue],
|
|
226
|
+
timeout
|
|
227
|
+
});
|
|
228
|
+
} catch (error) {
|
|
229
|
+
throw labelled(error, "KV ping");
|
|
230
|
+
}
|
|
231
|
+
var endpoints = Object.values(((_report = report) === null || _report === void 0 ? void 0 : _report.services) || {}).flat();
|
|
232
|
+
// One node answering is enough. A single node being down is the cluster's
|
|
233
|
+
// problem, and a fresh connection would not fix it.
|
|
234
|
+
if (!endpoints.some(endpoint => (endpoint === null || endpoint === void 0 ? void 0 : endpoint.state) === couchbase.PingState.Ok)) {
|
|
235
|
+
var _states = endpoints.map(endpoint => {
|
|
236
|
+
var _couchbase$PingState$;
|
|
237
|
+
return (_couchbase$PingState$ = couchbase.PingState[endpoint === null || endpoint === void 0 ? void 0 : endpoint.state]) !== null && _couchbase$PingState$ !== void 0 ? _couchbase$PingState$ : endpoint === null || endpoint === void 0 ? void 0 : endpoint.state;
|
|
238
|
+
}).join(", ");
|
|
239
|
+
throw labelled(new Error("no endpoint responded (".concat(_states || "no endpoints", ")")), "KV ping");
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
return _probeKv.apply(this, arguments);
|
|
243
|
+
}
|
|
244
|
+
function labelled(error, label) {
|
|
245
|
+
var wrapped = new Error("".concat(label, " failed: ").concat(describe(error)));
|
|
246
|
+
wrapped.cause = error;
|
|
247
|
+
wrapped.spiceConnectivity = true;
|
|
248
|
+
return wrapped;
|
|
249
|
+
}
|
|
250
|
+
function probe(_x7) {
|
|
234
251
|
return _probe.apply(this, arguments);
|
|
235
252
|
}
|
|
236
|
-
|
|
237
253
|
function _probe() {
|
|
238
254
|
_probe = _asyncToGenerator(function* (handles) {
|
|
239
|
-
if (!(handles
|
|
255
|
+
if (!(handles !== null && handles !== void 0 && handles.cluster) || !(handles !== null && handles !== void 0 && handles.bucket)) {
|
|
240
256
|
var error = new Error("No cluster handles to probe");
|
|
241
257
|
error.spiceConnectivity = true;
|
|
242
258
|
throw error;
|
|
243
259
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
yield deadline(Promise.all([handles.collection.exists("__spice_health_probe__", {
|
|
248
|
-
timeout
|
|
249
|
-
}), handles.cluster.query("SELECT 1", {
|
|
260
|
+
var timeout = settings.probeTimeoutMs;
|
|
261
|
+
// The hard deadline is the point: a wedged SDK may never honour its own.
|
|
262
|
+
yield deadline(Promise.all([probeKv(handles.bucket, timeout), handles.cluster.query("SELECT 1", {
|
|
250
263
|
timeout
|
|
264
|
+
}).catch(error => {
|
|
265
|
+
throw labelled(error, "Query probe");
|
|
251
266
|
})]), timeout + 1000, "Couchbase health probe");
|
|
252
267
|
});
|
|
253
268
|
return _probe.apply(this, arguments);
|
|
254
269
|
}
|
|
255
|
-
|
|
256
270
|
function markHealthy(state) {
|
|
257
271
|
state.probeFailures = 0;
|
|
258
272
|
state.failures = [];
|
|
259
273
|
state.reconnectAttempts = 0;
|
|
260
274
|
state.nextReconnectAt = 0;
|
|
261
|
-
|
|
262
275
|
if (!state.healthy) {
|
|
263
276
|
var downFor = state.unhealthySince ? now() - state.unhealthySince : 0;
|
|
264
277
|
log.log("[spice-couchbase] Connection \"".concat(state.name, "\" healthy again ") + "(generation ".concat(state.generation, ", unhealthy for ").concat(downFor, "ms)"));
|
|
265
278
|
}
|
|
266
|
-
|
|
267
279
|
state.healthy = true;
|
|
268
280
|
state.unhealthySince = null;
|
|
269
281
|
state.lastError = null;
|
|
270
282
|
}
|
|
271
|
-
|
|
272
283
|
function markUnhealthy(state, error) {
|
|
273
284
|
state.lastError = describe(error);
|
|
274
|
-
|
|
275
285
|
if (state.healthy) {
|
|
276
286
|
state.healthy = false;
|
|
277
287
|
state.unhealthySince = now();
|
|
278
288
|
log.warn("[spice-couchbase] Connection \"".concat(state.name, "\" unhealthy: ").concat(state.lastError));
|
|
279
289
|
}
|
|
280
290
|
}
|
|
281
|
-
|
|
282
291
|
function escalateIfStuck(state) {
|
|
283
292
|
if (state.terminated || stopped || !settings.exitAfterMs || !state.unhealthySince || now() - state.unhealthySince < settings.exitAfterMs) {
|
|
284
293
|
return;
|
|
285
294
|
}
|
|
286
|
-
|
|
287
295
|
state.terminated = true;
|
|
288
296
|
log.error("[spice-couchbase] Connection \"".concat(state.name, "\" has been unhealthy for ") + "".concat(now() - state.unhealthySince, "ms after ").concat(state.reconnectAttempts, " ") + "failed reconnect(s). Terminating so the supervisor restarts the process.");
|
|
289
297
|
terminateProcess();
|
|
290
298
|
}
|
|
291
|
-
|
|
292
299
|
function retire(state, cluster) {
|
|
293
300
|
var _timer$unref2;
|
|
294
|
-
|
|
295
301
|
if (!cluster) return;
|
|
296
302
|
state.retired.add(cluster);
|
|
297
303
|
var timer = setTimeout(() => {
|
|
298
304
|
state.retired.delete(cluster);
|
|
299
305
|
closeQuietly(cluster, log, "retired cluster for \"".concat(state.name, "\""));
|
|
300
306
|
}, settings.retireGraceMs);
|
|
301
|
-
(_timer$unref2 = timer.unref) === null || _timer$unref2 === void 0
|
|
307
|
+
(_timer$unref2 = timer.unref) === null || _timer$unref2 === void 0 || _timer$unref2.call(timer);
|
|
302
308
|
}
|
|
303
|
-
|
|
304
309
|
function backoffDelay(attempt) {
|
|
305
|
-
var base = Math.min(settings.reconnectBackoffMaxMs, settings.reconnectBackoffMinMs * 2 ** Math.max(0, attempt - 1));
|
|
306
|
-
|
|
310
|
+
var base = Math.min(settings.reconnectBackoffMaxMs, settings.reconnectBackoffMinMs * 2 ** Math.max(0, attempt - 1));
|
|
311
|
+
// +/- 20% jitter so a fleet of pods does not reconnect in lockstep.
|
|
307
312
|
return Math.round(base * (0.8 + random() * 0.4));
|
|
308
313
|
}
|
|
309
|
-
|
|
310
|
-
function doReconnect(_x6) {
|
|
314
|
+
function doReconnect(_x8) {
|
|
311
315
|
return _doReconnect.apply(this, arguments);
|
|
312
316
|
}
|
|
313
|
-
|
|
314
317
|
function _doReconnect() {
|
|
315
318
|
_doReconnect = _asyncToGenerator(function* (state) {
|
|
316
319
|
var _database$clusters2;
|
|
317
|
-
|
|
318
320
|
var started = now();
|
|
319
321
|
var attempt = state.reconnectAttempts + 1;
|
|
320
322
|
log.warn("[spice-couchbase] Reconnecting \"".concat(state.name, "\" (attempt ").concat(attempt, ")"));
|
|
321
323
|
var abandoned = false;
|
|
322
324
|
var handles;
|
|
323
|
-
|
|
324
325
|
try {
|
|
325
326
|
var _handles$bucket;
|
|
326
|
-
|
|
327
327
|
var opening = openHandles(state.config, {
|
|
328
328
|
connect
|
|
329
|
-
});
|
|
330
|
-
|
|
329
|
+
});
|
|
330
|
+
// If the connect outlives the deadline, close whatever it produces.
|
|
331
331
|
opening.then(late => {
|
|
332
332
|
if (abandoned) closeQuietly(late.cluster, log, "abandoned cluster");
|
|
333
333
|
}).catch(() => {});
|
|
334
334
|
handles = yield deadline(opening, settings.reconnectTimeoutMs, "Couchbase reconnect");
|
|
335
|
-
|
|
336
335
|
if (typeof ((_handles$bucket = handles.bucket) === null || _handles$bucket === void 0 ? void 0 : _handles$bucket.waitUntilReady) === "function") {
|
|
337
336
|
var _couchbase$ServiceTyp, _couchbase$ServiceTyp2;
|
|
338
|
-
|
|
339
337
|
var serviceTypes = [(_couchbase$ServiceTyp = couchbase.ServiceType) === null || _couchbase$ServiceTyp === void 0 ? void 0 : _couchbase$ServiceTyp.KeyValue, (_couchbase$ServiceTyp2 = couchbase.ServiceType) === null || _couchbase$ServiceTyp2 === void 0 ? void 0 : _couchbase$ServiceTyp2.Query].filter(Boolean);
|
|
340
338
|
yield deadline(handles.bucket.waitUntilReady(settings.reconnectTimeoutMs, serviceTypes.length ? {
|
|
341
339
|
serviceTypes
|
|
342
|
-
} : undefined)
|
|
340
|
+
} : undefined).catch(error => {
|
|
341
|
+
throw labelled(error, "waitUntilReady");
|
|
342
|
+
}), settings.reconnectTimeoutMs + 1000, "Couchbase waitUntilReady");
|
|
343
343
|
}
|
|
344
|
-
|
|
345
344
|
yield probe(handles);
|
|
346
345
|
} catch (error) {
|
|
347
346
|
abandoned = true;
|
|
@@ -353,31 +352,25 @@ function createHealthMonitor() {
|
|
|
353
352
|
log.error("[spice-couchbase] Reconnect of \"".concat(state.name, "\" failed: ") + "".concat(state.lastError, ". Next attempt in ~").concat(delay, "ms"));
|
|
354
353
|
return false;
|
|
355
354
|
}
|
|
356
|
-
|
|
357
355
|
if (stopped) {
|
|
358
356
|
closeQuietly(handles.cluster, log, "cluster opened during shutdown");
|
|
359
357
|
return false;
|
|
360
358
|
}
|
|
361
|
-
|
|
362
359
|
var globals = getGlobals();
|
|
363
360
|
var database = globals === null || globals === void 0 ? void 0 : globals.database;
|
|
364
|
-
var previous = database === null || database === void 0
|
|
365
|
-
|
|
361
|
+
var previous = database === null || database === void 0 || (_database$clusters2 = database.clusters) === null || _database$clusters2 === void 0 ? void 0 : _database$clusters2[state.name];
|
|
366
362
|
if (database) {
|
|
367
363
|
database.clusters[state.name] = handles.cluster;
|
|
368
364
|
database.buckets[state.name] = handles.bucket;
|
|
369
365
|
database.scopes[state.name] = handles.scope;
|
|
370
366
|
database.connections[state.name] = handles.collection;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
367
|
+
}
|
|
368
|
+
// Collection existence was learnt through the old cluster; re-learn it.
|
|
374
369
|
if (globals) {
|
|
375
|
-
var _globals$db, _globals$db$
|
|
376
|
-
|
|
370
|
+
var _globals$db, _globals$db$clear;
|
|
377
371
|
globals.checked_collections = {};
|
|
378
|
-
(_globals$db = globals.db) === null || _globals$db === void 0
|
|
372
|
+
(_globals$db = globals.db) === null || _globals$db === void 0 || (_globals$db = _globals$db.collections) === null || _globals$db === void 0 || (_globals$db$clear = _globals$db.clear) === null || _globals$db$clear === void 0 || _globals$db$clear.call(_globals$db);
|
|
379
373
|
}
|
|
380
|
-
|
|
381
374
|
state.generation += 1;
|
|
382
375
|
state.lastReconnectAt = now();
|
|
383
376
|
if (previous && previous !== handles.cluster) retire(state, previous);
|
|
@@ -387,11 +380,9 @@ function createHealthMonitor() {
|
|
|
387
380
|
});
|
|
388
381
|
return _doReconnect.apply(this, arguments);
|
|
389
382
|
}
|
|
390
|
-
|
|
391
383
|
function reconnect(name) {
|
|
392
384
|
var state = stateFor(name);
|
|
393
385
|
if (stopped || !state.config) return Promise.resolve(false);
|
|
394
|
-
|
|
395
386
|
if (!state.reconnecting) {
|
|
396
387
|
state.reconnecting = doReconnect(state).catch(error => {
|
|
397
388
|
log.error("[spice-couchbase] Reconnect of \"".concat(name, "\" crashed: ").concat(describe(error)));
|
|
@@ -400,80 +391,66 @@ function createHealthMonitor() {
|
|
|
400
391
|
state.reconnecting = null;
|
|
401
392
|
});
|
|
402
393
|
}
|
|
403
|
-
|
|
404
394
|
return state.reconnecting;
|
|
405
395
|
}
|
|
406
|
-
|
|
407
|
-
function doCheck(_x7) {
|
|
396
|
+
function doCheck(_x9) {
|
|
408
397
|
return _doCheck.apply(this, arguments);
|
|
409
398
|
}
|
|
410
399
|
/** Probes the connection now. Single-flight per connection. */
|
|
411
|
-
|
|
412
|
-
|
|
413
400
|
function _doCheck() {
|
|
414
401
|
_doCheck = _asyncToGenerator(function* (state) {
|
|
415
|
-
var {
|
|
416
|
-
suspected =
|
|
417
|
-
|
|
418
|
-
|
|
402
|
+
var _ref5 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
403
|
+
_ref5$suspected = _ref5.suspected,
|
|
404
|
+
suspected = _ref5$suspected === void 0 ? false : _ref5$suspected;
|
|
419
405
|
try {
|
|
420
406
|
yield probe(currentHandles(state.name));
|
|
421
407
|
markHealthy(state);
|
|
422
408
|
return true;
|
|
423
409
|
} catch (error) {
|
|
424
|
-
state.probeFailures += 1;
|
|
410
|
+
state.probeFailures += 1;
|
|
411
|
+
// A failed probe that follows a burst of operation timeouts is two
|
|
425
412
|
// independent signals, so it does not wait for a second probe.
|
|
426
|
-
|
|
427
413
|
if (suspected || state.probeFailures >= settings.probeFailureThreshold) {
|
|
428
414
|
markUnhealthy(state, error);
|
|
429
415
|
}
|
|
430
|
-
|
|
431
416
|
if (!state.healthy) {
|
|
432
417
|
if (!state.reconnecting && now() >= state.nextReconnectAt) {
|
|
433
418
|
yield reconnect(state.name);
|
|
434
419
|
}
|
|
435
|
-
|
|
436
420
|
escalateIfStuck(state);
|
|
437
421
|
}
|
|
438
|
-
|
|
439
422
|
return false;
|
|
440
423
|
}
|
|
441
424
|
});
|
|
442
425
|
return _doCheck.apply(this, arguments);
|
|
443
426
|
}
|
|
444
|
-
|
|
445
427
|
function check(name, options) {
|
|
446
428
|
var state = stateFor(name);
|
|
447
429
|
if (stopped || !state.config) return Promise.resolve(state.healthy);
|
|
448
430
|
if (state.reconnecting) return state.reconnecting;
|
|
449
|
-
|
|
450
431
|
if (!state.probing) {
|
|
451
432
|
state.probing = doCheck(state, options).finally(() => {
|
|
452
433
|
state.probing = null;
|
|
453
434
|
});
|
|
454
435
|
}
|
|
455
|
-
|
|
456
436
|
return state.probing;
|
|
457
437
|
}
|
|
438
|
+
|
|
458
439
|
/**
|
|
459
440
|
* Called by the driver after an operation. Only connectivity errors count,
|
|
460
441
|
* and only inside the sliding window, so a busy cluster that answers most
|
|
461
442
|
* requests is not torn down for one slow statement.
|
|
462
443
|
*/
|
|
463
|
-
|
|
464
|
-
|
|
465
444
|
function recordOutcome(name, error) {
|
|
466
445
|
if (stopped || !name || !isConnectivityError(error)) return;
|
|
467
446
|
var state = states.get(name);
|
|
468
|
-
if (!(state
|
|
447
|
+
if (!(state !== null && state !== void 0 && state.config)) return;
|
|
469
448
|
var at = now();
|
|
470
449
|
state.failures.push(at);
|
|
471
450
|
var cutoff = at - settings.failureWindowMs;
|
|
472
|
-
|
|
473
451
|
while (state.failures.length && state.failures[0] < cutoff) {
|
|
474
452
|
state.failures.shift();
|
|
475
453
|
}
|
|
476
|
-
|
|
477
454
|
if (state.failures.length >= settings.failureThreshold) {
|
|
478
455
|
state.failures = [];
|
|
479
456
|
check(name, {
|
|
@@ -481,31 +458,28 @@ function createHealthMonitor() {
|
|
|
481
458
|
}).catch(() => {});
|
|
482
459
|
}
|
|
483
460
|
}
|
|
484
|
-
/** Starts monitoring a connection. Called once its first connect succeeds. */
|
|
485
|
-
|
|
486
461
|
|
|
462
|
+
/** Starts monitoring a connection. Called once its first connect succeeds. */
|
|
487
463
|
function register(name, config) {
|
|
488
464
|
var _state$timer$unref, _state$timer;
|
|
489
|
-
|
|
490
465
|
var state = stateFor(name);
|
|
491
466
|
state.config = config;
|
|
492
467
|
if (stopped || state.timer || !settings.healthIntervalMs) return;
|
|
493
468
|
state.timer = setInterval(() => {
|
|
494
469
|
check(name).catch(() => {});
|
|
495
470
|
}, settings.healthIntervalMs);
|
|
496
|
-
(_state$timer$unref = (_state$timer = state.timer).unref) === null || _state$timer$unref === void 0
|
|
471
|
+
(_state$timer$unref = (_state$timer = state.timer).unref) === null || _state$timer$unref === void 0 || _state$timer$unref.call(_state$timer);
|
|
497
472
|
}
|
|
498
|
-
|
|
499
473
|
function generation(name) {
|
|
500
474
|
var _states$get;
|
|
501
|
-
|
|
502
475
|
return ((_states$get = states.get(name)) === null || _states$get === void 0 ? void 0 : _states$get.generation) || 1;
|
|
503
476
|
}
|
|
504
|
-
|
|
505
477
|
function status() {
|
|
506
478
|
var result = {};
|
|
507
|
-
|
|
508
|
-
|
|
479
|
+
for (var _ref4 of states) {
|
|
480
|
+
var _ref3 = _slicedToArray(_ref4, 2);
|
|
481
|
+
var name = _ref3[0];
|
|
482
|
+
var state = _ref3[1];
|
|
509
483
|
result[name] = {
|
|
510
484
|
healthy: state.healthy,
|
|
511
485
|
reconnecting: Boolean(state.reconnecting),
|
|
@@ -518,41 +492,33 @@ function createHealthMonitor() {
|
|
|
518
492
|
lastError: state.lastError
|
|
519
493
|
};
|
|
520
494
|
}
|
|
521
|
-
|
|
522
495
|
return result;
|
|
523
496
|
}
|
|
497
|
+
|
|
524
498
|
/**
|
|
525
499
|
* Stops probing and reconnecting, and closes clusters that were replaced
|
|
526
500
|
* but are still in their grace period. The current clusters stay open: the
|
|
527
501
|
* application closes those through `spice.database.clusters`.
|
|
528
502
|
*/
|
|
529
|
-
|
|
530
|
-
|
|
531
503
|
function stop() {
|
|
532
504
|
return _stop.apply(this, arguments);
|
|
533
505
|
}
|
|
534
|
-
|
|
535
506
|
function _stop() {
|
|
536
507
|
_stop = _asyncToGenerator(function* () {
|
|
537
508
|
stopped = true;
|
|
538
509
|
var closing = [];
|
|
539
|
-
|
|
540
510
|
for (var state of states.values()) {
|
|
541
511
|
if (state.timer) clearInterval(state.timer);
|
|
542
512
|
state.timer = null;
|
|
543
|
-
|
|
544
513
|
for (var cluster of state.retired) {
|
|
545
514
|
closing.push(closeQuietly(cluster, log, "retired cluster for \"".concat(state.name, "\"")));
|
|
546
515
|
}
|
|
547
|
-
|
|
548
516
|
state.retired.clear();
|
|
549
517
|
}
|
|
550
|
-
|
|
551
518
|
yield Promise.all(closing);
|
|
552
519
|
});
|
|
553
520
|
return _stop.apply(this, arguments);
|
|
554
521
|
}
|
|
555
|
-
|
|
556
522
|
return {
|
|
557
523
|
settings,
|
|
558
524
|
register,
|
|
@@ -564,7 +530,6 @@ function createHealthMonitor() {
|
|
|
564
530
|
stop
|
|
565
531
|
};
|
|
566
532
|
}
|
|
567
|
-
|
|
568
533
|
module.exports = {
|
|
569
534
|
createHealthMonitor,
|
|
570
535
|
openHandles,
|