Skip to content

Commit 71db931

Browse files
committed
fix: undefined stream.destroy call
To end streaming in case of error, we were calling an unofficial method of the stream API which was removed and does not exist in the version of node we use. The method is re-added officially in node v.8 but until we upgrade we need to destroy the streams manually, by pushing null for readables and calling stream.end() for writables.
1 parent 1270412 commit 71db931

File tree

3 files changed

+88
-6
lines changed

3 files changed

+88
-6
lines changed

lib/s3middleware/azureHelpers/SubStreamInterface.js

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
const stream = require('stream');
22

3+
class SubStream extends stream.PassThrough {
4+
constructor(options) {
5+
super(options);
6+
7+
this.on('stopStreamingToAzure', function stopStreamingToAzure() {
8+
this._abortStreaming();
9+
});
10+
}
11+
12+
_abortStreaming() {
13+
this.push(null);
14+
this.end();
15+
}
16+
}
17+
318
/**
419
* Interface for streaming subparts.
520
* @class SubStreamInterface
@@ -14,7 +29,8 @@ class SubStreamInterface {
1429
this._totalLengthCounter = 0;
1530
this._lengthCounter = 0;
1631
this._subPartIndex = 0;
17-
this._currentStream = new stream.PassThrough();
32+
this._currentStream = new SubStream();
33+
this._streamingAborted = false;
1834
}
1935

2036
/**
@@ -51,12 +67,11 @@ class SubStreamInterface {
5167
* @return {undefined}
5268
*/
5369
stopStreaming(piper) {
70+
this._streamingAborted = true;
5471
if (piper) {
5572
piper.unpipe();
56-
piper.destroy();
5773
}
58-
this._sourceStream.destroy();
59-
this._currentStream.destroy();
74+
this._currentStream.emit('stopStreamingToAzure');
6075
}
6176

6277
/**
@@ -97,7 +112,7 @@ class SubStreamInterface {
97112
this._totalLengthCounter += this._lengthCounter;
98113
this._lengthCounter = 0;
99114
this._subPartIndex++;
100-
this._currentStream = new stream.PassThrough();
115+
this._currentStream = new SubStream();
101116
this.resumeStreaming();
102117
return {
103118
nextStream: this._currentStream,
@@ -111,6 +126,10 @@ class SubStreamInterface {
111126
* @return {undefined}
112127
*/
113128
write(chunk) {
129+
if (this._streamingAborted) {
130+
// don't write
131+
return;
132+
}
114133
const ready = this._currentStream.write(chunk);
115134

116135
if (!ready) {

lib/s3middleware/azureHelpers/mpuUtils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ dataStoreName, log, cb) => {
151151
'putting multiple parts');
152152

153153
resultsCollector.on('error', (err, subPartIndex) => {
154-
streamInterface.stopStreaming(request);
155154
log.error(`Error putting subpart to Azure: ${subPartIndex}`,
156155
{ error: err.message, dataStoreName });
156+
streamInterface.stopStreaming(request);
157157
if (err.code === 'ContainerNotFound') {
158158
return cb(errors.NoSuchBucket);
159159
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const assert = require('assert');
2+
const stream = require('stream');
3+
const SubStreamInterface =
4+
require('../../../../lib/s3middleware/azureHelpers/SubStreamInterface');
5+
6+
describe('s3middleware SubStreamInterface.stopStreaming()', () => {
7+
const eventsEmitted = {
8+
sourceStreamUnpiped: false,
9+
currentStreamStopStreamingToAzure: false,
10+
currentStreamEnded: false,
11+
};
12+
const expectedSequence = {
13+
sourceStreamUnpiped: 0,
14+
currentStreamStopStreamingToAzure: 1,
15+
currentStreamEnded: 2,
16+
};
17+
const data = Buffer.alloc(100);
18+
let dataMarker = 0;
19+
let eventSequence = 0;
20+
const mockRequest = new stream.Readable({
21+
read: () => {
22+
if (dataMarker >= data.length) {
23+
return mockRequest.push(null);
24+
}
25+
mockRequest.push(data.slice(dataMarker, dataMarker + 1));
26+
dataMarker += 1;
27+
return undefined;
28+
},
29+
});
30+
const sourceStream = new stream.PassThrough();
31+
const subStreamInterface = new SubStreamInterface(sourceStream);
32+
sourceStream.on('unpipe', () => {
33+
eventsEmitted.sourceStreamUnpiped = eventSequence++;
34+
});
35+
subStreamInterface._currentStream.on('stopStreamingToAzure', () => {
36+
eventsEmitted.currentStreamStopStreamingToAzure = eventSequence++;
37+
});
38+
subStreamInterface._currentStream.on('finish', () => {
39+
eventsEmitted.currentStreamEnded = eventSequence++;
40+
});
41+
it('should stop streaming data and end current stream', done => {
42+
sourceStream.on('data', chunk => {
43+
const currentLength = subStreamInterface.getLengthCounter();
44+
if (currentLength === 10) {
45+
Object.keys(eventsEmitted).forEach(key => {
46+
assert.strictEqual(eventsEmitted[key], false);
47+
});
48+
assert.strictEqual(mockRequest._readableState.pipesCount, 1);
49+
return subStreamInterface.stopStreaming(mockRequest);
50+
}
51+
return subStreamInterface.write(chunk);
52+
});
53+
mockRequest.pipe(sourceStream);
54+
setTimeout(() => {
55+
Object.keys(eventsEmitted).forEach(key => {
56+
assert.strictEqual(eventsEmitted[key], expectedSequence[key]);
57+
});
58+
assert.strictEqual(subStreamInterface.getLengthCounter(), 10);
59+
assert.strictEqual(mockRequest._readableState.pipesCount, 0);
60+
return done();
61+
}, 1000);
62+
});
63+
});

0 commit comments

Comments
 (0)