Skip to content

Commit cc506f1

Browse files
committed
Fix potential control flow deadlock
If a task function returns a promise, but schedules no sub-tasks, wait for the promise to resolve as there may be tasks scheduled in the callback chain. Fixes issue 8094
1 parent ecfefd3 commit cc506f1

File tree

2 files changed

+108
-6
lines changed

2 files changed

+108
-6
lines changed

javascript/webdriver/promise.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,7 +1772,7 @@ promise.ControlFlow.prototype.getNextTask_ = function() {
17721772
var frame = this.activeFrame_;
17731773
var firstChild = frame.getFirstChild();
17741774
if (!firstChild) {
1775-
if (!frame.pendingCallback) {
1775+
if (!frame.pendingCallback && !frame.isBlocked_) {
17761776
this.resolveFrame_(frame);
17771777
}
17781778
return null;
@@ -1894,13 +1894,14 @@ promise.ControlFlow.prototype.runInFrame_ = function(
18941894
return;
18951895
}
18961896

1897-
// If the executed function was a task and the result is a promise, wait
1898-
// for the promise to resolve. If there is nothing scheduled, go ahead and
1899-
// discard the frame. Otherwise, we wait for the frame to be closed out
1900-
// by the event loop.
1897+
// If the executed function returned a promise, wait for it to resolve. If
1898+
// there is nothing scheduled in the frame, go ahead and discard it.
1899+
// Otherwise, we wait for the frame to be closed out by the event loop.
19011900
var shortCircuitTask;
1902-
if (opt_isTask && promise.isPromise(result)) {
1901+
if (promise.isPromise(result)) {
1902+
newFrame.isBlocked_ = true;
19031903
var onResolve = function() {
1904+
newFrame.isBlocked_ = false;
19041905
shortCircuitTask = new promise.MicroTask_(function() {
19051906
if (isCloseable(newFrame)) {
19061907
removeNewFrame();
@@ -1919,6 +1920,9 @@ promise.ControlFlow.prototype.runInFrame_ = function(
19191920

19201921
newFrame.once(promise.Frame_.CLOSE_EVENT, function() {
19211922
shortCircuitTask && shortCircuitTask.cancel();
1923+
if (isCloseable(newFrame)) {
1924+
removeNewFrame();
1925+
}
19221926
callback(result);
19231927
}).once(promise.Frame_.ERROR_EVENT, function(reason) {
19241928
shortCircuitTask && shortCircuitTask.cancel();
@@ -2128,6 +2132,13 @@ promise.Frame_ = goog.defineClass(webdriver.EventEmitter, {
21282132
*/
21292133
this.isLocked_ = false;
21302134

2135+
/**
2136+
* Whether this frame's completion is blocked on the resolution of a promise
2137+
* returned by its main function.
2138+
* @private
2139+
*/
2140+
this.isBlocked_ = false;
2141+
21312142
/**
21322143
* Whether this frame represents a pending callback attached to a
21332144
* {@link promise.Promise}.

javascript/webdriver/test/promise_flow_test.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,38 @@ function testFraming_lotsOfNesting() {
338338
}
339339

340340

341+
function testFrame_callbackReturnsPromiseThatDependsOnATask_1() {
342+
schedule('a').then(function() {
343+
schedule('b');
344+
return webdriver.promise.delayed(5).then(function() {
345+
return schedule('c');
346+
});
347+
});
348+
schedule('d');
349+
350+
return waitForIdle().then(function() {
351+
assertFlowHistory('a', 'b', 'c', 'd');
352+
});
353+
}
354+
355+
356+
function testFrame_callbackReturnsPromiseThatDependsOnATask_2() {
357+
schedule('a').then(function() {
358+
schedule('b');
359+
return webdriver.promise.delayed(5).
360+
then(function() { return webdriver.promise.delayed(5) }).
361+
then(function() { return webdriver.promise.delayed(5) }).
362+
then(function() { return webdriver.promise.delayed(5) }).
363+
then(function() { return schedule('c'); });
364+
});
365+
schedule('d');
366+
367+
return waitForIdle().then(function() {
368+
assertFlowHistory('a', 'b', 'c', 'd');
369+
});
370+
}
371+
372+
341373
function testFraming_eachCallbackWaitsForAllScheduledTasksToComplete() {
342374
schedule('a').
343375
then(function() {
@@ -1293,8 +1325,67 @@ function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_2() {
12931325
}
12941326

12951327

1328+
function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_3() {
1329+
scheduleAction('a', function() {
1330+
return webdriver.promise.delayed(10).then(function() {
1331+
return schedule('b');
1332+
});
1333+
});
1334+
schedule('c');
1335+
return waitForIdle().then(function() {
1336+
assertFlowHistory('a', 'b', 'c');
1337+
});
1338+
}
12961339

12971340

1341+
function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_4() {
1342+
scheduleAction('a', function() {
1343+
return webdriver.promise.delayed(5).then(function() {
1344+
return webdriver.promise.delayed(5).then(function() {
1345+
return schedule('b');
1346+
});
1347+
});
1348+
});
1349+
schedule('c');
1350+
return waitForIdle().then(function() {
1351+
assertFlowHistory('a', 'b', 'c');
1352+
});
1353+
}
1354+
1355+
1356+
function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_5() {
1357+
scheduleAction('a', function() {
1358+
return webdriver.promise.delayed(5).then(function() {
1359+
return webdriver.promise.delayed(5).then(function() {
1360+
return webdriver.promise.delayed(5).then(function() {
1361+
return webdriver.promise.delayed(5).then(function() {
1362+
return schedule('b');
1363+
});
1364+
});
1365+
});
1366+
});
1367+
});
1368+
schedule('c');
1369+
return waitForIdle().then(function() {
1370+
assertFlowHistory('a', 'b', 'c');
1371+
});
1372+
}
1373+
1374+
1375+
function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_6() {
1376+
scheduleAction('a', function() {
1377+
return webdriver.promise.delayed(5).
1378+
then(function() { return webdriver.promise.delayed(5) }).
1379+
then(function() { return webdriver.promise.delayed(5) }).
1380+
then(function() { return webdriver.promise.delayed(5) }).
1381+
then(function() { return schedule('b'); });
1382+
});
1383+
schedule('c');
1384+
return waitForIdle().then(function() {
1385+
assertFlowHistory('a', 'b', 'c');
1386+
});
1387+
}
1388+
12981389
function testSubtasks_subTaskFails_1() {
12991390
schedule('a');
13001391
scheduleAction('sub-tasks', function() {

0 commit comments

Comments
 (0)