mqtt-plus 1.4.13 → 1.4.14
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/CHANGELOG.md +5 -0
- package/dst-stage1/mqtt-plus-sink.js +10 -2
- package/dst-stage1/mqtt-plus-source.js +26 -16
- package/dst-stage2/mqtt-plus.cjs.js +35 -18
- package/dst-stage2/mqtt-plus.esm.js +35 -18
- package/dst-stage2/mqtt-plus.umd.js +9 -9
- package/package.json +1 -1
- package/src/mqtt-plus-sink.ts +10 -2
- package/src/mqtt-plus-source.ts +27 -16
package/CHANGELOG.md
CHANGED
|
@@ -115,6 +115,8 @@ export class SinkTrait extends SourceTrait {
|
|
|
115
115
|
/* utility functions for timeout management */
|
|
116
116
|
const pushTimerId = `sink-push-recv:${requestId}`;
|
|
117
117
|
const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
|
|
118
|
+
if (streamEnded)
|
|
119
|
+
return;
|
|
118
120
|
const stream = this.pushStreams.get(requestId);
|
|
119
121
|
if (stream !== undefined)
|
|
120
122
|
stream.destroy(new Error("push stream timeout"));
|
|
@@ -150,6 +152,7 @@ export class SinkTrait extends SourceTrait {
|
|
|
150
152
|
return;
|
|
151
153
|
if (chunkParsed.error !== undefined) {
|
|
152
154
|
streamEnded = true;
|
|
155
|
+
clearPushTimeout();
|
|
153
156
|
readable.destroy(new Error(chunkParsed.error));
|
|
154
157
|
}
|
|
155
158
|
else {
|
|
@@ -161,6 +164,7 @@ export class SinkTrait extends SourceTrait {
|
|
|
161
164
|
}
|
|
162
165
|
if (chunkParsed.final) {
|
|
163
166
|
streamEnded = true;
|
|
167
|
+
clearPushTimeout();
|
|
164
168
|
readable.push(null);
|
|
165
169
|
}
|
|
166
170
|
}
|
|
@@ -286,6 +290,7 @@ export class SinkTrait extends SourceTrait {
|
|
|
286
290
|
let remoteError = false;
|
|
287
291
|
let pushAcked = false;
|
|
288
292
|
let pushFinalized = false;
|
|
293
|
+
let pushDataFinalSent = false;
|
|
289
294
|
let pushFinalizeResolve;
|
|
290
295
|
let pushFinalizeReject;
|
|
291
296
|
const pushFinalize = new Promise((resolve, reject) => {
|
|
@@ -355,6 +360,8 @@ export class SinkTrait extends SourceTrait {
|
|
|
355
360
|
const chunkMsg = this.msg.makeSinkPushChunk(requestId, name, chunk, error, final, this.options.id, receiver);
|
|
356
361
|
const message = this.codec.encode(chunkMsg);
|
|
357
362
|
await this.publishToTopic(chunkTopic, message, { qos: 2, ...options });
|
|
363
|
+
if (error === undefined && final)
|
|
364
|
+
pushDataFinalSent = true;
|
|
358
365
|
};
|
|
359
366
|
/* iterate over all chunks of the buffer */
|
|
360
367
|
if (data instanceof Readable)
|
|
@@ -378,8 +385,9 @@ export class SinkTrait extends SourceTrait {
|
|
|
378
385
|
const error = ensureError(err);
|
|
379
386
|
abortController.abort(error);
|
|
380
387
|
/* send error chunk only if push was acked and error did not originate from receiver
|
|
381
|
-
(before ack, the sink has no chunk handler yet and will time out on its own
|
|
382
|
-
|
|
388
|
+
(before ack, the sink has no chunk handler yet and will time out on its own;
|
|
389
|
+
after final data chunk, no additional terminal chunk should be sent) */
|
|
390
|
+
if (pushAcked && !remoteError && !pushDataFinalSent) {
|
|
383
391
|
const chunkTopic = this.options.topicMake(name, "sink-push-request", receiver);
|
|
384
392
|
const chunkMsg = this.msg.makeSinkPushChunk(requestId, name, undefined, error.message, true, this.options.id, receiver);
|
|
385
393
|
const message = this.codec.encode(chunkMsg);
|
|
@@ -98,6 +98,12 @@ export class SourceTrait extends ServiceTrait {
|
|
|
98
98
|
const message = this.codec.encode(response);
|
|
99
99
|
await this.publishToTopic(responseTopic, message, { qos: options.qos ?? 2 });
|
|
100
100
|
};
|
|
101
|
+
/* create a resource spool for request cleanup */
|
|
102
|
+
const reqSpool = new Spool();
|
|
103
|
+
reqSpool.roll(() => {
|
|
104
|
+
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
105
|
+
this.sourceControllers.delete(requestId);
|
|
106
|
+
});
|
|
101
107
|
/* define abort controller and signal */
|
|
102
108
|
const abortController = new AbortController();
|
|
103
109
|
this.sourceControllers.set(requestId, abortController);
|
|
@@ -117,11 +123,11 @@ export class SourceTrait extends ServiceTrait {
|
|
|
117
123
|
gate.abort();
|
|
118
124
|
this.sourceCreditGates.delete(requestId);
|
|
119
125
|
}
|
|
120
|
-
|
|
121
|
-
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
126
|
+
reqSpool.unroll();
|
|
122
127
|
});
|
|
123
128
|
const clearSourceTimeout = () => this.timerClear(sourceTimerId);
|
|
124
129
|
refreshSourceTimeout();
|
|
130
|
+
reqSpool.roll(() => { clearSourceTimeout(); });
|
|
125
131
|
/* callback for creating and sending a chunk message */
|
|
126
132
|
const sendChunk = async (chunk, error, final) => {
|
|
127
133
|
refreshSourceTimeout();
|
|
@@ -132,6 +138,7 @@ export class SourceTrait extends ServiceTrait {
|
|
|
132
138
|
/* call the handler callback */
|
|
133
139
|
let ackSent = false;
|
|
134
140
|
let creditGate;
|
|
141
|
+
let cancelledByFetcher = false;
|
|
135
142
|
try {
|
|
136
143
|
if (topicName !== request.name)
|
|
137
144
|
throw new Error(`source name mismatch (topic: "${topicName}", payload: "${request.name}")`);
|
|
@@ -143,12 +150,18 @@ export class SourceTrait extends ServiceTrait {
|
|
|
143
150
|
const initialCredit = request.credit;
|
|
144
151
|
creditGate = (initialCredit !== undefined && initialCredit > 0)
|
|
145
152
|
? new CreditGate(initialCredit) : undefined;
|
|
146
|
-
if (creditGate)
|
|
153
|
+
if (creditGate) {
|
|
147
154
|
this.sourceCreditGates.set(requestId, creditGate);
|
|
155
|
+
reqSpool.roll(() => {
|
|
156
|
+
creditGate.abort();
|
|
157
|
+
this.sourceCreditGates.delete(requestId);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
148
160
|
/* register credit/cancel handler (unconditional for cancel support) */
|
|
149
161
|
this.onResponse.set(`source-fetch-credit:${requestId}`, (creditParsed) => {
|
|
150
162
|
if (creditParsed.credit === 0) {
|
|
151
163
|
/* cancel signal from fetcher */
|
|
164
|
+
cancelledByFetcher = true;
|
|
152
165
|
abortController.abort(new Error(`source fetch "${name}" cancelled by fetcher`));
|
|
153
166
|
return;
|
|
154
167
|
}
|
|
@@ -178,22 +191,19 @@ export class SourceTrait extends ServiceTrait {
|
|
|
178
191
|
/* cleanup stream resource (if provided by handler) */
|
|
179
192
|
const error = ensureError(err, `handler for source "${name}" failed`);
|
|
180
193
|
abortController.abort(error);
|
|
181
|
-
/*
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
194
|
+
/* on explicit fetcher cancellation, abort silently without emitting error responses */
|
|
195
|
+
if (!cancelledByFetcher) {
|
|
196
|
+
/* send error as nak response or as error chunk */
|
|
197
|
+
this.error(error);
|
|
198
|
+
if (ackSent)
|
|
199
|
+
await sendChunk(undefined, error.message, true).catch(() => { });
|
|
200
|
+
else
|
|
201
|
+
await sendResponse(error.message).catch(() => { });
|
|
202
|
+
}
|
|
187
203
|
}
|
|
188
204
|
finally {
|
|
189
205
|
/* cleanup resources */
|
|
190
|
-
|
|
191
|
-
if (creditGate) {
|
|
192
|
-
creditGate.abort();
|
|
193
|
-
this.sourceCreditGates.delete(requestId);
|
|
194
|
-
}
|
|
195
|
-
this.sourceControllers.delete(requestId);
|
|
196
|
-
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
206
|
+
await reqSpool.unroll();
|
|
197
207
|
}
|
|
198
208
|
});
|
|
199
209
|
spool.roll(() => { this.onRequest.delete(`source-fetch-request:${name}`); });
|
|
@@ -1626,6 +1626,11 @@ class SourceTrait extends ServiceTrait {
|
|
|
1626
1626
|
const message = this.codec.encode(response);
|
|
1627
1627
|
await this.publishToTopic(responseTopic, message, { qos: options.qos ?? 2 });
|
|
1628
1628
|
};
|
|
1629
|
+
const reqSpool = new Spool();
|
|
1630
|
+
reqSpool.roll(() => {
|
|
1631
|
+
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
1632
|
+
this.sourceControllers.delete(requestId);
|
|
1633
|
+
});
|
|
1629
1634
|
const abortController = new AbortController();
|
|
1630
1635
|
this.sourceControllers.set(requestId, abortController);
|
|
1631
1636
|
const abortSignal = abortController.signal;
|
|
@@ -1642,11 +1647,13 @@ class SourceTrait extends ServiceTrait {
|
|
|
1642
1647
|
gate.abort();
|
|
1643
1648
|
this.sourceCreditGates.delete(requestId);
|
|
1644
1649
|
}
|
|
1645
|
-
|
|
1646
|
-
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
1650
|
+
reqSpool.unroll();
|
|
1647
1651
|
});
|
|
1648
1652
|
const clearSourceTimeout = () => this.timerClear(sourceTimerId);
|
|
1649
1653
|
refreshSourceTimeout();
|
|
1654
|
+
reqSpool.roll(() => {
|
|
1655
|
+
clearSourceTimeout();
|
|
1656
|
+
});
|
|
1650
1657
|
const sendChunk = async (chunk, error, final) => {
|
|
1651
1658
|
refreshSourceTimeout();
|
|
1652
1659
|
const chunkMsg = this.msg.makeSourceFetchChunk(requestId, name, chunk, error, final, this.options.id, sender);
|
|
@@ -1655,6 +1662,7 @@ class SourceTrait extends ServiceTrait {
|
|
|
1655
1662
|
};
|
|
1656
1663
|
let ackSent = false;
|
|
1657
1664
|
let creditGate;
|
|
1665
|
+
let cancelledByFetcher = false;
|
|
1658
1666
|
try {
|
|
1659
1667
|
if (topicName !== request.name)
|
|
1660
1668
|
throw new Error(`source name mismatch (topic: "${topicName}", payload: "${request.name}")`);
|
|
@@ -1664,10 +1672,16 @@ class SourceTrait extends ServiceTrait {
|
|
|
1664
1672
|
throw new Error(`source "${name}" failed authentication`);
|
|
1665
1673
|
const initialCredit = request.credit;
|
|
1666
1674
|
creditGate = initialCredit !== void 0 && initialCredit > 0 ? new CreditGate(initialCredit) : void 0;
|
|
1667
|
-
if (creditGate)
|
|
1675
|
+
if (creditGate) {
|
|
1668
1676
|
this.sourceCreditGates.set(requestId, creditGate);
|
|
1677
|
+
reqSpool.roll(() => {
|
|
1678
|
+
creditGate.abort();
|
|
1679
|
+
this.sourceCreditGates.delete(requestId);
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1669
1682
|
this.onResponse.set(`source-fetch-credit:${requestId}`, (creditParsed) => {
|
|
1670
1683
|
if (creditParsed.credit === 0) {
|
|
1684
|
+
cancelledByFetcher = true;
|
|
1671
1685
|
abortController.abort(new Error(`source fetch "${name}" cancelled by fetcher`));
|
|
1672
1686
|
return;
|
|
1673
1687
|
}
|
|
@@ -1690,21 +1704,17 @@ class SourceTrait extends ServiceTrait {
|
|
|
1690
1704
|
} catch (err) {
|
|
1691
1705
|
const error = ensureError(err, `handler for source "${name}" failed`);
|
|
1692
1706
|
abortController.abort(error);
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
clearSourceTimeout();
|
|
1702
|
-
if (creditGate) {
|
|
1703
|
-
creditGate.abort();
|
|
1704
|
-
this.sourceCreditGates.delete(requestId);
|
|
1707
|
+
if (!cancelledByFetcher) {
|
|
1708
|
+
this.error(error);
|
|
1709
|
+
if (ackSent)
|
|
1710
|
+
await sendChunk(void 0, error.message, true).catch(() => {
|
|
1711
|
+
});
|
|
1712
|
+
else
|
|
1713
|
+
await sendResponse(error.message).catch(() => {
|
|
1714
|
+
});
|
|
1705
1715
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1716
|
+
} finally {
|
|
1717
|
+
await reqSpool.unroll();
|
|
1708
1718
|
}
|
|
1709
1719
|
});
|
|
1710
1720
|
spool.roll(() => {
|
|
@@ -1929,6 +1939,8 @@ class SinkTrait extends SourceTrait {
|
|
|
1929
1939
|
} : void 0;
|
|
1930
1940
|
const pushTimerId = `sink-push-recv:${requestId}`;
|
|
1931
1941
|
const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
|
|
1942
|
+
if (streamEnded)
|
|
1943
|
+
return;
|
|
1932
1944
|
const stream = this.pushStreams.get(requestId);
|
|
1933
1945
|
if (stream !== void 0)
|
|
1934
1946
|
stream.destroy(new Error("push stream timeout"));
|
|
@@ -1965,6 +1977,7 @@ class SinkTrait extends SourceTrait {
|
|
|
1965
1977
|
return;
|
|
1966
1978
|
if (chunkParsed.error !== void 0) {
|
|
1967
1979
|
streamEnded = true;
|
|
1980
|
+
clearPushTimeout();
|
|
1968
1981
|
readable.destroy(new Error(chunkParsed.error));
|
|
1969
1982
|
} else {
|
|
1970
1983
|
refreshPushTimeout();
|
|
@@ -1975,6 +1988,7 @@ class SinkTrait extends SourceTrait {
|
|
|
1975
1988
|
}
|
|
1976
1989
|
if (chunkParsed.final) {
|
|
1977
1990
|
streamEnded = true;
|
|
1991
|
+
clearPushTimeout();
|
|
1978
1992
|
readable.push(null);
|
|
1979
1993
|
}
|
|
1980
1994
|
}
|
|
@@ -2087,6 +2101,7 @@ class SinkTrait extends SourceTrait {
|
|
|
2087
2101
|
let remoteError = false;
|
|
2088
2102
|
let pushAcked = false;
|
|
2089
2103
|
let pushFinalized = false;
|
|
2104
|
+
let pushDataFinalSent = false;
|
|
2090
2105
|
let pushFinalizeResolve;
|
|
2091
2106
|
let pushFinalizeReject;
|
|
2092
2107
|
const pushFinalize = new Promise((resolve, reject) => {
|
|
@@ -2158,6 +2173,8 @@ class SinkTrait extends SourceTrait {
|
|
|
2158
2173
|
const chunkMsg = this.msg.makeSinkPushChunk(requestId, name, chunk, error, final, this.options.id, receiver);
|
|
2159
2174
|
const message = this.codec.encode(chunkMsg);
|
|
2160
2175
|
await this.publishToTopic(chunkTopic, message, { qos: 2, ...options });
|
|
2176
|
+
if (error === void 0 && final)
|
|
2177
|
+
pushDataFinalSent = true;
|
|
2161
2178
|
};
|
|
2162
2179
|
if (data instanceof node_stream.Readable)
|
|
2163
2180
|
await sendStreamAsChunks(data, this.options.chunkSize, sendChunk, creditGate, abortSignal);
|
|
@@ -2177,7 +2194,7 @@ class SinkTrait extends SourceTrait {
|
|
|
2177
2194
|
} catch (err) {
|
|
2178
2195
|
const error = ensureError(err);
|
|
2179
2196
|
abortController.abort(error);
|
|
2180
|
-
if (pushAcked && !remoteError) {
|
|
2197
|
+
if (pushAcked && !remoteError && !pushDataFinalSent) {
|
|
2181
2198
|
const chunkTopic = this.options.topicMake(name, "sink-push-request", receiver);
|
|
2182
2199
|
const chunkMsg = this.msg.makeSinkPushChunk(requestId, name, void 0, error.message, true, this.options.id, receiver);
|
|
2183
2200
|
const message = this.codec.encode(chunkMsg);
|
|
@@ -1605,6 +1605,11 @@ class SourceTrait extends ServiceTrait {
|
|
|
1605
1605
|
const message = this.codec.encode(response);
|
|
1606
1606
|
await this.publishToTopic(responseTopic, message, { qos: options.qos ?? 2 });
|
|
1607
1607
|
};
|
|
1608
|
+
const reqSpool = new Spool();
|
|
1609
|
+
reqSpool.roll(() => {
|
|
1610
|
+
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
1611
|
+
this.sourceControllers.delete(requestId);
|
|
1612
|
+
});
|
|
1608
1613
|
const abortController = new AbortController();
|
|
1609
1614
|
this.sourceControllers.set(requestId, abortController);
|
|
1610
1615
|
const abortSignal = abortController.signal;
|
|
@@ -1621,11 +1626,13 @@ class SourceTrait extends ServiceTrait {
|
|
|
1621
1626
|
gate.abort();
|
|
1622
1627
|
this.sourceCreditGates.delete(requestId);
|
|
1623
1628
|
}
|
|
1624
|
-
|
|
1625
|
-
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
1629
|
+
reqSpool.unroll();
|
|
1626
1630
|
});
|
|
1627
1631
|
const clearSourceTimeout = () => this.timerClear(sourceTimerId);
|
|
1628
1632
|
refreshSourceTimeout();
|
|
1633
|
+
reqSpool.roll(() => {
|
|
1634
|
+
clearSourceTimeout();
|
|
1635
|
+
});
|
|
1629
1636
|
const sendChunk = async (chunk, error, final) => {
|
|
1630
1637
|
refreshSourceTimeout();
|
|
1631
1638
|
const chunkMsg = this.msg.makeSourceFetchChunk(requestId, name, chunk, error, final, this.options.id, sender);
|
|
@@ -1634,6 +1641,7 @@ class SourceTrait extends ServiceTrait {
|
|
|
1634
1641
|
};
|
|
1635
1642
|
let ackSent = false;
|
|
1636
1643
|
let creditGate;
|
|
1644
|
+
let cancelledByFetcher = false;
|
|
1637
1645
|
try {
|
|
1638
1646
|
if (topicName !== request.name)
|
|
1639
1647
|
throw new Error(`source name mismatch (topic: "${topicName}", payload: "${request.name}")`);
|
|
@@ -1643,10 +1651,16 @@ class SourceTrait extends ServiceTrait {
|
|
|
1643
1651
|
throw new Error(`source "${name}" failed authentication`);
|
|
1644
1652
|
const initialCredit = request.credit;
|
|
1645
1653
|
creditGate = initialCredit !== void 0 && initialCredit > 0 ? new CreditGate(initialCredit) : void 0;
|
|
1646
|
-
if (creditGate)
|
|
1654
|
+
if (creditGate) {
|
|
1647
1655
|
this.sourceCreditGates.set(requestId, creditGate);
|
|
1656
|
+
reqSpool.roll(() => {
|
|
1657
|
+
creditGate.abort();
|
|
1658
|
+
this.sourceCreditGates.delete(requestId);
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1648
1661
|
this.onResponse.set(`source-fetch-credit:${requestId}`, (creditParsed) => {
|
|
1649
1662
|
if (creditParsed.credit === 0) {
|
|
1663
|
+
cancelledByFetcher = true;
|
|
1650
1664
|
abortController.abort(new Error(`source fetch "${name}" cancelled by fetcher`));
|
|
1651
1665
|
return;
|
|
1652
1666
|
}
|
|
@@ -1669,21 +1683,17 @@ class SourceTrait extends ServiceTrait {
|
|
|
1669
1683
|
} catch (err) {
|
|
1670
1684
|
const error = ensureError(err, `handler for source "${name}" failed`);
|
|
1671
1685
|
abortController.abort(error);
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
clearSourceTimeout();
|
|
1681
|
-
if (creditGate) {
|
|
1682
|
-
creditGate.abort();
|
|
1683
|
-
this.sourceCreditGates.delete(requestId);
|
|
1686
|
+
if (!cancelledByFetcher) {
|
|
1687
|
+
this.error(error);
|
|
1688
|
+
if (ackSent)
|
|
1689
|
+
await sendChunk(void 0, error.message, true).catch(() => {
|
|
1690
|
+
});
|
|
1691
|
+
else
|
|
1692
|
+
await sendResponse(error.message).catch(() => {
|
|
1693
|
+
});
|
|
1684
1694
|
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1695
|
+
} finally {
|
|
1696
|
+
await reqSpool.unroll();
|
|
1687
1697
|
}
|
|
1688
1698
|
});
|
|
1689
1699
|
spool.roll(() => {
|
|
@@ -1908,6 +1918,8 @@ class SinkTrait extends SourceTrait {
|
|
|
1908
1918
|
} : void 0;
|
|
1909
1919
|
const pushTimerId = `sink-push-recv:${requestId}`;
|
|
1910
1920
|
const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
|
|
1921
|
+
if (streamEnded)
|
|
1922
|
+
return;
|
|
1911
1923
|
const stream = this.pushStreams.get(requestId);
|
|
1912
1924
|
if (stream !== void 0)
|
|
1913
1925
|
stream.destroy(new Error("push stream timeout"));
|
|
@@ -1944,6 +1956,7 @@ class SinkTrait extends SourceTrait {
|
|
|
1944
1956
|
return;
|
|
1945
1957
|
if (chunkParsed.error !== void 0) {
|
|
1946
1958
|
streamEnded = true;
|
|
1959
|
+
clearPushTimeout();
|
|
1947
1960
|
readable.destroy(new Error(chunkParsed.error));
|
|
1948
1961
|
} else {
|
|
1949
1962
|
refreshPushTimeout();
|
|
@@ -1954,6 +1967,7 @@ class SinkTrait extends SourceTrait {
|
|
|
1954
1967
|
}
|
|
1955
1968
|
if (chunkParsed.final) {
|
|
1956
1969
|
streamEnded = true;
|
|
1970
|
+
clearPushTimeout();
|
|
1957
1971
|
readable.push(null);
|
|
1958
1972
|
}
|
|
1959
1973
|
}
|
|
@@ -2066,6 +2080,7 @@ class SinkTrait extends SourceTrait {
|
|
|
2066
2080
|
let remoteError = false;
|
|
2067
2081
|
let pushAcked = false;
|
|
2068
2082
|
let pushFinalized = false;
|
|
2083
|
+
let pushDataFinalSent = false;
|
|
2069
2084
|
let pushFinalizeResolve;
|
|
2070
2085
|
let pushFinalizeReject;
|
|
2071
2086
|
const pushFinalize = new Promise((resolve, reject) => {
|
|
@@ -2137,6 +2152,8 @@ class SinkTrait extends SourceTrait {
|
|
|
2137
2152
|
const chunkMsg = this.msg.makeSinkPushChunk(requestId, name, chunk, error, final, this.options.id, receiver);
|
|
2138
2153
|
const message = this.codec.encode(chunkMsg);
|
|
2139
2154
|
await this.publishToTopic(chunkTopic, message, { qos: 2, ...options });
|
|
2155
|
+
if (error === void 0 && final)
|
|
2156
|
+
pushDataFinalSent = true;
|
|
2140
2157
|
};
|
|
2141
2158
|
if (data instanceof Readable)
|
|
2142
2159
|
await sendStreamAsChunks(data, this.options.chunkSize, sendChunk, creditGate, abortSignal);
|
|
@@ -2156,7 +2173,7 @@ class SinkTrait extends SourceTrait {
|
|
|
2156
2173
|
} catch (err) {
|
|
2157
2174
|
const error = ensureError(err);
|
|
2158
2175
|
abortController.abort(error);
|
|
2159
|
-
if (pushAcked && !remoteError) {
|
|
2176
|
+
if (pushAcked && !remoteError && !pushDataFinalSent) {
|
|
2160
2177
|
const chunkTopic = this.options.topicMake(name, "sink-push-request", receiver);
|
|
2161
2178
|
const chunkMsg = this.msg.makeSinkPushChunk(requestId, name, void 0, error.message, true, this.options.id, receiver);
|
|
2162
2179
|
const message = this.codec.encode(chunkMsg);
|