Skip to content

Commit b0f7737

Browse files
committed
[js]: don't drop blocked callbacks from the task queue until they are pulled for
execution Fixes SeleniumHQ#1207
1 parent a3b32a3 commit b0f7737

File tree

4 files changed

+67
-49
lines changed

4 files changed

+67
-49
lines changed

javascript/node/selenium-webdriver/CHANGES.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
## v2.49.0-dev
1+
## v2.48.1
2+
3+
* FIXED: Adjusted how the control flow tracks promise callbacks to avoid a
4+
potential deadlock.
25

36
## v2.48.0
47

javascript/node/selenium-webdriver/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "selenium-webdriver",
3-
"version": "2.49.0-dev",
3+
"version": "2.48.1",
44
"description": "The official WebDriver JavaScript bindings from the Selenium project",
55
"license": "Apache-2.0",
66
"keywords": [

javascript/webdriver/promise.js

Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -330,12 +330,12 @@
330330
* var p = new promise.Promise(function(fulfill) {
331331
* setTimeout(fulfill, 100);
332332
* });
333-
*
334333
* p.then(() => console.log('promise resolved!'));
335-
*
334+
* flow.execute(() => console.log('sub-task!'));
336335
* }).then(function() {
337336
* console.log('task complete!');
338337
* });
338+
* // sub-task!
339339
* // task complete!
340340
* // promise resolved!
341341
*
@@ -1296,7 +1296,7 @@ promise.Promise = goog.defineClass(null, {
12961296
this.callbacks_ = [];
12971297
}
12981298
this.callbacks_.push(cb);
1299-
cb.isVolatile = true;
1299+
cb.blocked = true;
13001300
this.flow_.getActiveQueue_().enqueue(cb);
13011301
}
13021302

