Skip to content

Commit

Permalink
[js] Fix a bug that causes callbacks to be dropped after recovering f…
Browse files Browse the repository at this point in the history
…rom an

unhandled promise rejection

Fixes #2636
  • Loading branch information
jleyba committed Aug 21, 2016
1 parent 8b6ee4d commit b7cea82
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 17 deletions.
6 changes: 6 additions & 0 deletions javascript/node/selenium-webdriver/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## v.next

* Fixed a bug where the promise manager would silently drop callbacks after
recovering from an unhandled promise rejection.


## v3.0.0-beta-2

### API Changes
Expand Down
6 changes: 6 additions & 0 deletions javascript/node/selenium-webdriver/lib/promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -2670,6 +2670,12 @@ class TaskQueue extends events.EventEmitter {
this.tasks_ = [];
}

// Now that all of the remaining tasks have been silently cancelled (e.g. no
// exisitng callbacks on those tasks will fire), clear the silence bit on
// the cancellation error. This ensures additional callbacks registered in
// the future will actually execute.
cancellation.silent_ = false;

if (this.pending_) {
vlog(2, () => this + '.abort(); cancelling pending task', this);
this.pending_.task.promise.cancel(
Expand Down
106 changes: 89 additions & 17 deletions javascript/node/selenium-webdriver/test/lib/promise_error_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,25 +564,97 @@ describe('promise error handling', function() {
});
});

it('testFrameCancelsRemainingTasks_onUnhandledTaskFailure', function() {
var run = false;
return flow.execute(function() {
flow.execute(throwStubError);
flow.execute(function() { run = true; });
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.ok(!run);
describe('frame cancels remaining tasks', function() {
it('on unhandled task failure', function() {
var run = false;
return flow.execute(function() {
flow.execute(throwStubError);
flow.execute(function() { run = true; });
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.ok(!run);
});
});
});

it('testFrameCancelsRemainingTasks_onUnhandledPromiseRejection', function() {
var run = false;
return flow.execute(function() {
promise.rejected(new StubError);
flow.execute(function() { run = true; });
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.ok(!run);
it('on unhandled promise rejection', function() {
var run = false;
return flow.execute(function() {
promise.rejected(new StubError);
flow.execute(function() { run = true; });
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.ok(!run);
});
});

it('if task throws', function() {
var run = false;
return flow.execute(function() {
flow.execute(function() { run = true; });
throw new StubError;
}).then(assert.fail, function(e) {
assertIsStubError(e);
assert.ok(!run);
});
});

describe('task callbacks scheduled in another frame', function() {
flow = promise.controlFlow();
function noop() {}

let subTask;

before(function() {
flow.execute(function() {
// This task will be discarded and never run because of the error below.
subTask = flow.execute(() => 'abc');
throw new StubError('stub');
}).catch(noop);
});

function assertCancellation(e) {
assert.ok(e instanceof promise.CancellationError);
assert.equal(
'Task was discarded due to a previous failure: stub', e.message);
}

it('are rejected with cancellation error', function() {
let result;
return Promise.resolve().then(function() {
return flow.execute(function() {
result = subTask.then(assert.fail);
});
})
.then(() => result)
.then(assert.fail, assertCancellation);
});

it('cancellation errors propagate through callbacks (1)', function() {
let result;
return Promise.resolve().then(function() {
return flow.execute(function() {
result = subTask
.then(assert.fail, assertCancellation)
.then(() => 'abc123');
});
})
.then(() => result)
.then(value => assert.equal('abc123', value));
});

it('cancellation errors propagate through callbacks (2)', function() {
let result;
return Promise.resolve().then(function() {
return flow.execute(function() {
result = subTask.then(assert.fail)
.then(noop, assertCancellation)
.then(() => 'fin');
});
})
// Verify result actually computed successfully all the way through.
.then(() => result)
.then(value => assert.equal('fin', value));
});
});
});

Expand Down

0 comments on commit b7cea82

Please sign in to comment.