Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make xhr transports resilient to onmessage errors #564

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions lib/transport/receiver/jsonp.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,16 @@ JsonpReceiver.prototype._callback = function(data) {
return;
}

if (data) {
debug('message', data);
this.emit('message', data);
try {
if (data) {
debug('message', data);
this.emit('message', data);
}
} finally {
// Must clean up even if 'message' event handlers throw
this.emit('close', null, 'network');
this.removeAllListeners();
}
this.emit('close', null, 'network');
this.removeAllListeners();
};

JsonpReceiver.prototype._abort = function(err) {
Expand Down
15 changes: 11 additions & 4 deletions lib/transport/receiver/xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,23 @@ XhrReceiver.prototype._chunkHandler = function(status, text) {
return;
}

for (var idx = -1; ; this.bufferPosition += idx + 1) {
for (var idx = -1; ; ) {
var buf = text.slice(this.bufferPosition);
idx = buf.indexOf('\n');
if (idx === -1) {
break;
}
var msg = buf.slice(0, idx);
if (msg) {
debug('message', msg);
this.emit('message', msg);
try {
if (msg) {
debug('message', msg);
this.emit('message', msg);
}
} finally {
// The bufferPosition must advance even if 'message' handler
// throws an exception, otherwise the same message will be
// replayed the next time _chunkHandler is called.
this.bufferPosition += idx + 1;
}
}
};
Expand Down
65 changes: 64 additions & 1 deletion tests/lib/receivers.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,32 @@ describe('Receivers', function () {
});
});

it('survives errors in handlers', function (done) {
var test = this.runnable();
JsonpReceiver.prototype._createScript = function () {
var self = this;
setTimeout(function () {
try {
global[utils.WPrefix][self.id]('datadata');
} catch (e) {
if (!(test.timedOut || test.duration)) {
done(new Error('close event not fired'));
}
}
}, 5);
};
var jpr = new JsonpReceiver('test');
jpr.on('close', function () {
if (test.timedOut || test.duration) {
return;
}
done();
});
jpr.on('message', function () {
throw new Error('boom');
});
});

it('will timeout', function (done) {
this.timeout(500);
var test = this.runnable();
Expand Down Expand Up @@ -226,14 +252,15 @@ describe('Receivers', function () {
return;
}
try {
expect(i).to.equal(3);
expect(reason).to.equal('network');
} catch (e) {
done(e);
return;
}
done();
});
xhr._chunkHandler(200, 'test\nmultiple\nlines');
xhr._chunkHandler(200, 'test\nmultiple\nlines\n');
});

it('emits no messages for an empty string response', function (done) {
Expand Down Expand Up @@ -287,5 +314,41 @@ describe('Receivers', function () {
});
xhr.abort();
});

it('survives errors in message handlers', function (done) {
var test = this.runnable();
var xhr = new XhrReceiver('test', XhrFake);
var messages = [];
xhr.on('message', function (msg) {
messages.push(msg);
if (msg === 'one') {
throw new Error('boom');
}
});
xhr.on('close', function (code, reason) {
if (test.timedOut || test.duration) {
return;
}
try {
expect(messages).to.eql(['one', 'two', 'three']);
} catch (e) {
done(e);
return;
}
done();
});
try {
xhr._chunkHandler(200, 'one\n');
} catch (e) {
// This is expected
}
try {
xhr._chunkHandler(200, 'one\ntwo\nthree\n');
} catch (e) {
// This is NOT expected, but let the error be reported in the close handler
// instead of here
}
xhr.abort();
});
});
});