@@ -2491,12 +2491,20 @@ var Task = goog.defineClass(promise.Deferred, {
24912491
this.queue = null;
24922492

24932493
/**
2494-
* Whether this task is volatile. Volatile tasks may be registered in a
2495-
* a task queue, but will be dropped on the next turn of the JS event loop
2496-
* if still marked volatile.
2494+
* Whether this task is considered block. A blocked task may be registered
2495+
* in a task queue, but will be dropped if it is still blocked when it
2496+
* reaches the front of the queue. A dropped task may always be rescheduled.
2497+
*
2498+
* Blocked tasks are used when a callback is attached to an unsettled
2499+
* promise to reserve a spot in line (in a manner of speaking). If the
2500+
* promise is not settled before the callback reaches the front of the
2501+
* of the queue, it will be dropped. Once the promise is settled, the
2502+
* dropped task will be rescheduled as an interrupt on the currently task
2503+
* queue.
2504+
*
24972505
* @type {boolean}
24982506
*/
2499-
this.isVolatile = false;
2507+
this.blocked = false;
25002508

25012509
if (opt_stackOptions) {
25022510
this.promise.stack_ = promise.captureStackTrace(
@@ -2537,9 +2545,6 @@ var TaskQueue = goog.defineClass(EventEmitter, {
25372545
/** @private {!Array<!Task>} */
25382546
this.tasks_ = [];
25392547

2540-
/** @private {Array<!Task>} */
2541-
this.volatileTasks_ = null;
2542-
25432548
/** @private {Array<!Task>} */
25442549
this.interrupts_ = null;
25452550

@@ -2592,11 +2597,6 @@ var TaskQueue = goog.defineClass(EventEmitter, {
25922597
throw Error('Task is already scheduled in another queue');
25932598
}
25942599

2595-
if (task.isVolatile) {
2596-
this.volatileTasks_ = this.volatileTasks_ || [];
2597-
this.volatileTasks_.push(task);
2598-
}
2599-
26002600
this.tasks_.push(task);
26012601
task.queue = this;
26022602
task.promise[CANCEL_HANDLER_SYMBOL] =
@@ -2628,10 +2628,9 @@ var TaskQueue = goog.defineClass(EventEmitter, {
26282628
return;
26292629
}
26302630
promise.callbacks_.forEach(function(cb) {
2631-
cb.promise[CANCEL_HANDLER_SYMBOL] =
2632-
this.onTaskCancelled_.bind(this, cb);
2631+
cb.promise[CANCEL_HANDLER_SYMBOL] = this.onTaskCancelled_.bind(this, cb);
26332632

2634-
cb.isVolatile = false;
2633+
cb.blocked = false;
26352634
if (cb.queue === this && this.tasks_.indexOf(cb) !== -1) {
26362635
return;
26372636
}
@@ -2711,7 +2710,6 @@ var TaskQueue = goog.defineClass(EventEmitter, {
27112710
return;
27122711
}
27132712
this.state_ = TaskQueueState.STARTED;
2714-
this.dropVolatileTasks_();
27152713

27162714
if (this.pending_ != null || this.processUnhandledRejections_()) {
27172715
return;
@@ -2762,7 +2760,6 @@ var TaskQueue = goog.defineClass(EventEmitter, {
27622760
return fn();
27632761
} finally {
27642762
this.flow_.activeQueue_ = null;
2765-
this.dropVolatileTasks_();
27662763
activeFlows.pop();
27672764
}
27682765
},
@@ -2801,23 +2798,6 @@ var TaskQueue = goog.defineClass(EventEmitter, {
28012798
}
28022799
},
28032800

2804-
/**
2805-
* Drops any volatile tasks scheduled within this task queue.
2806-
* @private
2807-
*/
2808-
dropVolatileTasks_: function() {
2809-
if (!this.volatileTasks_) {
2810-
return;
2811-
}
2812-
for (var task of this.volatileTasks_) {
2813-
if (task.isVolatile) {
2814-
vlog(2, () => this + ' dropping volatile task ' + task, this);
2815-
this.dropTask_(task);
2816-
}
2817-
}
2818-
this.volatileTasks_ = null;
2819-
},
2820-
28212801
/**
28222802
* @param {!Task} task The task to drop.
28232803
* @private
@@ -2882,13 +2862,21 @@ var TaskQueue = goog.defineClass(EventEmitter, {
28822862
*/
28832863
getNextTask_: function() {
28842864
var task = undefined;
2885-
if (this.interrupts_) {
2886-
task = this.interrupts_.shift();
2887-
}
2888-
if (!task && this.tasks_) {
2889-
task = this.tasks_.shift();
2865+
while (true) {
2866+
if (this.interrupts_) {
2867+
task = this.interrupts_.shift();
2868+
}
2869+
if (!task && this.tasks_) {
2870+
task = this.tasks_.shift();
2871+
}
2872+
if (task && task.blocked) {
2873+
vlog(2, () => this + ' skipping blocked task ' + task, this);
2874+
task.queue = null;
2875+
task = null;
2876+
continue;
2877+
}
2878+
return task;
28902879
}
2891-
return task;
28922880
}
28932881
});
28942882

javascript/webdriver/test/promise_flow_test.js

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ function testFraming_allCallbacksInAFrameAreScheduledWhenPromiseIsResolved() {
435435
schedule('e');
436436

437437
return waitForIdle().then(function() {
438-
assertFlowHistory('a', 'b', 'd', 'c', 'e');
438+
assertFlowHistory('a', 'b', 'c', 'd', 'e');
439439
});
440440
}
441441

@@ -2093,10 +2093,11 @@ function testCancellingAPendingTask() {
20932093
return unresolved.promise;
20942094
});
20952095

2096+
// Since the outerTask is cancelled below, innerTask should be cancelled
2097+
// with a DiscardedTaskError, which means its callbacks are silently
2098+
// dropped - so this should never execute.
20962099
innerTask.thenCatch(function(e) {
20972100
order.push(2);
2098-
assertTrue(e instanceof webdriver.promise.CancellationError);
2099-
assertEquals('no soup for you', e.message);
21002101
});
21012102
});
21022103
schedule('b');
@@ -2119,7 +2120,7 @@ function testCancellingAPendingTask() {
21192120
return waitForIdle();
21202121
}).then(function() {
21212122
assertFlowHistory('a', 'a.1', 'b');
2122-
assertArrayEquals([1, 4, 2, 3], order);
2123+
assertArrayEquals([1, 4, 3], order);
21232124
});
21242125
}
21252126

@@ -2312,3 +2313,29 @@ function testSettledPromiseCallbacksInsideATask_2() {
23122313
assertArrayEquals(['a', 'c', 'b', 'fin'], order);
23132314
});
23142315
}
2316+
2317+
function testTasksDoNotWaitForNewlyCreatedPromises() {
2318+
var order = [];
2319+
2320+
flow.execute(function() {
2321+
var d = webdriver.promise.defer();
2322+
2323+
// This is a normal promise, not a task, so the task for this callback is
2324+
// considered volatile. Volatile tasks should be skipped when they reach
2325+
// the front of the task queue.
2326+
d.promise.then(() => order.push('a'));
2327+
2328+
flow.execute(() => order.push('b'));
2329+
flow.execute(function() {
2330+
flow.execute(() => order.push('c'));
2331+
d.promise.then(() => order.push('d'));
2332+
d.fulfill();
2333+
});
2334+
flow.execute(() => order.push('e'));
2335+
2336+
}).then(() => order.push('fin'));
2337+
2338+
return waitForIdle().then(function() {
2339+
assertArrayEquals(['b', 'a', 'c', 'd', 'e', 'fin'], order);
2340+
});
2341+
}

0 commit comments

Comments
 (0)