Skip to content

Commit 0745a30

Browse files
committed
Tighten up field/method visibility in Executor and made some code more clear to read.
I was reading Executor just now and found that some latest changes introduced some weird code path with too much monadic chaining and unnecessary fields. I cleaned it up a bit, and also tightened up the visibility of various fields/methods. Also added some inline documentation to help understand this code better. Author: Reynold Xin <rxin@databricks.com> Closes apache#4850 from rxin/executor and squashes the following commits: 866fc60 [Reynold Xin] Code review feedback. 020efbb [Reynold Xin] Tighten up field/method visibility in Executor and made some code more clear to read.
1 parent f17d43b commit 0745a30

5 files changed

Lines changed: 120 additions & 106 deletions

File tree

core/src/main/scala/org/apache/spark/TaskEndReason.scala

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,7 @@ case object TaskKilled extends TaskFailedReason {
151151
* Task requested the driver to commit, but was denied.
152152
*/
153153
@DeveloperApi
154-
case class TaskCommitDenied(
155-
jobID: Int,
156-
partitionID: Int,
157-
attemptID: Int)
158-
extends TaskFailedReason {
154+
case class TaskCommitDenied(jobID: Int, partitionID: Int, attemptID: Int) extends TaskFailedReason {
159155
override def toErrorString: String = s"TaskCommitDenied (Driver denied task commit)" +
160156
s" for job: $jobID, partition: $partitionID, attempt: $attemptID"
161157
}

core/src/main/scala/org/apache/spark/executor/CommitDeniedException.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,12 @@ import org.apache.spark.{TaskCommitDenied, TaskEndReason}
2222
/**
2323
* Exception thrown when a task attempts to commit output to HDFS but is denied by the driver.
2424
*/
25-
class CommitDeniedException(
25+
private[spark] class CommitDeniedException(
2626
msg: String,
2727
jobID: Int,
2828
splitID: Int,
2929
attemptID: Int)
3030
extends Exception(msg) {
3131

32-
def toTaskEndReason: TaskEndReason = new TaskCommitDenied(jobID, splitID, attemptID)
33-
32+
def toTaskEndReason: TaskEndReason = TaskCommitDenied(jobID, splitID, attemptID)
3433
}
35-

core/src/main/scala/org/apache/spark/executor/Executor.scala

Lines changed: 106 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import java.io.File
2121
import java.lang.management.ManagementFactory
2222
import java.net.URL
2323
import java.nio.ByteBuffer
24-
import java.util.concurrent._
24+
import java.util.concurrent.ConcurrentHashMap
2525

2626
import scala.collection.JavaConversions._
2727
import scala.collection.mutable.{ArrayBuffer, HashMap}
@@ -31,24 +31,26 @@ import akka.actor.Props
3131

3232
import org.apache.spark._
3333
import org.apache.spark.deploy.SparkHadoopUtil
34-
import org.apache.spark.scheduler._
34+
import org.apache.spark.scheduler.{DirectTaskResult, IndirectTaskResult, Task}
3535
import org.apache.spark.shuffle.FetchFailedException
3636
import org.apache.spark.storage.{StorageLevel, TaskResultBlockId}
37-
import org.apache.spark.util.{ChildFirstURLClassLoader, MutableURLClassLoader,
38-
SparkUncaughtExceptionHandler, AkkaUtils, Utils}
37+
import org.apache.spark.util._
3938

4039
/**
41-
* Spark executor used with Mesos, YARN, and the standalone scheduler.
42-
* In coarse-grained mode, an existing actor system is provided.
40+
* Spark executor, backed by a threadpool to run tasks.
41+
*
42+
* This can be used with Mesos, YARN, and the standalone scheduler.
43+
* An internal RPC interface (at the moment Akka) is used for communication with the driver,
44+
* except in the case of Mesos fine-grained mode.
4345
*/
4446
private[spark] class Executor(
4547
executorId: String,
4648
executorHostname: String,
4749
env: SparkEnv,
4850
userClassPath: Seq[URL] = Nil,
4951
isLocal: Boolean = false)
50-
extends Logging
51-
{
52+
extends Logging {
53+
5254
logInfo(s"Starting executor ID $executorId on host $executorHostname")
5355

5456
// Application dependencies (added through SparkContext) that we've fetched so far on this node.
@@ -78,9 +80,8 @@ private[spark] class Executor(
7880
}
7981

8082
// Start worker thread pool
81-
val threadPool = Utils.newDaemonCachedThreadPool("Executor task launch worker")
82-
83-
val executorSource = new ExecutorSource(this, executorId)
83+
private val threadPool = Utils.newDaemonCachedThreadPool("Executor task launch worker")
84+
private val executorSource = new ExecutorSource(threadPool, executorId)
8485

8586
if (!isLocal) {
8687
env.metricsSystem.registerSource(executorSource)
@@ -122,21 +123,21 @@ private[spark] class Executor(
122123
taskId: Long,
123124
attemptNumber: Int,
124125
taskName: String,
125-
serializedTask: ByteBuffer) {
126+
serializedTask: ByteBuffer): Unit = {
126127
val tr = new TaskRunner(context, taskId = taskId, attemptNumber = attemptNumber, taskName,
127128
serializedTask)
128129
runningTasks.put(taskId, tr)
129130
threadPool.execute(tr)
130131
}
131132

132-
def killTask(taskId: Long, interruptThread: Boolean) {
133+
def killTask(taskId: Long, interruptThread: Boolean): Unit = {
133134
val tr = runningTasks.get(taskId)
134135
if (tr != null) {
135136
tr.kill(interruptThread)
136137
}
137138
}
138139

139-
def stop() {
140+
def stop(): Unit = {
140141
env.metricsSystem.report()
141142
env.actorSystem.stop(executorActor)
142143
isStopped = true
@@ -146,7 +147,10 @@ private[spark] class Executor(
146147
}
147148
}
148149

149-
private def gcTime = ManagementFactory.getGarbageCollectorMXBeans.map(_.getCollectionTime).sum
150+
/** Returns the total amount of time this JVM process has spent in garbage collection. */
151+
private def computeTotalGcTime(): Long = {
152+
ManagementFactory.getGarbageCollectorMXBeans.map(_.getCollectionTime).sum
153+
}
150154

151155
class TaskRunner(
152156
execBackend: ExecutorBackend,
@@ -156,27 +160,34 @@ private[spark] class Executor(
156160
serializedTask: ByteBuffer)
157161
extends Runnable {
158162

163+
/** Whether this task has been killed. */
159164
@volatile private var killed = false
160-
@volatile var task: Task[Any] = _
161-
@volatile var attemptedTask: Option[Task[Any]] = None
165+
166+
/** How much the JVM process has spent in GC when the task starts to run. */
162167
@volatile var startGCTime: Long = _
163168

164-
def kill(interruptThread: Boolean) {
169+
/**
170+
* The task to run. This will be set in run() by deserializing the task binary coming
171+
* from the driver. Once it is set, it will never be changed.
172+
*/
173+
@volatile var task: Task[Any] = _
174+
175+
def kill(interruptThread: Boolean): Unit = {
165176
logInfo(s"Executor is trying to kill $taskName (TID $taskId)")
166177
killed = true
167178
if (task != null) {
168179
task.kill(interruptThread)
169180
}
170181
}
171182

172-
override def run() {
183+
override def run(): Unit = {
173184
val deserializeStartTime = System.currentTimeMillis()
174185
Thread.currentThread.setContextClassLoader(replClassLoader)
175186
val ser = env.closureSerializer.newInstance()
176187
logInfo(s"Running $taskName (TID $taskId)")
177188
execBackend.statusUpdate(taskId, TaskState.RUNNING, EMPTY_BYTE_BUFFER)
178189
var taskStart: Long = 0
179-
startGCTime = gcTime
190+
startGCTime = computeTotalGcTime()
180191

181192
try {
182193
val (taskFiles, taskJars, taskBytes) = Task.deserializeWithDependencies(serializedTask)
@@ -193,7 +204,6 @@ private[spark] class Executor(
193204
throw new TaskKilledException
194205
}
195206

196-
attemptedTask = Some(task)
197207
logDebug("Task " + taskId + "'s epoch is " + task.epoch)
198208
env.mapOutputTracker.updateEpoch(task.epoch)
199209

@@ -215,18 +225,17 @@ private[spark] class Executor(
215225
for (m <- task.metrics) {
216226
m.setExecutorDeserializeTime(taskStart - deserializeStartTime)
217227
m.setExecutorRunTime(taskFinish - taskStart)
218-
m.setJvmGCTime(gcTime - startGCTime)
228+
m.setJvmGCTime(computeTotalGcTime() - startGCTime)
219229
m.setResultSerializationTime(afterSerialization - beforeSerialization)
220230
}
221231

222232
val accumUpdates = Accumulators.values
223-
224233
val directResult = new DirectTaskResult(valueBytes, accumUpdates, task.metrics.orNull)
225234
val serializedDirectResult = ser.serialize(directResult)
226235
val resultSize = serializedDirectResult.limit
227236

228237
// directSend = sending directly back to the driver
229-
val serializedResult = {
238+
val serializedResult: ByteBuffer = {
230239
if (maxResultSize > 0 && resultSize > maxResultSize) {
231240
logWarning(s"Finished $taskName (TID $taskId). Result is larger than maxResultSize " +
232241
s"(${Utils.bytesToString(resultSize)} > ${Utils.bytesToString(maxResultSize)}), " +
@@ -248,42 +257,40 @@ private[spark] class Executor(
248257
execBackend.statusUpdate(taskId, TaskState.FINISHED, serializedResult)
249258

250259
} catch {
251-
case ffe: FetchFailedException => {
260+
case ffe: FetchFailedException =>
252261
val reason = ffe.toTaskEndReason
253262
execBackend.statusUpdate(taskId, TaskState.FAILED, ser.serialize(reason))
254-
}
255263

256-
case _: TaskKilledException | _: InterruptedException if task.killed => {
264+
case _: TaskKilledException | _: InterruptedException if task.killed =>
257265
logInfo(s"Executor killed $taskName (TID $taskId)")
258266
execBackend.statusUpdate(taskId, TaskState.KILLED, ser.serialize(TaskKilled))
259-
}
260267

261-
case cDE: CommitDeniedException => {
268+
case cDE: CommitDeniedException =>
262269
val reason = cDE.toTaskEndReason
263270
execBackend.statusUpdate(taskId, TaskState.FAILED, ser.serialize(reason))
264-
}
265271

266-
case t: Throwable => {
272+
case t: Throwable =>
267273
// Attempt to exit cleanly by informing the driver of our failure.
268274
// If anything goes wrong (or this was a fatal exception), we will delegate to
269275
// the default uncaught exception handler, which will terminate the Executor.
270276
logError(s"Exception in $taskName (TID $taskId)", t)
271277

272-
val serviceTime = System.currentTimeMillis() - taskStart
273-
val metrics = attemptedTask.flatMap(t => t.metrics)
274-
for (m <- metrics) {
275-
m.setExecutorRunTime(serviceTime)
276-
m.setJvmGCTime(gcTime - startGCTime)
278+
val metrics: Option[TaskMetrics] = Option(task).flatMap { task =>
279+
task.metrics.map { m =>
280+
m.setExecutorRunTime(System.currentTimeMillis() - taskStart)
281+
m.setJvmGCTime(computeTotalGcTime() - startGCTime)
282+
m
283+
}
277284
}
278-
val reason = new ExceptionFailure(t, metrics)
279-
execBackend.statusUpdate(taskId, TaskState.FAILED, ser.serialize(reason))
285+
val taskEndReason = new ExceptionFailure(t, metrics)
286+
execBackend.statusUpdate(taskId, TaskState.FAILED, ser.serialize(taskEndReason))
280287

281288
// Don't forcibly exit unless the exception was inherently fatal, to avoid
282289
// stopping other tasks unnecessarily.
283290
if (Utils.isFatalError(t)) {
284291
SparkUncaughtExceptionHandler.uncaughtException(t)
285292
}
286-
}
293+
287294
} finally {
288295
// Release memory used by this thread for shuffles
289296
env.shuffleMemoryManager.releaseMemoryForThisThread()
@@ -358,7 +365,7 @@ private[spark] class Executor(
358365
for ((name, timestamp) <- newFiles if currentFiles.getOrElse(name, -1L) < timestamp) {
359366
logInfo("Fetching " + name + " with timestamp " + timestamp)
360367
// Fetch file with useCache mode, close cache for local mode.
361-
Utils.fetchFile(name, new File(SparkFiles.getRootDirectory), conf,
368+
Utils.fetchFile(name, new File(SparkFiles.getRootDirectory()), conf,
362369
env.securityManager, hadoopConf, timestamp, useCache = !isLocal)
363370
currentFiles(name) = timestamp
364371
}
@@ -370,12 +377,12 @@ private[spark] class Executor(
370377
if (currentTimeStamp < timestamp) {
371378
logInfo("Fetching " + name + " with timestamp " + timestamp)
372379
// Fetch file with useCache mode, close cache for local mode.
373-
Utils.fetchFile(name, new File(SparkFiles.getRootDirectory), conf,
380+
Utils.fetchFile(name, new File(SparkFiles.getRootDirectory()), conf,
374381
env.securityManager, hadoopConf, timestamp, useCache = !isLocal)
375382
currentJars(name) = timestamp
376383
// Add it to our class loader
377-
val url = new File(SparkFiles.getRootDirectory, localName).toURI.toURL
378-
if (!urlClassLoader.getURLs.contains(url)) {
384+
val url = new File(SparkFiles.getRootDirectory(), localName).toURI.toURL
385+
if (!urlClassLoader.getURLs().contains(url)) {
379386
logInfo("Adding " + url + " to class loader")
380387
urlClassLoader.addURL(url)
381388
}
@@ -384,61 +391,70 @@ private[spark] class Executor(
384391
}
385392
}
386393

387-
def startDriverHeartbeater() {
388-
val interval = conf.getInt("spark.executor.heartbeatInterval", 10000)
389-
val timeout = AkkaUtils.lookupTimeout(conf)
390-
val retryAttempts = AkkaUtils.numRetries(conf)
391-
val retryIntervalMs = AkkaUtils.retryWaitMs(conf)
392-
val heartbeatReceiverRef = AkkaUtils.makeDriverRef("HeartbeatReceiver", conf, env.actorSystem)
394+
private val timeout = AkkaUtils.lookupTimeout(conf)
395+
private val retryAttempts = AkkaUtils.numRetries(conf)
396+
private val retryIntervalMs = AkkaUtils.retryWaitMs(conf)
397+
private val heartbeatReceiverRef =
398+
AkkaUtils.makeDriverRef("HeartbeatReceiver", conf, env.actorSystem)
399+
400+
/** Reports heartbeat and metrics for active tasks to the driver. */
401+
private def reportHeartBeat(): Unit = {
402+
// list of (task id, metrics) to send back to the driver
403+
val tasksMetrics = new ArrayBuffer[(Long, TaskMetrics)]()
404+
val curGCTime = computeTotalGcTime()
405+
406+
for (taskRunner <- runningTasks.values()) {
407+
if (taskRunner.task != null) {
408+
taskRunner.task.metrics.foreach { metrics =>
409+
metrics.updateShuffleReadMetrics()
410+
metrics.updateInputMetrics()
411+
metrics.setJvmGCTime(curGCTime - taskRunner.startGCTime)
412+
413+
if (isLocal) {
414+
// JobProgressListener will hold an reference of it during
415+
// onExecutorMetricsUpdate(), then JobProgressListener can not see
416+
// the changes of metrics any more, so make a deep copy of it
417+
val copiedMetrics = Utils.deserialize[TaskMetrics](Utils.serialize(metrics))
418+
tasksMetrics += ((taskRunner.taskId, copiedMetrics))
419+
} else {
420+
// It will be copied by serialization
421+
tasksMetrics += ((taskRunner.taskId, metrics))
422+
}
423+
}
424+
}
425+
}
393426

394-
val t = new Thread() {
427+
val message = Heartbeat(executorId, tasksMetrics.toArray, env.blockManager.blockManagerId)
428+
try {
429+
val response = AkkaUtils.askWithReply[HeartbeatResponse](message, heartbeatReceiverRef,
430+
retryAttempts, retryIntervalMs, timeout)
431+
if (response.reregisterBlockManager) {
432+
logWarning("Told to re-register on heartbeat")
433+
env.blockManager.reregister()
434+
}
435+
} catch {
436+
case NonFatal(e) => logWarning("Issue communicating with driver in heartbeater", e)
437+
}
438+
}
439+
440+
/**
441+
* Starts a thread to report heartbeat and partial metrics for active tasks to driver.
442+
* This thread stops running when the executor is stopped.
443+
*/
444+
private def startDriverHeartbeater(): Unit = {
445+
val interval = conf.getInt("spark.executor.heartbeatInterval", 10000)
446+
val thread = new Thread() {
395447
override def run() {
396448
// Sleep a random interval so the heartbeats don't end up in sync
397449
Thread.sleep(interval + (math.random * interval).asInstanceOf[Int])
398-
399450
while (!isStopped) {
400-
val tasksMetrics = new ArrayBuffer[(Long, TaskMetrics)]()
401-
val curGCTime = gcTime
402-
403-
for (taskRunner <- runningTasks.values()) {
404-
if (taskRunner.attemptedTask.nonEmpty) {
405-
Option(taskRunner.task).flatMap(_.metrics).foreach { metrics =>
406-
metrics.updateShuffleReadMetrics()
407-
metrics.updateInputMetrics()
408-
metrics.setJvmGCTime(curGCTime - taskRunner.startGCTime)
409-
410-
if (isLocal) {
411-
// JobProgressListener will hold an reference of it during
412-
// onExecutorMetricsUpdate(), then JobProgressListener can not see
413-
// the changes of metrics any more, so make a deep copy of it
414-
val copiedMetrics = Utils.deserialize[TaskMetrics](Utils.serialize(metrics))
415-
tasksMetrics += ((taskRunner.taskId, copiedMetrics))
416-
} else {
417-
// It will be copied by serialization
418-
tasksMetrics += ((taskRunner.taskId, metrics))
419-
}
420-
}
421-
}
422-
}
423-
424-
val message = Heartbeat(executorId, tasksMetrics.toArray, env.blockManager.blockManagerId)
425-
try {
426-
val response = AkkaUtils.askWithReply[HeartbeatResponse](message, heartbeatReceiverRef,
427-
retryAttempts, retryIntervalMs, timeout)
428-
if (response.reregisterBlockManager) {
429-
logWarning("Told to re-register on heartbeat")
430-
env.blockManager.reregister()
431-
}
432-
} catch {
433-
case NonFatal(t) => logWarning("Issue communicating with driver in heartbeater", t)
434-
}
435-
451+
reportHeartBeat()
436452
Thread.sleep(interval)
437453
}
438454
}
439455
}
440-
t.setDaemon(true)
441-
t.setName("Driver Heartbeater")
442-
t.start()
456+
thread.setDaemon(true)
457+
thread.setName("driver-heartbeater")
458+
thread.start()
443459
}
444460
}

0 commit comments

Comments
 (0)