@tdengine/websocket 3.2.3 → 3.3.0
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/lib/package.json +1 -1
- package/lib/src/client/wsClient.d.ts +23 -7
- package/lib/src/client/wsClient.d.ts.map +1 -1
- package/lib/src/client/wsClient.js +154 -139
- package/lib/src/client/wsConnector.d.ts +55 -9
- package/lib/src/client/wsConnector.d.ts.map +1 -1
- package/lib/src/client/wsConnector.js +519 -100
- package/lib/src/client/wsConnectorPool.d.ts +5 -1
- package/lib/src/client/wsConnectorPool.d.ts.map +1 -1
- package/lib/src/client/wsConnectorPool.js +61 -43
- package/lib/src/client/wsEventCallback.d.ts +3 -0
- package/lib/src/client/wsEventCallback.d.ts.map +1 -1
- package/lib/src/client/wsEventCallback.js +67 -8
- package/lib/src/common/addressConnectionTracker.d.ts +11 -0
- package/lib/src/common/addressConnectionTracker.d.ts.map +1 -0
- package/lib/src/common/addressConnectionTracker.js +53 -0
- package/lib/src/common/dsn.d.ts +14 -2
- package/lib/src/common/dsn.d.ts.map +1 -1
- package/lib/src/common/dsn.js +91 -22
- package/lib/src/common/taosResult.d.ts.map +1 -1
- package/lib/src/common/taosResult.js +0 -5
- package/lib/src/common/urlParser.d.ts +32 -0
- package/lib/src/common/urlParser.d.ts.map +1 -0
- package/lib/src/common/urlParser.js +201 -0
- package/lib/src/common/utils.d.ts +2 -1
- package/lib/src/common/utils.d.ts.map +1 -1
- package/lib/src/common/utils.js +35 -34
- package/lib/src/sql/wsSql.js +2 -2
- package/lib/src/stmt/FieldBindParams.d.ts.map +1 -1
- package/lib/src/stmt/wsColumnInfo.d.ts.map +1 -1
- package/lib/src/stmt/wsParams1.d.ts.map +1 -1
- package/lib/src/stmt/wsParams1.js +26 -26
- package/lib/src/stmt/wsParams2.d.ts.map +1 -1
- package/lib/src/stmt/wsParams2.js +0 -3
- package/lib/src/stmt/wsParamsBase.d.ts.map +1 -1
- package/lib/src/stmt/wsProto.d.ts.map +1 -1
- package/lib/src/stmt/wsProto.js +16 -16
- package/lib/src/stmt/wsStmt1.d.ts.map +1 -1
- package/lib/src/stmt/wsStmt2.d.ts +12 -4
- package/lib/src/stmt/wsStmt2.d.ts.map +1 -1
- package/lib/src/stmt/wsStmt2.js +182 -64
- package/lib/src/stmt/wsTableInfo.d.ts.map +1 -1
- package/lib/src/tmq/config.d.ts +3 -2
- package/lib/src/tmq/config.d.ts.map +1 -1
- package/lib/src/tmq/config.js +15 -15
- package/lib/src/tmq/wsTmq.d.ts +4 -1
- package/lib/src/tmq/wsTmq.d.ts.map +1 -1
- package/lib/src/tmq/wsTmq.js +50 -27
- package/lib/test/bulkPulling/a.test.d.ts +2 -0
- package/lib/test/bulkPulling/a.test.d.ts.map +1 -0
- package/lib/test/bulkPulling/a.test.js +166 -0
- package/lib/test/bulkPulling/dsn.test.js +19 -0
- package/lib/test/bulkPulling/retryConfig.test.js +11 -11
- package/lib/test/bulkPulling/sql.failover.test.d.ts +2 -0
- package/lib/test/bulkPulling/sql.failover.test.d.ts.map +1 -0
- package/lib/test/bulkPulling/sql.failover.test.js +338 -0
- package/lib/test/bulkPulling/stmt2.failover.test.d.ts +2 -0
- package/lib/test/bulkPulling/stmt2.failover.test.d.ts.map +1 -0
- package/lib/test/bulkPulling/stmt2.failover.test.js +313 -0
- package/lib/test/bulkPulling/stmt2.init.failover.test.d.ts +2 -0
- package/lib/test/bulkPulling/stmt2.init.failover.test.d.ts.map +1 -0
- package/lib/test/bulkPulling/stmt2.init.failover.test.js +50 -0
- package/lib/test/bulkPulling/tmq.failover.test.d.ts +2 -0
- package/lib/test/bulkPulling/tmq.failover.test.d.ts.map +1 -0
- package/lib/test/bulkPulling/tmq.failover.test.js +404 -0
- package/lib/test/bulkPulling/urlParser.test.d.ts +2 -0
- package/lib/test/bulkPulling/urlParser.test.d.ts.map +1 -0
- package/lib/test/bulkPulling/urlParser.test.js +452 -0
- package/lib/test/bulkPulling/wsClient.reconnect.integration.test.js +2 -2
- package/lib/test/bulkPulling/wsClient.recovery.test.d.ts +2 -0
- package/lib/test/bulkPulling/wsClient.recovery.test.d.ts.map +1 -0
- package/lib/test/bulkPulling/wsClient.recovery.test.js +104 -0
- package/lib/test/bulkPulling/wsConfig.dsn.test.js +2 -0
- package/lib/test/bulkPulling/wsConnector.failover.test.js +396 -27
- package/lib/test/bulkPulling/wsConnectorPool.key.test.js +12 -10
- package/lib/test/bulkPulling/wsConnectorPool.keyAuth.test.d.ts +2 -0
- package/lib/test/bulkPulling/wsConnectorPool.keyAuth.test.d.ts.map +1 -0
- package/lib/test/bulkPulling/wsConnectorPool.keyAuth.test.js +28 -0
- package/lib/test/bulkPulling/wsProxy.failover.integration.test.d.ts +2 -0
- package/lib/test/bulkPulling/wsProxy.failover.integration.test.d.ts.map +1 -0
- package/lib/test/bulkPulling/wsProxy.failover.integration.test.js +120 -0
- package/lib/test/bulkPulling/wsProxy.failover.test.d.ts +2 -0
- package/lib/test/bulkPulling/wsProxy.failover.test.d.ts.map +1 -0
- package/lib/test/bulkPulling/wsProxy.failover.test.js +465 -0
- package/lib/test/client/wsClient.recovery.test.d.ts +2 -0
- package/lib/test/client/wsClient.recovery.test.d.ts.map +1 -0
- package/lib/test/client/wsClient.recovery.test.js +122 -0
- package/lib/test/client/wsConnectPool.test.d.ts +2 -0
- package/lib/test/client/wsConnectPool.test.d.ts.map +1 -0
- package/lib/test/client/wsConnectPool.test.js +147 -0
- package/lib/test/client/wsConnector.failover.test.d.ts +2 -0
- package/lib/test/client/wsConnector.failover.test.d.ts.map +1 -0
- package/lib/test/client/wsConnector.failover.test.js +681 -0
- package/lib/test/client/wsConnector.leastConnections.test.d.ts +2 -0
- package/lib/test/client/wsConnector.leastConnections.test.d.ts.map +1 -0
- package/lib/test/client/wsConnector.leastConnections.test.js +71 -0
- package/lib/test/client/wsConnectorPool.key.test.d.ts +2 -0
- package/lib/test/client/wsConnectorPool.key.test.d.ts.map +1 -0
- package/lib/test/client/wsConnectorPool.key.test.js +127 -0
- package/lib/test/client/wsEventCallback.test.d.ts +2 -0
- package/lib/test/client/wsEventCallback.test.d.ts.map +1 -0
- package/lib/test/client/wsEventCallback.test.js +98 -0
- package/lib/test/common/addressConnectionTracker.test.d.ts +2 -0
- package/lib/test/common/addressConnectionTracker.test.d.ts.map +1 -0
- package/lib/test/common/addressConnectionTracker.test.js +74 -0
- package/lib/test/common/dsn.test.d.ts +2 -0
- package/lib/test/common/dsn.test.d.ts.map +1 -0
- package/lib/test/common/dsn.test.js +406 -0
- package/lib/test/common/log.test.d.ts +2 -0
- package/lib/test/common/log.test.d.ts.map +1 -0
- package/lib/test/common/log.test.js +54 -0
- package/lib/test/common/utils.test.d.ts +2 -0
- package/lib/test/common/utils.test.d.ts.map +1 -0
- package/lib/test/common/utils.test.js +13 -0
- package/lib/test/common/wsConfig.dsn.test.d.ts +2 -0
- package/lib/test/common/wsConfig.dsn.test.d.ts.map +1 -0
- package/lib/test/common/wsConfig.dsn.test.js +39 -0
- package/lib/test/helpers/utils.d.ts +27 -0
- package/lib/test/helpers/utils.d.ts.map +1 -0
- package/lib/test/helpers/utils.js +341 -0
- package/lib/test/helpers/wsFailoverProxy.d.ts +109 -0
- package/lib/test/helpers/wsFailoverProxy.d.ts.map +1 -0
- package/lib/test/helpers/wsFailoverProxy.js +420 -0
- package/lib/test/helpers/wsProxy.d.ts +110 -0
- package/lib/test/helpers/wsProxy.d.ts.map +1 -0
- package/lib/test/helpers/wsProxy.js +429 -0
- package/lib/test/sql/core/decimal.test.d.ts +2 -0
- package/lib/test/sql/core/decimal.test.d.ts.map +1 -0
- package/lib/test/sql/core/decimal.test.js +153 -0
- package/lib/test/sql/core/queryTables.test.d.ts +2 -0
- package/lib/test/sql/core/queryTables.test.d.ts.map +1 -0
- package/lib/test/sql/core/queryTables.test.js +506 -0
- package/lib/test/sql/core/schemaless.test.d.ts +2 -0
- package/lib/test/sql/core/schemaless.test.d.ts.map +1 -0
- package/lib/test/sql/core/schemaless.test.js +102 -0
- package/lib/test/sql/core/sql.test.d.ts +2 -0
- package/lib/test/sql/core/sql.test.d.ts.map +1 -0
- package/lib/test/sql/core/sql.test.js +324 -0
- package/lib/test/sql/decimal.test.d.ts +2 -0
- package/lib/test/sql/decimal.test.d.ts.map +1 -0
- package/lib/test/sql/decimal.test.js +153 -0
- package/lib/test/sql/failover/sql.failover.test.d.ts +2 -0
- package/lib/test/sql/failover/sql.failover.test.d.ts.map +1 -0
- package/lib/test/sql/failover/sql.failover.test.js +341 -0
- package/lib/test/sql/queryTables.test.d.ts +2 -0
- package/lib/test/sql/queryTables.test.d.ts.map +1 -0
- package/lib/test/sql/queryTables.test.js +506 -0
- package/lib/test/sql/schemaless.test.d.ts +2 -0
- package/lib/test/sql/schemaless.test.d.ts.map +1 -0
- package/lib/test/sql/schemaless.test.js +102 -0
- package/lib/test/sql/sql.failover.test.d.ts +2 -0
- package/lib/test/sql/sql.failover.test.d.ts.map +1 -0
- package/lib/test/sql/sql.failover.test.js +341 -0
- package/lib/test/sql/sql.test.d.ts +2 -0
- package/lib/test/sql/sql.test.d.ts.map +1 -0
- package/lib/test/sql/sql.test.js +324 -0
- package/lib/test/stmt/failover/stmt2.failover.mock.test.d.ts +2 -0
- package/lib/test/stmt/failover/stmt2.failover.mock.test.d.ts.map +1 -0
- package/lib/test/stmt/failover/stmt2.failover.mock.test.js +341 -0
- package/lib/test/stmt/failover/stmt2.failover.test.d.ts +2 -0
- package/lib/test/stmt/failover/stmt2.failover.test.d.ts.map +1 -0
- package/lib/test/stmt/failover/stmt2.failover.test.js +384 -0
- package/lib/test/stmt/stmt1.func.test.d.ts +2 -0
- package/lib/test/stmt/stmt1.func.test.d.ts.map +1 -0
- package/lib/test/stmt/stmt1.func.test.js +418 -0
- package/lib/test/stmt/stmt1.type.test.d.ts +2 -0
- package/lib/test/stmt/stmt1.type.test.d.ts.map +1 -0
- package/lib/test/stmt/stmt1.type.test.js +399 -0
- package/lib/test/stmt/stmt2.failover.mock.test.d.ts +2 -0
- package/lib/test/stmt/stmt2.failover.mock.test.d.ts.map +1 -0
- package/lib/test/stmt/stmt2.failover.mock.test.js +341 -0
- package/lib/test/stmt/stmt2.failover.test.d.ts +2 -0
- package/lib/test/stmt/stmt2.failover.test.d.ts.map +1 -0
- package/lib/test/stmt/stmt2.failover.test.js +384 -0
- package/lib/test/stmt/stmt2.func.test.d.ts +2 -0
- package/lib/test/stmt/stmt2.func.test.d.ts.map +1 -0
- package/lib/test/stmt/stmt2.func.test.js +537 -0
- package/lib/test/stmt/stmt2.type.test.d.ts +2 -0
- package/lib/test/stmt/stmt2.type.test.d.ts.map +1 -0
- package/lib/test/stmt/stmt2.type.test.js +401 -0
- package/lib/test/stmt/v1/stmt1.func.test.d.ts +2 -0
- package/lib/test/stmt/v1/stmt1.func.test.d.ts.map +1 -0
- package/lib/test/stmt/v1/stmt1.func.test.js +418 -0
- package/lib/test/stmt/v1/stmt1.type.test.d.ts +2 -0
- package/lib/test/stmt/v1/stmt1.type.test.d.ts.map +1 -0
- package/lib/test/stmt/v1/stmt1.type.test.js +399 -0
- package/lib/test/stmt/v2/stmt2.func.test.d.ts +2 -0
- package/lib/test/stmt/v2/stmt2.func.test.d.ts.map +1 -0
- package/lib/test/stmt/v2/stmt2.func.test.js +537 -0
- package/lib/test/stmt/v2/stmt2.type.test.d.ts +2 -0
- package/lib/test/stmt/v2/stmt2.type.test.d.ts.map +1 -0
- package/lib/test/stmt/v2/stmt2.type.test.js +401 -0
- package/lib/test/tmq/cloud/cloud.tmq.test.d.ts +2 -0
- package/lib/test/tmq/cloud/cloud.tmq.test.d.ts.map +1 -0
- package/lib/test/tmq/cloud/cloud.tmq.test.js +84 -0
- package/lib/test/tmq/cloud/tmq.cloud.test.d.ts +2 -0
- package/lib/test/tmq/cloud/tmq.cloud.test.d.ts.map +1 -0
- package/lib/test/tmq/cloud/tmq.cloud.test.js +82 -0
- package/lib/test/tmq/core/tmq.config.test.d.ts +2 -0
- package/lib/test/tmq/core/tmq.config.test.d.ts.map +1 -0
- package/lib/test/tmq/core/tmq.config.test.js +83 -0
- package/lib/test/tmq/core/tmq.test.d.ts +2 -0
- package/lib/test/tmq/core/tmq.test.d.ts.map +1 -0
- package/lib/test/tmq/core/tmq.test.js +513 -0
- package/lib/test/tmq/failover/tmq.failover.test.d.ts +2 -0
- package/lib/test/tmq/failover/tmq.failover.test.d.ts.map +1 -0
- package/lib/test/tmq/failover/tmq.failover.test.js +404 -0
- package/lib/test/tmq/tmq.cloud.test.d.ts +2 -0
- package/lib/test/tmq/tmq.cloud.test.d.ts.map +1 -0
- package/lib/test/tmq/tmq.cloud.test.js +82 -0
- package/lib/test/tmq/tmq.config.test.d.ts +2 -0
- package/lib/test/tmq/tmq.config.test.d.ts.map +1 -0
- package/lib/test/tmq/tmq.config.test.js +94 -0
- package/lib/test/tmq/tmq.failover.test.d.ts +2 -0
- package/lib/test/tmq/tmq.failover.test.d.ts.map +1 -0
- package/lib/test/tmq/tmq.failover.test.js +404 -0
- package/lib/test/tmq/tmq.test.d.ts +2 -0
- package/lib/test/tmq/tmq.test.d.ts.map +1 -0
- package/lib/test/tmq/tmq.test.js +513 -0
- package/lib/test/unit/connectionManager.test.d.ts +2 -0
- package/lib/test/unit/connectionManager.test.d.ts.map +1 -0
- package/lib/test/unit/connectionManager.test.js +91 -0
- package/package.json +1 -1
- package/readme.md +2 -2
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const wsConnector_1 = require("@src/client/wsConnector");
|
|
4
|
+
const wsEventCallback_1 = require("@src/client/wsEventCallback");
|
|
5
|
+
const addressConnectionTracker_1 = require("@src/common/addressConnectionTracker");
|
|
6
|
+
const dsn_1 = require("@src/common/dsn");
|
|
7
|
+
function createInflightStore() {
|
|
8
|
+
let nextMsgId = 1n;
|
|
9
|
+
const reqIdToMsgId = new Map();
|
|
10
|
+
const msgIdToRequest = new Map();
|
|
11
|
+
return {
|
|
12
|
+
insert(req) {
|
|
13
|
+
const msgId = nextMsgId;
|
|
14
|
+
nextMsgId += 1n;
|
|
15
|
+
reqIdToMsgId.set(req.reqId, msgId);
|
|
16
|
+
msgIdToRequest.set(msgId, req);
|
|
17
|
+
},
|
|
18
|
+
remove(reqId) {
|
|
19
|
+
const msgId = reqIdToMsgId.get(reqId);
|
|
20
|
+
if (msgId === undefined) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
reqIdToMsgId.delete(reqId);
|
|
24
|
+
msgIdToRequest.delete(msgId);
|
|
25
|
+
},
|
|
26
|
+
getRequests() {
|
|
27
|
+
return Array.from(msgIdToRequest.entries())
|
|
28
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
29
|
+
.map(([, req]) => req);
|
|
30
|
+
},
|
|
31
|
+
clear() {
|
|
32
|
+
nextMsgId = 1n;
|
|
33
|
+
reqIdToMsgId.clear();
|
|
34
|
+
msgIdToRequest.clear();
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function createBareConnector(dsn = "ws://root:taosdata@host1:6041,host2:6042") {
|
|
39
|
+
const connector = Object.create(wsConnector_1.WebSocketConnector.prototype);
|
|
40
|
+
connector._timeout = 5000;
|
|
41
|
+
connector._dsn = (0, dsn_1.parse)(dsn);
|
|
42
|
+
connector._currentAddress = connector._dsn.addresses[0];
|
|
43
|
+
connector._retryConfig = new wsConnector_1.RetryConfig(1, 1, 8);
|
|
44
|
+
connector._reconnectLock = null;
|
|
45
|
+
connector._isReconnecting = false;
|
|
46
|
+
connector._allowReconnect = true;
|
|
47
|
+
connector._connectionReady = Promise.resolve();
|
|
48
|
+
connector._suppressedSockets = new WeakSet();
|
|
49
|
+
connector._sessionRecoveryHook = null;
|
|
50
|
+
connector._inflightStore = createInflightStore();
|
|
51
|
+
connector._conn = {
|
|
52
|
+
readyState: 1,
|
|
53
|
+
send: jest.fn(),
|
|
54
|
+
close: jest.fn(),
|
|
55
|
+
};
|
|
56
|
+
return connector;
|
|
57
|
+
}
|
|
58
|
+
function buildBinaryMessage(action) {
|
|
59
|
+
const buffer = new ArrayBuffer(26);
|
|
60
|
+
const view = new DataView(buffer);
|
|
61
|
+
view.setBigInt64(16, action, true);
|
|
62
|
+
return buffer;
|
|
63
|
+
}
|
|
64
|
+
function delay(ms) {
|
|
65
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
66
|
+
}
|
|
67
|
+
function hasInflightRequest(connector, reqId) {
|
|
68
|
+
return connector
|
|
69
|
+
._inflightStore
|
|
70
|
+
.getRequests()
|
|
71
|
+
.some((req) => req.reqId === reqId);
|
|
72
|
+
}
|
|
73
|
+
function listInflightReqIds(connector) {
|
|
74
|
+
return connector
|
|
75
|
+
._inflightStore
|
|
76
|
+
.getRequests()
|
|
77
|
+
.map((req) => req.reqId);
|
|
78
|
+
}
|
|
79
|
+
function resetAddressTracker() {
|
|
80
|
+
const tracker = addressConnectionTracker_1.AddressConnectionTracker.instance();
|
|
81
|
+
tracker._counts.clear();
|
|
82
|
+
}
|
|
83
|
+
function resetCallbackRegistry() {
|
|
84
|
+
const CallbackClass = wsEventCallback_1.WsEventCallback;
|
|
85
|
+
CallbackClass._msgActionRegister = new Map();
|
|
86
|
+
}
|
|
87
|
+
describe("WebSocketConnector failover and retry", () => {
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
resetAddressTracker();
|
|
90
|
+
resetCallbackRegistry();
|
|
91
|
+
});
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
jest.restoreAllMocks();
|
|
94
|
+
resetAddressTracker();
|
|
95
|
+
resetCallbackRegistry();
|
|
96
|
+
});
|
|
97
|
+
test("deduplicates concurrent reconnect triggers with reconnect lock", async () => {
|
|
98
|
+
const connector = createBareConnector();
|
|
99
|
+
const reconnectImpl = jest.fn(async () => {
|
|
100
|
+
await delay(20);
|
|
101
|
+
});
|
|
102
|
+
connector._doReconnect = reconnectImpl;
|
|
103
|
+
await Promise.all([
|
|
104
|
+
connector.triggerReconnect(),
|
|
105
|
+
connector.triggerReconnect(),
|
|
106
|
+
connector.triggerReconnect(),
|
|
107
|
+
]);
|
|
108
|
+
expect(reconnectImpl).toHaveBeenCalledTimes(1);
|
|
109
|
+
});
|
|
110
|
+
test("switches to least-connected address after retries are exhausted", async () => {
|
|
111
|
+
const connector = createBareConnector();
|
|
112
|
+
const leastSelector = jest
|
|
113
|
+
.spyOn(addressConnectionTracker_1.AddressConnectionTracker.instance(), "selectLeastConnected")
|
|
114
|
+
.mockImplementation(() => 0);
|
|
115
|
+
const attempts = [];
|
|
116
|
+
connector.sleep = jest.fn(async () => { });
|
|
117
|
+
connector.reconnect = jest.fn(async () => {
|
|
118
|
+
const current = connector._currentAddress;
|
|
119
|
+
const addr = `${current.host}:${current.port}`;
|
|
120
|
+
attempts.push(addr);
|
|
121
|
+
if (addr === "host1:6041") {
|
|
122
|
+
throw new Error("host1 down");
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
await connector.attemptReconnect();
|
|
126
|
+
expect(attempts).toEqual([
|
|
127
|
+
"host1:6041",
|
|
128
|
+
"host2:6042",
|
|
129
|
+
]);
|
|
130
|
+
expect(leastSelector).toHaveBeenCalledWith([
|
|
131
|
+
connector._dsn.addresses[1],
|
|
132
|
+
]);
|
|
133
|
+
expect(`${connector._currentAddress.host}:${connector._currentAddress.port}`)
|
|
134
|
+
.toBe("host2:6042");
|
|
135
|
+
});
|
|
136
|
+
test("attemptReconnect does not reselect failed addresses in one reconnect round", async () => {
|
|
137
|
+
const connector = createBareConnector("ws://root:taosdata@host1:6041,host2:6042,host3:6043");
|
|
138
|
+
const leastSelector = jest
|
|
139
|
+
.spyOn(addressConnectionTracker_1.AddressConnectionTracker.instance(), "selectLeastConnected")
|
|
140
|
+
.mockImplementation(() => 0);
|
|
141
|
+
const attempts = [];
|
|
142
|
+
connector.sleep = jest.fn(async () => { });
|
|
143
|
+
connector.reconnect = jest.fn(async () => {
|
|
144
|
+
const current = connector._currentAddress;
|
|
145
|
+
attempts.push(`${current.host}:${current.port}`);
|
|
146
|
+
throw new Error("all down");
|
|
147
|
+
});
|
|
148
|
+
await expect(connector.attemptReconnect()).rejects.toThrow("Failed to reconnect to any available address");
|
|
149
|
+
expect(attempts).toEqual([
|
|
150
|
+
"host1:6041",
|
|
151
|
+
"host2:6042",
|
|
152
|
+
"host3:6043",
|
|
153
|
+
]);
|
|
154
|
+
expect(leastSelector).toHaveBeenNthCalledWith(1, [
|
|
155
|
+
connector._dsn.addresses[1],
|
|
156
|
+
connector._dsn.addresses[2],
|
|
157
|
+
]);
|
|
158
|
+
expect(leastSelector).toHaveBeenNthCalledWith(2, [
|
|
159
|
+
connector._dsn.addresses[2],
|
|
160
|
+
]);
|
|
161
|
+
});
|
|
162
|
+
test("attemptReconnect keeps retrying same address for single-address dsn", async () => {
|
|
163
|
+
const connector = createBareConnector("ws://root:taosdata@host1:6041");
|
|
164
|
+
connector._retryConfig = new wsConnector_1.RetryConfig(3, 1, 8);
|
|
165
|
+
connector.sleep = jest.fn(async () => { });
|
|
166
|
+
const attempts = [];
|
|
167
|
+
connector.reconnect = jest.fn(async () => {
|
|
168
|
+
const current = connector._currentAddress;
|
|
169
|
+
attempts.push(`${current.host}:${current.port}`);
|
|
170
|
+
throw new Error("host1 down");
|
|
171
|
+
});
|
|
172
|
+
await expect(connector.attemptReconnect()).rejects.toThrow("Failed to reconnect to any available address");
|
|
173
|
+
expect(attempts).toEqual([
|
|
174
|
+
"host1:6041",
|
|
175
|
+
"host1:6041",
|
|
176
|
+
"host1:6041",
|
|
177
|
+
]);
|
|
178
|
+
});
|
|
179
|
+
test("attemptReconnect throws after all addresses and retries are exhausted", async () => {
|
|
180
|
+
const connector = createBareConnector();
|
|
181
|
+
jest
|
|
182
|
+
.spyOn(addressConnectionTracker_1.AddressConnectionTracker.instance(), "selectLeastConnected")
|
|
183
|
+
.mockImplementation(() => 0);
|
|
184
|
+
connector.sleep = jest.fn(async () => { });
|
|
185
|
+
connector.reconnect = jest.fn(async () => {
|
|
186
|
+
throw new Error("all down");
|
|
187
|
+
});
|
|
188
|
+
await expect(connector.attemptReconnect()).rejects.toThrow("Failed to reconnect to any available address");
|
|
189
|
+
expect(connector.reconnect).toHaveBeenCalledTimes(2);
|
|
190
|
+
expect(`${connector._currentAddress.host}:${connector._currentAddress.port}`)
|
|
191
|
+
.toBe("host2:6042");
|
|
192
|
+
});
|
|
193
|
+
test("handleDisconnect skips reconnect when reconnect is disabled", async () => {
|
|
194
|
+
const connector = createBareConnector();
|
|
195
|
+
connector._allowReconnect = false;
|
|
196
|
+
connector.triggerReconnect = jest.fn();
|
|
197
|
+
await connector.handleDisconnect(connector._conn);
|
|
198
|
+
expect(connector.triggerReconnect).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
test("handleDisconnect skips reconnect on normal close", async () => {
|
|
201
|
+
const connector = createBareConnector();
|
|
202
|
+
connector.triggerReconnect = jest.fn();
|
|
203
|
+
await connector.handleDisconnect(connector._conn, { code: 1000, reason: "normal close" });
|
|
204
|
+
expect(connector.triggerReconnect).not.toHaveBeenCalled();
|
|
205
|
+
});
|
|
206
|
+
test("handleDisconnect swallows reconnect failure", async () => {
|
|
207
|
+
const connector = createBareConnector();
|
|
208
|
+
connector.triggerReconnect = jest.fn(async () => {
|
|
209
|
+
throw new Error("reconnect failed");
|
|
210
|
+
});
|
|
211
|
+
await expect(connector.handleDisconnect(connector._conn, { code: 1006, reason: "abnormal close" })).resolves.toBeUndefined();
|
|
212
|
+
expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
|
|
213
|
+
});
|
|
214
|
+
test("handleDisconnect rejects non-retriable pending callbacks immediately", async () => {
|
|
215
|
+
const connector = createBareConnector();
|
|
216
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
217
|
+
connector._conn.send = jest.fn(() => { });
|
|
218
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
219
|
+
action: "query",
|
|
220
|
+
args: {
|
|
221
|
+
req_id: 1201,
|
|
222
|
+
},
|
|
223
|
+
}));
|
|
224
|
+
void connector.handleDisconnect(connector._conn, { code: 1006, reason: "abnormal close" });
|
|
225
|
+
const state = await Promise.race([
|
|
226
|
+
pending.then(() => "resolved").catch(() => "rejected"),
|
|
227
|
+
delay(40).then(() => "pending"),
|
|
228
|
+
]);
|
|
229
|
+
expect(state).toBe("rejected");
|
|
230
|
+
expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
|
|
231
|
+
expect(hasInflightRequest(connector, 1201n)).toBe(false);
|
|
232
|
+
});
|
|
233
|
+
test("handleDisconnect keeps retriable pending callbacks for replay", async () => {
|
|
234
|
+
const connector = createBareConnector();
|
|
235
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
236
|
+
connector._conn.send = jest.fn(() => { });
|
|
237
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
238
|
+
action: "insert",
|
|
239
|
+
args: {
|
|
240
|
+
req_id: 1202,
|
|
241
|
+
},
|
|
242
|
+
}));
|
|
243
|
+
void pending.catch(() => { });
|
|
244
|
+
void connector.handleDisconnect(connector._conn, { code: 1006, reason: "abnormal close" });
|
|
245
|
+
const state = await Promise.race([
|
|
246
|
+
pending.then(() => "resolved").catch(() => "rejected"),
|
|
247
|
+
delay(40).then(() => "pending"),
|
|
248
|
+
]);
|
|
249
|
+
expect(state).toBe("pending");
|
|
250
|
+
expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
|
|
251
|
+
expect(hasInflightRequest(connector, 1202n)).toBe(true);
|
|
252
|
+
connector.failAllInflightRequests(new Error("cleanup"));
|
|
253
|
+
});
|
|
254
|
+
test("_doReconnect fails all inflight requests when reconnect fails", async () => {
|
|
255
|
+
const connector = createBareConnector();
|
|
256
|
+
const rejectSpy = jest.fn();
|
|
257
|
+
connector.attemptReconnect = jest.fn(async () => {
|
|
258
|
+
throw new Error("reconnect failed");
|
|
259
|
+
});
|
|
260
|
+
connector._inflightStore.insert({
|
|
261
|
+
reqId: 901n,
|
|
262
|
+
action: "insert",
|
|
263
|
+
message: JSON.stringify({
|
|
264
|
+
action: "insert",
|
|
265
|
+
args: { req_id: 901 },
|
|
266
|
+
}),
|
|
267
|
+
resolve: jest.fn(),
|
|
268
|
+
reject: rejectSpy,
|
|
269
|
+
});
|
|
270
|
+
await expect(connector._doReconnect()).rejects.toThrow("reconnect failed");
|
|
271
|
+
expect(rejectSpy).toHaveBeenCalledTimes(1);
|
|
272
|
+
expect(hasInflightRequest(connector, 901n)).toBe(false);
|
|
273
|
+
});
|
|
274
|
+
test("close disables reconnect and closes current socket", () => {
|
|
275
|
+
const connector = createBareConnector();
|
|
276
|
+
const decrementSpy = jest
|
|
277
|
+
.spyOn(addressConnectionTracker_1.AddressConnectionTracker.instance(), "decrement")
|
|
278
|
+
.mockImplementation(() => { });
|
|
279
|
+
connector.close();
|
|
280
|
+
expect(decrementSpy).toHaveBeenCalledWith("host1:6041");
|
|
281
|
+
expect(connector._allowReconnect).toBe(false);
|
|
282
|
+
expect(connector._conn.close).toHaveBeenCalledTimes(1);
|
|
283
|
+
expect(connector._suppressedSockets.has(connector._conn)).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
test("attemptReconnect aborts silently when close is called mid-retry", async () => {
|
|
286
|
+
const connector = createBareConnector("ws://root:taosdata@host1:6041");
|
|
287
|
+
connector._retryConfig = new wsConnector_1.RetryConfig(3, 1, 8);
|
|
288
|
+
connector.reconnect = jest.fn(async () => {
|
|
289
|
+
throw new Error("host down");
|
|
290
|
+
});
|
|
291
|
+
connector.sleep = jest.fn(async () => {
|
|
292
|
+
connector.close();
|
|
293
|
+
});
|
|
294
|
+
await expect(connector.attemptReconnect()).resolves.toBeUndefined();
|
|
295
|
+
expect(connector.reconnect).toHaveBeenCalledTimes(1);
|
|
296
|
+
});
|
|
297
|
+
test("close rejects retriable inflight request immediately", async () => {
|
|
298
|
+
const connector = createBareConnector();
|
|
299
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
300
|
+
connector._conn.send = jest.fn(() => {
|
|
301
|
+
throw new Error("cannot call send() while not connected");
|
|
302
|
+
});
|
|
303
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
304
|
+
action: "insert",
|
|
305
|
+
args: {
|
|
306
|
+
req_id: 401,
|
|
307
|
+
},
|
|
308
|
+
}));
|
|
309
|
+
void pending.catch(() => { });
|
|
310
|
+
await delay(20);
|
|
311
|
+
connector.close();
|
|
312
|
+
const state = await Promise.race([
|
|
313
|
+
pending.then(() => "resolved").catch(() => "rejected"),
|
|
314
|
+
delay(40).then(() => "pending"),
|
|
315
|
+
]);
|
|
316
|
+
expect(state).toBe("rejected");
|
|
317
|
+
expect(hasInflightRequest(connector, 401n)).toBe(false);
|
|
318
|
+
});
|
|
319
|
+
test("close during callback registration does not leave callback entry until timeout", async () => {
|
|
320
|
+
const connector = createBareConnector();
|
|
321
|
+
connector._conn.send = jest.fn();
|
|
322
|
+
const callback = wsEventCallback_1.WsEventCallback.instance();
|
|
323
|
+
const originalRegister = callback.registerCallback.bind(callback);
|
|
324
|
+
jest
|
|
325
|
+
.spyOn(callback, "registerCallback")
|
|
326
|
+
.mockImplementation(async (id, res, rej) => {
|
|
327
|
+
await delay(20);
|
|
328
|
+
await originalRegister(id, res, rej);
|
|
329
|
+
});
|
|
330
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
331
|
+
action: "insert",
|
|
332
|
+
args: {
|
|
333
|
+
req_id: 402,
|
|
334
|
+
},
|
|
335
|
+
}));
|
|
336
|
+
void pending.catch(() => { });
|
|
337
|
+
await delay(5);
|
|
338
|
+
connector.close();
|
|
339
|
+
await delay(30);
|
|
340
|
+
expect(wsEventCallback_1.WsEventCallback._msgActionRegister.size).toBe(0);
|
|
341
|
+
expect(connector._conn.send).not.toHaveBeenCalled();
|
|
342
|
+
});
|
|
343
|
+
test("reconnect decrements current address before closing old socket", async () => {
|
|
344
|
+
const connector = createBareConnector();
|
|
345
|
+
const decrementSpy = jest
|
|
346
|
+
.spyOn(addressConnectionTracker_1.AddressConnectionTracker.instance(), "decrement")
|
|
347
|
+
.mockImplementation(() => { });
|
|
348
|
+
connector.createConnection = jest.fn(() => {
|
|
349
|
+
connector._connectionReady = Promise.resolve();
|
|
350
|
+
});
|
|
351
|
+
await connector.reconnect();
|
|
352
|
+
expect(decrementSpy).toHaveBeenCalledWith("host1:6041");
|
|
353
|
+
expect(connector._conn.close).toHaveBeenCalledTimes(1);
|
|
354
|
+
expect(connector.createConnection).toHaveBeenCalledTimes(1);
|
|
355
|
+
});
|
|
356
|
+
test("triggers reconnect and keeps retriable string request inflight when send throws", async () => {
|
|
357
|
+
const connector = createBareConnector();
|
|
358
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
359
|
+
connector._conn.send = jest.fn(() => {
|
|
360
|
+
throw new Error("cannot call send() while not connected");
|
|
361
|
+
});
|
|
362
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
363
|
+
action: "insert",
|
|
364
|
+
args: {
|
|
365
|
+
req_id: 101,
|
|
366
|
+
},
|
|
367
|
+
}));
|
|
368
|
+
void pending.catch(() => { });
|
|
369
|
+
const state = await Promise.race([
|
|
370
|
+
pending.then(() => "resolved").catch(() => "rejected"),
|
|
371
|
+
delay(20).then(() => "pending"),
|
|
372
|
+
]);
|
|
373
|
+
expect(state).toBe("pending");
|
|
374
|
+
expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
|
|
375
|
+
expect(hasInflightRequest(connector, 101n)).toBe(true);
|
|
376
|
+
connector.failAllInflightRequests(new Error("cleanup"));
|
|
377
|
+
});
|
|
378
|
+
test("triggers reconnect and keeps retriable poll request inflight when send throws", async () => {
|
|
379
|
+
const connector = createBareConnector();
|
|
380
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
381
|
+
connector._conn.send = jest.fn(() => {
|
|
382
|
+
throw new Error("cannot call send() while not connected");
|
|
383
|
+
});
|
|
384
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
385
|
+
action: "poll",
|
|
386
|
+
args: {
|
|
387
|
+
req_id: 109,
|
|
388
|
+
blocking_time: 500,
|
|
389
|
+
message_id: 0,
|
|
390
|
+
},
|
|
391
|
+
}));
|
|
392
|
+
void pending.catch(() => { });
|
|
393
|
+
const state = await Promise.race([
|
|
394
|
+
pending.then(() => "resolved").catch(() => "rejected"),
|
|
395
|
+
delay(20).then(() => "pending"),
|
|
396
|
+
]);
|
|
397
|
+
expect(state).toBe("pending");
|
|
398
|
+
expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
|
|
399
|
+
expect(hasInflightRequest(connector, 109n)).toBe(true);
|
|
400
|
+
connector.failAllInflightRequests(new Error("cleanup"));
|
|
401
|
+
});
|
|
402
|
+
test("triggers reconnect and keeps retriable subscribe request inflight when send throws", async () => {
|
|
403
|
+
const connector = createBareConnector();
|
|
404
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
405
|
+
connector._conn.send = jest.fn(() => {
|
|
406
|
+
throw new Error("cannot call send() while not connected");
|
|
407
|
+
});
|
|
408
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
409
|
+
action: "subscribe",
|
|
410
|
+
args: {
|
|
411
|
+
req_id: 110,
|
|
412
|
+
topics: ["topic_a"],
|
|
413
|
+
},
|
|
414
|
+
}));
|
|
415
|
+
void pending.catch(() => { });
|
|
416
|
+
const state = await Promise.race([
|
|
417
|
+
pending.then(() => "resolved").catch(() => "rejected"),
|
|
418
|
+
delay(20).then(() => "pending"),
|
|
419
|
+
]);
|
|
420
|
+
expect(state).toBe("pending");
|
|
421
|
+
expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
|
|
422
|
+
expect(hasInflightRequest(connector, 110n)).toBe(true);
|
|
423
|
+
connector.failAllInflightRequests(new Error("cleanup"));
|
|
424
|
+
});
|
|
425
|
+
test("rejects non-retriable string request immediately when send throws", async () => {
|
|
426
|
+
const connector = createBareConnector();
|
|
427
|
+
connector._conn.send = jest.fn(() => {
|
|
428
|
+
throw new Error("send failed");
|
|
429
|
+
});
|
|
430
|
+
await expect(connector.sendMsg(JSON.stringify({
|
|
431
|
+
action: "query",
|
|
432
|
+
args: {
|
|
433
|
+
req_id: 102,
|
|
434
|
+
},
|
|
435
|
+
}))).rejects.toThrow("send failed");
|
|
436
|
+
expect(hasInflightRequest(connector, 102n)).toBe(false);
|
|
437
|
+
});
|
|
438
|
+
test("rejects retriable string request immediately when send throws with non-network error", async () => {
|
|
439
|
+
const connector = createBareConnector();
|
|
440
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
441
|
+
connector._conn.send = jest.fn(() => {
|
|
442
|
+
throw new Error("serialize failed");
|
|
443
|
+
});
|
|
444
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
445
|
+
action: "insert",
|
|
446
|
+
args: {
|
|
447
|
+
req_id: 111,
|
|
448
|
+
},
|
|
449
|
+
}));
|
|
450
|
+
void pending.catch(() => { });
|
|
451
|
+
const state = await Promise.race([
|
|
452
|
+
pending.then(() => "resolved").catch(() => "rejected"),
|
|
453
|
+
delay(20).then(() => "pending"),
|
|
454
|
+
]);
|
|
455
|
+
expect(state).toBe("rejected");
|
|
456
|
+
expect(connector.triggerReconnect).not.toHaveBeenCalled();
|
|
457
|
+
expect(hasInflightRequest(connector, 111n)).toBe(false);
|
|
458
|
+
});
|
|
459
|
+
test("rejects retriable subscribe request immediately when send throws with non-network error", async () => {
|
|
460
|
+
const connector = createBareConnector();
|
|
461
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
462
|
+
connector._conn.send = jest.fn(() => {
|
|
463
|
+
throw new Error("serialize failed");
|
|
464
|
+
});
|
|
465
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
466
|
+
action: "subscribe",
|
|
467
|
+
args: {
|
|
468
|
+
req_id: 113,
|
|
469
|
+
topics: ["topic_a"],
|
|
470
|
+
},
|
|
471
|
+
}));
|
|
472
|
+
void pending.catch(() => { });
|
|
473
|
+
const state = await Promise.race([
|
|
474
|
+
pending.then(() => "resolved").catch(() => "rejected"),
|
|
475
|
+
delay(20).then(() => "pending"),
|
|
476
|
+
]);
|
|
477
|
+
expect(state).toBe("rejected");
|
|
478
|
+
expect(connector.triggerReconnect).not.toHaveBeenCalled();
|
|
479
|
+
expect(hasInflightRequest(connector, 113n)).toBe(false);
|
|
480
|
+
});
|
|
481
|
+
test("triggers reconnect and rejects non-retriable string request on network send error", async () => {
|
|
482
|
+
const connector = createBareConnector();
|
|
483
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
484
|
+
connector._conn.send = jest.fn(() => {
|
|
485
|
+
throw new Error("cannot call send() while not connected");
|
|
486
|
+
});
|
|
487
|
+
await expect(connector.sendMsg(JSON.stringify({
|
|
488
|
+
action: "query",
|
|
489
|
+
args: {
|
|
490
|
+
req_id: 112,
|
|
491
|
+
},
|
|
492
|
+
}))).rejects.toThrow("cannot call send() while not connected");
|
|
493
|
+
expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
|
|
494
|
+
expect(hasInflightRequest(connector, 112n)).toBe(false);
|
|
495
|
+
});
|
|
496
|
+
test("triggers reconnect and keeps retriable binary request inflight when send throws", async () => {
|
|
497
|
+
const connector = createBareConnector();
|
|
498
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
499
|
+
connector._conn.send = jest.fn(() => {
|
|
500
|
+
throw new Error("cannot call send() while not connected");
|
|
501
|
+
});
|
|
502
|
+
const message = buildBinaryMessage(6n);
|
|
503
|
+
const pending = connector.sendBinaryMsg(201n, "binary_query", message);
|
|
504
|
+
void pending.catch(() => { });
|
|
505
|
+
const state = await Promise.race([
|
|
506
|
+
pending.then(() => "resolved").catch(() => "rejected"),
|
|
507
|
+
delay(20).then(() => "pending"),
|
|
508
|
+
]);
|
|
509
|
+
expect(state).toBe("pending");
|
|
510
|
+
expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
|
|
511
|
+
expect(hasInflightRequest(connector, 201n)).toBe(true);
|
|
512
|
+
connector.failAllInflightRequests(new Error("cleanup"));
|
|
513
|
+
});
|
|
514
|
+
test("rejects non-retriable binary request immediately when send throws", async () => {
|
|
515
|
+
const connector = createBareConnector();
|
|
516
|
+
connector._conn.send = jest.fn(() => {
|
|
517
|
+
throw new Error("send failed");
|
|
518
|
+
});
|
|
519
|
+
const message = buildBinaryMessage(7n);
|
|
520
|
+
await expect(connector.sendBinaryMsg(202n, "fetch", message)).rejects.toThrow("send failed");
|
|
521
|
+
expect(hasInflightRequest(connector, 202n)).toBe(false);
|
|
522
|
+
});
|
|
523
|
+
test("sendMsgDirect unregisters callback on network send error without reconnect", async () => {
|
|
524
|
+
const connector = createBareConnector();
|
|
525
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
526
|
+
connector._conn.send = jest.fn(() => {
|
|
527
|
+
throw new Error("cannot call send() while not connected");
|
|
528
|
+
});
|
|
529
|
+
const registerSpy = jest
|
|
530
|
+
.spyOn(wsEventCallback_1.WsEventCallback.instance(), "registerCallback")
|
|
531
|
+
.mockResolvedValue();
|
|
532
|
+
const unregisterSpy = jest
|
|
533
|
+
.spyOn(wsEventCallback_1.WsEventCallback.instance(), "unregisterCallback")
|
|
534
|
+
.mockResolvedValue();
|
|
535
|
+
await expect(connector.sendMsgDirect(JSON.stringify({
|
|
536
|
+
action: "conn",
|
|
537
|
+
args: {
|
|
538
|
+
req_id: 3001,
|
|
539
|
+
},
|
|
540
|
+
}))).rejects.toThrow("cannot call send() while not connected");
|
|
541
|
+
expect(registerSpy).toHaveBeenCalledTimes(1);
|
|
542
|
+
expect(unregisterSpy).toHaveBeenCalledWith(3001n);
|
|
543
|
+
expect(connector.triggerReconnect).not.toHaveBeenCalled();
|
|
544
|
+
});
|
|
545
|
+
test("sendMsgNoResp rejects on network send error and triggers reconnect", async () => {
|
|
546
|
+
const connector = createBareConnector();
|
|
547
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
548
|
+
connector._conn.send = jest.fn(() => {
|
|
549
|
+
throw new Error("cannot call send() while not connected");
|
|
550
|
+
});
|
|
551
|
+
await expect(connector.sendMsgNoResp(JSON.stringify({
|
|
552
|
+
action: "query",
|
|
553
|
+
args: {
|
|
554
|
+
req_id: 3002,
|
|
555
|
+
},
|
|
556
|
+
}))).rejects.toThrow("cannot call send() while not connected");
|
|
557
|
+
expect(connector.triggerReconnect).toHaveBeenCalledTimes(1);
|
|
558
|
+
});
|
|
559
|
+
test("keeps inflight store consistent when retriable requests are enqueued concurrently", async () => {
|
|
560
|
+
const connector = createBareConnector();
|
|
561
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
562
|
+
connector._conn.send = jest.fn(() => {
|
|
563
|
+
throw new Error("cannot call send() while not connected");
|
|
564
|
+
});
|
|
565
|
+
const reqIds = [301, 302, 303, 304];
|
|
566
|
+
await Promise.all(reqIds.map(async (reqId) => {
|
|
567
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
568
|
+
action: "insert",
|
|
569
|
+
args: {
|
|
570
|
+
req_id: reqId,
|
|
571
|
+
data: `insert into t values(now, ${reqId})`,
|
|
572
|
+
},
|
|
573
|
+
}));
|
|
574
|
+
void pending.catch(() => { });
|
|
575
|
+
}));
|
|
576
|
+
await delay(20);
|
|
577
|
+
expect(listInflightReqIds(connector)).toEqual([301n, 302n, 303n, 304n]);
|
|
578
|
+
for (const reqId of reqIds) {
|
|
579
|
+
expect(hasInflightRequest(connector, BigInt(reqId))).toBe(true);
|
|
580
|
+
}
|
|
581
|
+
connector.failAllInflightRequests(new Error("cleanup"));
|
|
582
|
+
});
|
|
583
|
+
test("replays inflight requests in the same put order", async () => {
|
|
584
|
+
const connector = createBareConnector();
|
|
585
|
+
connector.triggerReconnect = jest.fn(() => new Promise(() => { }));
|
|
586
|
+
connector._conn.send = jest.fn(() => {
|
|
587
|
+
throw new Error("cannot call send() while not connected");
|
|
588
|
+
});
|
|
589
|
+
const reqIds = [101, 102, 103, 104];
|
|
590
|
+
for (const reqId of reqIds) {
|
|
591
|
+
const pending = connector.sendMsg(JSON.stringify({
|
|
592
|
+
action: "insert",
|
|
593
|
+
args: {
|
|
594
|
+
req_id: reqId,
|
|
595
|
+
data: `insert into t values(now, ${reqId})`,
|
|
596
|
+
},
|
|
597
|
+
}));
|
|
598
|
+
void pending.catch(() => { });
|
|
599
|
+
}
|
|
600
|
+
await delay(20);
|
|
601
|
+
const replaySend = jest.fn();
|
|
602
|
+
connector._conn.send = replaySend;
|
|
603
|
+
await connector.replayRequests();
|
|
604
|
+
const replayedReqIds = replaySend.mock.calls.map(([payload]) => {
|
|
605
|
+
const parsed = JSON.parse(payload);
|
|
606
|
+
return parsed.args.req_id;
|
|
607
|
+
});
|
|
608
|
+
expect(replayedReqIds).toEqual([101, 102, 103, 104]);
|
|
609
|
+
connector.failAllInflightRequests(new Error("cleanup"));
|
|
610
|
+
});
|
|
611
|
+
test("replayRequests stops current round and keeps inflight when network send error happens", async () => {
|
|
612
|
+
const connector = createBareConnector();
|
|
613
|
+
const reject1 = jest.fn();
|
|
614
|
+
const reject2 = jest.fn();
|
|
615
|
+
connector._inflightStore.insert({
|
|
616
|
+
reqId: 501n,
|
|
617
|
+
action: "insert",
|
|
618
|
+
registerCallback: false,
|
|
619
|
+
message: JSON.stringify({
|
|
620
|
+
action: "insert",
|
|
621
|
+
args: {
|
|
622
|
+
req_id: 501,
|
|
623
|
+
},
|
|
624
|
+
}),
|
|
625
|
+
resolve: jest.fn(),
|
|
626
|
+
reject: reject1,
|
|
627
|
+
});
|
|
628
|
+
connector._inflightStore.insert({
|
|
629
|
+
reqId: 502n,
|
|
630
|
+
action: "insert",
|
|
631
|
+
registerCallback: false,
|
|
632
|
+
message: JSON.stringify({
|
|
633
|
+
action: "insert",
|
|
634
|
+
args: {
|
|
635
|
+
req_id: 502,
|
|
636
|
+
},
|
|
637
|
+
}),
|
|
638
|
+
resolve: jest.fn(),
|
|
639
|
+
reject: reject2,
|
|
640
|
+
});
|
|
641
|
+
connector._conn.readyState = 3;
|
|
642
|
+
connector._conn.send = jest.fn(() => {
|
|
643
|
+
throw new Error("cannot call send() while not connected");
|
|
644
|
+
});
|
|
645
|
+
await expect(connector.replayRequests()).rejects.toThrow("cannot call send() while not connected");
|
|
646
|
+
expect(hasInflightRequest(connector, 501n)).toBe(true);
|
|
647
|
+
expect(hasInflightRequest(connector, 502n)).toBe(true);
|
|
648
|
+
expect(reject1).not.toHaveBeenCalled();
|
|
649
|
+
expect(reject2).not.toHaveBeenCalled();
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
describe("RetryConfig", () => {
|
|
653
|
+
test("uses defaults when retry params are not provided", () => {
|
|
654
|
+
const dsn = (0, dsn_1.parse)("ws://root:taosdata@localhost:6041");
|
|
655
|
+
const config = wsConnector_1.RetryConfig.fromDsn(dsn);
|
|
656
|
+
expect(config.retries).toBe(5);
|
|
657
|
+
expect(config.retryBackoffMs).toBe(200);
|
|
658
|
+
expect(config.retryBackoffMaxMs).toBe(2000);
|
|
659
|
+
});
|
|
660
|
+
test("reads retry params from dsn", () => {
|
|
661
|
+
const dsn = (0, dsn_1.parse)("ws://root:taosdata@localhost:6041?retries=7&retry_backoff_ms=150&retry_backoff_max_ms=3200");
|
|
662
|
+
const config = wsConnector_1.RetryConfig.fromDsn(dsn);
|
|
663
|
+
expect(config.retries).toBe(7);
|
|
664
|
+
expect(config.retryBackoffMs).toBe(150);
|
|
665
|
+
expect(config.retryBackoffMaxMs).toBe(3200);
|
|
666
|
+
});
|
|
667
|
+
test("normalizes invalid params to safe defaults", () => {
|
|
668
|
+
const dsn = (0, dsn_1.parse)("ws://root:taosdata@localhost:6041?retries=-1&retry_backoff_ms=abc&retry_backoff_max_ms=0");
|
|
669
|
+
const config = wsConnector_1.RetryConfig.fromDsn(dsn);
|
|
670
|
+
expect(config.retries).toBe(5);
|
|
671
|
+
expect(config.retryBackoffMs).toBe(200);
|
|
672
|
+
expect(config.retryBackoffMaxMs).toBe(2000);
|
|
673
|
+
});
|
|
674
|
+
test("computes exponential backoff and caps at max", () => {
|
|
675
|
+
const config = new wsConnector_1.RetryConfig(3, 100, 350);
|
|
676
|
+
expect(config.getBackoffDelay(0)).toBe(100);
|
|
677
|
+
expect(config.getBackoffDelay(1)).toBe(200);
|
|
678
|
+
expect(config.getBackoffDelay(2)).toBe(350);
|
|
679
|
+
expect(config.getBackoffDelay(10)).toBe(350);
|
|
680
|
+
});
|
|
681
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wsConnector.leastConnections.test.d.ts","sourceRoot":"","sources":["../../../test/client/wsConnector.leastConnections.test.ts"],"names":[],"mappings":""}
|