@@ -740,7 +740,7 @@ class TaskSetManagerSuite extends SparkFunSuite with LocalSparkContext with Logg
740
740
// Mark the task as available for speculation, and then offer another resource,
741
741
// which should be used to launch a speculative copy of the task.
742
742
manager.speculatableTasks += singleTask.partitionId
743
- manager.addPendingTask(singleTask.partitionId, speculative = true )
743
+ manager.addPendingTask(singleTask.partitionId, speculatable = true )
744
744
val task2 = manager.resourceOffer(" execB" , " host2" , TaskLocality .ANY ).get
745
745
746
746
assert(manager.runningTasks === 2 )
@@ -886,7 +886,7 @@ class TaskSetManagerSuite extends SparkFunSuite with LocalSparkContext with Logg
886
886
assert(manager.resourceOffer(" execA" , " host1" , NO_PREF ).get.index == 1 )
887
887
888
888
manager.speculatableTasks += 1
889
- manager.addPendingTask(1 , speculative = true )
889
+ manager.addPendingTask(1 , speculatable = true )
890
890
clock.advance(LOCALITY_WAIT_MS )
891
891
// schedule the nonPref task
892
892
assert(manager.resourceOffer(" execA" , " host1" , NO_PREF ).get.index === 2 )
@@ -1671,16 +1671,13 @@ class TaskSetManagerSuite extends SparkFunSuite with LocalSparkContext with Logg
1671
1671
val accumUpdatesByTask : Array [Seq [AccumulatorV2 [_, _]]] = taskSet.tasks.map { task =>
1672
1672
task.metrics.internalAccums
1673
1673
}
1674
- // Offer resources for 4 tasks to start
1675
- for ((k, v) <- List (
1676
- " exec1" -> " host1" ,
1677
- " exec1" -> " host1" ,
1678
- " exec2" -> " host2" ,
1679
- " exec2" -> " host2" )) {
1680
- val taskOption = manager.resourceOffer(k, v, NO_PREF )
1681
- assert(taskOption.isDefined)
1682
- val task = taskOption.get
1683
- assert(task.executorId === k)
1674
+ // Offer resources for 4 tasks to start, 2 on each exec
1675
+ Seq (" exec1" -> " host1" , " exec2" -> " host2" ).foreach { case (exec, host) =>
1676
+ (0 until 2 ).foreach { _ =>
1677
+ val taskOption = manager.resourceOffer(exec, host, NO_PREF )
1678
+ assert(taskOption.isDefined)
1679
+ assert(taskOption.get.executorId === exec)
1680
+ }
1684
1681
}
1685
1682
assert(sched.startedTasks.toSet === Set (0 , 1 , 2 , 3 ))
1686
1683
clock.advance(1 )
@@ -1698,21 +1695,23 @@ class TaskSetManagerSuite extends SparkFunSuite with LocalSparkContext with Logg
1698
1695
assert(manager.copiesRunning(2 ) === 1 )
1699
1696
assert(manager.copiesRunning(3 ) === 1 )
1700
1697
1701
- // Offer resource to start the speculative attempt for the running task
1702
- val taskOption5 = manager.resourceOffer(" exec1" , " host1" , NO_PREF )
1703
- val taskOption6 = manager.resourceOffer(" exec1" , " host1" , NO_PREF )
1704
- assert(taskOption5.isDefined)
1705
- val task5 = taskOption5.get
1706
- assert(task5.index === 3 )
1707
- assert(task5.taskId === 4 )
1708
- assert(task5.executorId === " exec1" )
1709
- assert(task5.attemptNumber === 1 )
1710
- assert(taskOption6.isDefined)
1711
- val task6 = taskOption6.get
1712
- assert(task6.index === 2 )
1713
- assert(task6.taskId === 5 )
1714
- assert(task6.executorId === " exec1" )
1715
- assert(task6.attemptNumber === 1 )
1698
+ // Offer resource to start the speculative attempt for the running task. We offer more
1699
+ // resources, and ensure that speculative tasks get scheduled appropriately -- only one extra
1700
+ // copy per speculatable task
1701
+ val taskOption2 = manager.resourceOffer(" exec1" , " host1" , NO_PREF )
1702
+ val taskOption3 = manager.resourceOffer(" exec1" , " host1" , NO_PREF )
1703
+ assert(taskOption2.isDefined)
1704
+ val task2 = taskOption2.get
1705
+ assert(task2.index === 3 )
1706
+ assert(task2.taskId === 4 )
1707
+ assert(task2.executorId === " exec1" )
1708
+ assert(task2.attemptNumber === 1 )
1709
+ assert(taskOption3.isDefined)
1710
+ val task3 = taskOption3.get
1711
+ assert(task3.index === 2 )
1712
+ assert(task3.taskId === 5 )
1713
+ assert(task3.executorId === " exec1" )
1714
+ assert(task3.attemptNumber === 1 )
1716
1715
clock.advance(1 )
1717
1716
// Running checkSpeculatableTasks again should return false
1718
1717
assert(! manager.checkSpeculatableTasks(0 ))
@@ -1723,4 +1722,48 @@ class TaskSetManagerSuite extends SparkFunSuite with LocalSparkContext with Logg
1723
1722
assert(manager.resourceOffer(" exec2" , " host2" , ANY ).isEmpty)
1724
1723
assert(manager.resourceOffer(" exec3" , " host3" , ANY ).isEmpty)
1725
1724
}
1725
+
1726
+ test(" SPARK-26755 Ensure that a speculative task obeys the original locality preferences" ) {
1727
+ sc = new SparkContext (" local" , " test" )
1728
+ sched = new FakeTaskScheduler (sc, (" exec1" , " host1" ),
1729
+ (" exec2" , " host2" ), (" exec3" , " host3" ), (" exec4" , " host4" ))
1730
+ // Create 3 tasks with locality preferences
1731
+ val taskSet = FakeTask .createTaskSet(3 ,
1732
+ Seq (TaskLocation (" host1" ), TaskLocation (" host3" )),
1733
+ Seq (TaskLocation (" host2" )),
1734
+ Seq (TaskLocation (" host3" )))
1735
+ // Set the speculation multiplier to be 0 so speculative tasks are launched immediately
1736
+ sc.conf.set(config.SPECULATION_MULTIPLIER , 0.0 )
1737
+ sc.conf.set(config.SPECULATION_ENABLED , true )
1738
+ sc.conf.set(config.SPECULATION_QUANTILE , 0.5 )
1739
+ val clock = new ManualClock ()
1740
+ val manager = new TaskSetManager (sched, taskSet, MAX_TASK_FAILURES , clock = clock)
1741
+ val accumUpdatesByTask : Array [Seq [AccumulatorV2 [_, _]]] = taskSet.tasks.map { task =>
1742
+ task.metrics.internalAccums
1743
+ }
1744
+ // Offer resources for 3 tasks to start
1745
+ Seq (" exec1" -> " host1" , " exec2" -> " host2" , " exec3" -> " host3" ).foreach { case (exec, host) =>
1746
+ val taskOption = manager.resourceOffer(exec, host, NO_PREF )
1747
+ assert(taskOption.isDefined)
1748
+ assert(taskOption.get.executorId === exec)
1749
+ }
1750
+ assert(sched.startedTasks.toSet === Set (0 , 1 , 2 ))
1751
+ clock.advance(1 )
1752
+ // Finish one task and mark the others as speculatable
1753
+ manager.handleSuccessfulTask(2 , createTaskResult(2 , accumUpdatesByTask(2 )))
1754
+ assert(sched.endedTasks(2 ) === Success )
1755
+ clock.advance(1 )
1756
+ assert(manager.checkSpeculatableTasks(0 ))
1757
+ assert(sched.speculativeTasks.toSet === Set (0 , 1 ))
1758
+ // Ensure that the speculatable tasks obey the original locality preferences
1759
+ assert(manager.resourceOffer(" exec4" , " host4" , NODE_LOCAL ).isEmpty)
1760
+ assert(manager.resourceOffer(" exec2" , " host2" , NODE_LOCAL ).isEmpty)
1761
+ assert(manager.resourceOffer(" exec3" , " host3" , NODE_LOCAL ).isDefined)
1762
+ assert(manager.resourceOffer(" exec4" , " host4" , ANY ).isDefined)
1763
+ // Since, all speculatable tasks have been launched, making another offer
1764
+ // should not schedule any more tasks
1765
+ assert(manager.resourceOffer(" exec1" , " host1" , ANY ).isEmpty)
1766
+ assert(! manager.checkSpeculatableTasks(0 ))
1767
+ assert(manager.resourceOffer(" exec1" , " host1" , ANY ).isEmpty)
1768
+ }
1726
1769
}
0 commit comments