Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
10 changes: 10 additions & 0 deletions core/src/main/java/org/apache/spark/TaskContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,18 @@ static void unset() {

public abstract int partitionId();

/**
* An ID recording how many times this task has been attempted. The first task attempt will be
* assigned attemptId = 0, and subsequent attempts will have increasing IDs.
*/
public abstract long attemptId();

/**
* An ID that is unique to this task attempt (within the same SparkContext, no two task attempts
* will share the same task ID).
*/
public abstract long taskId();

/** ::DeveloperApi:: */
@DeveloperApi
public abstract TaskMetrics taskMetrics();
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/scala/org/apache/spark/TaskContextImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import org.apache.spark.util.{TaskCompletionListener, TaskCompletionListenerExce

import scala.collection.mutable.ArrayBuffer

private[spark] class TaskContextImpl(val stageId: Int,
private[spark] class TaskContextImpl(
val stageId: Int,
val partitionId: Int,
val taskId: Long,
val attemptId: Long,
val runningLocally: Boolean = false,
val taskMetrics: TaskMetrics = TaskMetrics.empty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ private[spark] class CoarseGrainedExecutorBackend(
val ser = env.closureSerializer.newInstance()
val taskDesc = ser.deserialize[TaskDescription](data.value)
logInfo("Got assigned task " + taskDesc.taskId)
executor.launchTask(this, taskDesc.taskId, taskDesc.name, taskDesc.serializedTask)
executor.launchTask(this, taskId = taskDesc.taskId, attemptId = taskDesc.attemptId,
taskDesc.name, taskDesc.serializedTask)
}

case KillTask(taskId, _, interruptThread) =>
Expand Down
17 changes: 13 additions & 4 deletions core/src/main/scala/org/apache/spark/executor/Executor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,13 @@ private[spark] class Executor(
startDriverHeartbeater()

def launchTask(
context: ExecutorBackend, taskId: Long, taskName: String, serializedTask: ByteBuffer) {
val tr = new TaskRunner(context, taskId, taskName, serializedTask)
context: ExecutorBackend,
taskId: Long,
attemptId: Long,
taskName: String,
serializedTask: ByteBuffer) {
val tr =
new TaskRunner(context, taskId = taskId, attemptId = attemptId, taskName, serializedTask)
runningTasks.put(taskId, tr)
threadPool.execute(tr)
}
Expand All @@ -134,7 +139,11 @@ private[spark] class Executor(
private def gcTime = ManagementFactory.getGarbageCollectorMXBeans.map(_.getCollectionTime).sum

class TaskRunner(
execBackend: ExecutorBackend, val taskId: Long, taskName: String, serializedTask: ByteBuffer)
execBackend: ExecutorBackend,
val taskId: Long,
val attemptId: Long,
taskName: String,
serializedTask: ByteBuffer)
extends Runnable {

@volatile private var killed = false
Expand Down Expand Up @@ -180,7 +189,7 @@ private[spark] class Executor(

// Run the actual task and measure its runtime.
taskStart = System.currentTimeMillis()
val value = task.run(taskId.toInt)
val value = task.run(taskId = taskId.toLong, attemptId = attemptId.toLong)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the toLong's are not necessary, are they?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch; this was a carryover from the old code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out that attemptId was a long but we only expected it to hold values that could fit in an int, hence this weird .toLong in a few places, plus a few % Int.MaxValue modulus calls in various places; I've cleaned this up.

val taskFinish = System.currentTimeMillis()

// If the task has been killed, let's fail it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,13 @@ private[spark] class MesosExecutorBackend

override def launchTask(d: ExecutorDriver, taskInfo: TaskInfo) {
val taskId = taskInfo.getTaskId.getValue.toLong
val attemptIdPlusData = taskInfo.getData.asReadOnlyByteBuffer()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here's the corresponding receive-side of the Mesos trickiness.

val attemptId = attemptIdPlusData.getLong // Updates the position by 8 bytes
val data = attemptIdPlusData.slice() // Subsequence starting at the current position
if (executor == null) {
logError("Received launchTask but executor was null")
} else {
executor.launchTask(this, taskId, taskInfo.getName, taskInfo.getData.asReadOnlyByteBuffer)
executor.launchTask(this, taskId = taskId, attemptId = attemptId, taskInfo.getName, data)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ class DAGScheduler(
val rdd = job.finalStage.rdd
val split = rdd.partitions(job.partitions(0))
val taskContext =
new TaskContextImpl(job.finalStage.id, job.partitions(0), 0, true)
new TaskContextImpl(job.finalStage.id, job.partitions(0), 0, 0, true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use named argument for the two zeros and the true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call; I didn't do this for the test code, but this line is in DAGScheduler so it should use named arguments.

TaskContextHelper.setTaskContext(taskContext)
try {
val result = job.func(taskContext, rdd.iterator(split, taskContext))
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/scala/org/apache/spark/scheduler/Task.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ import org.apache.spark.util.Utils
*/
private[spark] abstract class Task[T](val stageId: Int, var partitionId: Int) extends Serializable {

final def run(attemptId: Long): T = {
context = new TaskContextImpl(stageId, partitionId, attemptId, runningLocally = false)
final def run(taskId: Long, attemptId: Long): T = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be great to add javadoc here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the descriptions in TaskContext okay? If so, I'll just copy those for the parameters.

context = new TaskContextImpl(stageId = stageId, partitionId = partitionId, taskId = taskId,
attemptId = attemptId, runningLocally = false)
TaskContextHelper.setTaskContext(context)
context.taskMetrics.hostname = Utils.localHostName()
taskThread = Thread.currentThread()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.apache.spark.util.SerializableBuffer
*/
private[spark] class TaskDescription(
val taskId: Long,
val attemptId: Long,
val executorId: String,
val name: String,
val index: Int, // Index within this task's TaskSet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,8 @@ private[spark] class TaskSetManager(
taskName, taskId, host, taskLocality, serializedTask.limit))

sched.dagScheduler.taskStarted(task, info)
return Some(new TaskDescription(taskId, execId, taskName, index, serializedTask))
return Some(new TaskDescription(taskId = taskId, attemptId = attemptNum, execId, taskName,
index, serializedTask))
}
case _ =>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.spark.scheduler.cluster.mesos

import java.io.File
import java.nio.ByteBuffer
import java.util.{ArrayList => JArrayList, List => JList}
import java.util.Collections

Expand Down Expand Up @@ -290,13 +291,22 @@ private[spark] class MesosSchedulerBackend(
.setType(Value.Type.SCALAR)
.setScalar(Value.Scalar.newBuilder().setValue(scheduler.CPUS_PER_TASK).build())
.build()
// Encode the attemptId as part of the data payload, since there's not a MesosTaskInfo field
// to hold it:
val data = {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the Mesos trickiness that I alluded to in the PR description.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we type data explicitly?

val serializedTask = task.serializedTask
val dataBuffer = ByteBuffer.allocate(8 + serializedTask.limit())
dataBuffer.putLong(task.attemptId)
dataBuffer.put(serializedTask)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we introduce another wrapper here instead? I'm imagine we will be adding more fields to serialize to Mesos executors, and it's a lot easier to maintain a struct then position with types and offsets.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea; putting the serialization / deserialization code in the same wrapper class will make it much easier to verify that it's correct / test it separately. I'll push a new commit to do this.

ByteString.copyFrom(dataBuffer)
}
MesosTaskInfo.newBuilder()
.setTaskId(taskId)
.setSlaveId(SlaveID.newBuilder().setValue(slaveId).build())
.setExecutor(createExecutorInfo(slaveId))
.setName(task.name)
.addResources(cpuResource)
.setData(ByteString.copyFrom(task.serializedTask))
.setData(data)
.build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ private[spark] class LocalActor(
val offers = Seq(new WorkerOffer(localExecutorId, localExecutorHostname, freeCores))
for (task <- scheduler.resourceOffers(offers).flatten) {
freeCores -= scheduler.CPUS_PER_TASK
executor.launchTask(executorBackend, task.taskId, task.name, task.serializedTask)
executor.launchTask(executorBackend, taskId = task.taskId, attemptId = task.attemptId,
task.name, task.serializedTask)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/test/java/org/apache/spark/JavaAPISuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ public void persist() {
@Test
public void iterator() {
JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5), 2);
TaskContext context = new TaskContextImpl(0, 0, 0L, false, new TaskMetrics());
TaskContext context = new TaskContextImpl(0, 0, 0L, 0L, false, new TaskMetrics());
Assert.assertEquals(1, rdd.iterator(rdd.partitions().get(0), context).next().intValue());
}

Expand Down
8 changes: 4 additions & 4 deletions core/src/test/scala/org/apache/spark/CacheManagerSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class CacheManagerSuite extends FunSuite with BeforeAndAfter with EasyMockSugar
// in blockManager.put is a losing battle. You have been warned.
blockManager = sc.env.blockManager
cacheManager = sc.env.cacheManager
val context = new TaskContextImpl(0, 0, 0)
val context = new TaskContextImpl(0, 0, 0, 0)
val computeValue = cacheManager.getOrCompute(rdd, split, context, StorageLevel.MEMORY_ONLY)
val getValue = blockManager.get(RDDBlockId(rdd.id, split.index))
assert(computeValue.toList === List(1, 2, 3, 4))
Expand All @@ -81,7 +81,7 @@ class CacheManagerSuite extends FunSuite with BeforeAndAfter with EasyMockSugar
}

whenExecuting(blockManager) {
val context = new TaskContextImpl(0, 0, 0)
val context = new TaskContextImpl(0, 0, 0, 0)
val value = cacheManager.getOrCompute(rdd, split, context, StorageLevel.MEMORY_ONLY)
assert(value.toList === List(5, 6, 7))
}
Expand All @@ -94,15 +94,15 @@ class CacheManagerSuite extends FunSuite with BeforeAndAfter with EasyMockSugar
}

whenExecuting(blockManager) {
val context = new TaskContextImpl(0, 0, 0, true)
val context = new TaskContextImpl(0, 0, 0, 0, true)
val value = cacheManager.getOrCompute(rdd, split, context, StorageLevel.MEMORY_ONLY)
assert(value.toList === List(1, 2, 3, 4))
}
}

test("verify task metrics updated correctly") {
cacheManager = sc.env.cacheManager
val context = new TaskContextImpl(0, 0, 0)
val context = new TaskContextImpl(0, 0, 0, 0)
cacheManager.getOrCompute(rdd3, split, context, StorageLevel.MEMORY_ONLY)
assert(context.taskMetrics.updatedBlocks.getOrElse(Seq()).size === 2)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class PipedRDDSuite extends FunSuite with SharedSparkContext {
}
val hadoopPart1 = generateFakeHadoopPartition()
val pipedRdd = new PipedRDD(nums, "printenv " + varName)
val tContext = new TaskContextImpl(0, 0, 0)
val tContext = new TaskContextImpl(0, 0, 0, 0)
val rddIter = pipedRdd.compute(hadoopPart1, tContext)
val arr = rddIter.toArray
assert(arr(0) == "/some/path")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ class TaskContextSuite extends FunSuite with BeforeAndAfter with LocalSparkConte
val task = new ResultTask[String, String](
0, sc.broadcast(closureSerializer.serialize((rdd, func)).array), rdd.partitions(0), Seq(), 0)
intercept[RuntimeException] {
task.run(0)
task.run(0, 0)
}
assert(TaskContextSuite.completed === true)
}

test("all TaskCompletionListeners should be called even if some fail") {
val context = new TaskContextImpl(0, 0, 0)
val context = new TaskContextImpl(0, 0, 0, 0)
val listener = mock(classOf[TaskCompletionListener])
context.addTaskCompletionListener(_ => throw new Exception("blah"))
context.addTaskCompletionListener(listener)
Expand All @@ -63,6 +63,25 @@ class TaskContextSuite extends FunSuite with BeforeAndAfter with LocalSparkConte

verify(listener, times(1)).onTaskCompletion(any())
}

test("TaskContext.attemptId should return attempt number, not task id (SPARK-4014)") {
sc = new SparkContext("local-cluster[2,1,512]", "test")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized that I can change the maxFailures property on local instead of having to use local-cluster. Let me make that change, since it's a better practice and will speed up the tests.

// Check that attemptIds are 0 for all tasks' initial attempts
val attemptIds = sc.parallelize(Seq(1, 2), 2).mapPartitions { iter =>
Seq(TaskContext.get().attemptId()).iterator
}.collect()
assert(attemptIds.toSet === Set(0))

// Test a job with failed tasks
val attemptIdsWithFailedTask = sc.parallelize(Seq(1, 2), 2).mapPartitions { iter =>
val attemptId = TaskContext.get().attemptId()
if (iter.next() == 1 && attemptId == 0) {
throw new Exception("First execution of task failed")
}
Seq(attemptId).iterator
}.collect()
assert(attemptIdsWithFailedTask.toSet === Set(0, 1))
}
}

private object TaskContextSuite {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class FakeTaskSetManager(
{
if (tasksSuccessful + numRunningTasks < numTasks) {
increaseRunningTasks(1)
Some(new TaskDescription(0, execId, "task 0:0", 0, null))
Some(new TaskDescription(0, 0, execId, "task 0:0", 0, null))
} else {
None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class MesosSchedulerBackendSuite extends FunSuite with LocalSparkContext with Ea
mesosOffers.get(2).getHostname,
2
))
val taskDesc = new TaskDescription(1L, "s1", "n1", 0, ByteBuffer.wrap(new Array[Byte](0)))
val taskDesc = new TaskDescription(1L, 0, "s1", "n1", 0, ByteBuffer.wrap(new Array[Byte](0)))
EasyMock.expect(taskScheduler.resourceOffers(EasyMock.eq(expectedWorkerOffers))).andReturn(Seq(Seq(taskDesc)))
EasyMock.expect(taskScheduler.CPUS_PER_TASK).andReturn(2).anyTimes()
EasyMock.replay(taskScheduler)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class ShuffleBlockFetcherIteratorSuite extends FunSuite {
)

val iterator = new ShuffleBlockFetcherIterator(
new TaskContextImpl(0, 0, 0),
new TaskContextImpl(0, 0, 0, 0),
transfer,
blockManager,
blocksByAddress,
Expand Down Expand Up @@ -154,7 +154,7 @@ class ShuffleBlockFetcherIteratorSuite extends FunSuite {
val blocksByAddress = Seq[(BlockManagerId, Seq[(BlockId, Long)])](
(remoteBmId, blocks.keys.map(blockId => (blockId, 1.asInstanceOf[Long])).toSeq))

val taskContext = new TaskContextImpl(0, 0, 0)
val taskContext = new TaskContextImpl(0, 0, 0, 0)
val iterator = new ShuffleBlockFetcherIterator(
taskContext,
transfer,
Expand Down Expand Up @@ -217,7 +217,7 @@ class ShuffleBlockFetcherIteratorSuite extends FunSuite {
val blocksByAddress = Seq[(BlockManagerId, Seq[(BlockId, Long)])](
(remoteBmId, blocks.keys.map(blockId => (blockId, 1.asInstanceOf[Long])).toSeq))

val taskContext = new TaskContextImpl(0, 0, 0)
val taskContext = new TaskContextImpl(0, 0, 0, 0)
val iterator = new ShuffleBlockFetcherIterator(
taskContext,
transfer,
Expand Down