Skip to content

Commit 020efbb

Browse files
committed
Tighten up field/method visibility in Executor and made some code more clear to read.
1 parent f17d43b commit 020efbb

5 files changed

Lines changed: 110 additions & 96 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: 96 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ import org.apache.spark.util.{ChildFirstURLClassLoader, MutableURLClassLoader,
3838
SparkUncaughtExceptionHandler, AkkaUtils, Utils}
3939

4040
/**
41-
* Spark executor used with Mesos, YARN, and the standalone scheduler.
42-
* In coarse-grained mode, an existing actor system is provided.
41+
* Spark executor, backed by a threadpool to run tasks.
42+
*
43+
* This can be used with Mesos, YARN, and the standalone scheduler.
44+
* An internal RPC interface (at the moment Akka) is used for communication with the driver,
45+
* except in the case of Mesos fine-grained mode.
4346
*/
4447
private[spark] class Executor(
4548
executorId: String,
@@ -78,9 +81,8 @@ private[spark] class Executor(
7881
}
7982

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

8587
if (!isLocal) {
8688
env.metricsSystem.registerSource(executorSource)
@@ -122,21 +124,21 @@ private[spark] class Executor(
122124
taskId: Long,
123125
attemptNumber: Int,
124126
taskName: String,
125-
serializedTask: ByteBuffer) {
127+
serializedTask: ByteBuffer): Unit = {
126128
val tr = new TaskRunner(context, taskId = taskId, attemptNumber = attemptNumber, taskName,
127129
serializedTask)
128130
runningTasks.put(taskId, tr)
129131
threadPool.execute(tr)
130132
}
131133

132-
def killTask(taskId: Long, interruptThread: Boolean) {
134+
def killTask(taskId: Long, interruptThread: Boolean): Unit = {
133135
val tr = runningTasks.get(taskId)
134136
if (tr != null) {
135137
tr.kill(interruptThread)
136138
}
137139
}
138140

139-
def stop() {
141+
def stop(): Unit = {
140142
env.metricsSystem.report()
141143
env.actorSystem.stop(executorActor)
142144
isStopped = true
@@ -146,7 +148,10 @@ private[spark] class Executor(
146148
}
147149
}
148150

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

151156
class TaskRunner(
152157
execBackend: ExecutorBackend,
@@ -156,27 +161,34 @@ private[spark] class Executor(
156161
serializedTask: ByteBuffer)
157162
extends Runnable {
158163

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

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

172-
override def run() {
184+
override def run(): Unit = {
173185
val deserializeStartTime = System.currentTimeMillis()
174186
Thread.currentThread.setContextClassLoader(replClassLoader)
175187
val ser = env.closureSerializer.newInstance()
176188
logInfo(s"Running $taskName (TID $taskId)")
177189
execBackend.statusUpdate(taskId, TaskState.RUNNING, EMPTY_BYTE_BUFFER)
178190
var taskStart: Long = 0
179-
startGCTime = gcTime
191+
startGCTime = computeTotalGcTime()
180192

181193
try {
182194
val (taskFiles, taskJars, taskBytes) = Task.deserializeWithDependencies(serializedTask)
@@ -193,7 +205,6 @@ private[spark] class Executor(
193205
throw new TaskKilledException
194206
}
195207

196-
attemptedTask = Some(task)
197208
logDebug("Task " + taskId + "'s epoch is " + task.epoch)
198209
env.mapOutputTracker.updateEpoch(task.epoch)
199210

@@ -215,18 +226,17 @@ private[spark] class Executor(
215226
for (m <- task.metrics) {
216227
m.setExecutorDeserializeTime(taskStart - deserializeStartTime)
217228
m.setExecutorRunTime(taskFinish - taskStart)
218-
m.setJvmGCTime(gcTime - startGCTime)
229+
m.setJvmGCTime(computeTotalGcTime() - startGCTime)
219230
m.setResultSerializationTime(afterSerialization - beforeSerialization)
220231
}
221232

222233
val accumUpdates = Accumulators.values
223-
224234
val directResult = new DirectTaskResult(valueBytes, accumUpdates, task.metrics.orNull)
225235
val serializedDirectResult = ser.serialize(directResult)
226236
val resultSize = serializedDirectResult.limit
227237

228238
// directSend = sending directly back to the driver
229-
val serializedResult = {
239+
val serializedResult: ByteBuffer = {
230240
if (maxResultSize > 0 && resultSize > maxResultSize) {
231241
logWarning(s"Finished $taskName (TID $taskId). Result is larger than maxResultSize " +
232242
s"(${Utils.bytesToString(resultSize)} > ${Utils.bytesToString(maxResultSize)}), " +
@@ -248,42 +258,40 @@ private[spark] class Executor(
248258
execBackend.statusUpdate(taskId, TaskState.FINISHED, serializedResult)
249259

250260
} catch {
251-
case ffe: FetchFailedException => {
261+
case ffe: FetchFailedException =>
252262
val reason = ffe.toTaskEndReason
253263
execBackend.statusUpdate(taskId, TaskState.FAILED, ser.serialize(reason))
254-
}
255264

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

261-
case cDE: CommitDeniedException => {
269+
case cDE: CommitDeniedException =>
262270
val reason = cDE.toTaskEndReason
263271
execBackend.statusUpdate(taskId, TaskState.FAILED, ser.serialize(reason))
264-
}
265272

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

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)
279+
val metrics: Option[TaskMetrics] = Option(task).flatMap { task =>
280+
task.metrics.map { m =>
281+
m.setExecutorRunTime(System.currentTimeMillis() - taskStart)
282+
m.setJvmGCTime(computeTotalGcTime() - startGCTime)
283+
m
284+
}
277285
}
278-
val reason = new ExceptionFailure(t, metrics)
279-
execBackend.statusUpdate(taskId, TaskState.FAILED, ser.serialize(reason))
286+
val taskEndReason = new ExceptionFailure(t, metrics)
287+
execBackend.statusUpdate(taskId, TaskState.FAILED, ser.serialize(taskEndReason))
280288

281289
// Don't forcibly exit unless the exception was inherently fatal, to avoid
282290
// stopping other tasks unnecessarily.
283291
if (Utils.isFatalError(t)) {
284292
SparkUncaughtExceptionHandler.uncaughtException(t)
285293
}
286-
}
294+
287295
} finally {
288296
// Release memory used by this thread for shuffles
289297
env.shuffleMemoryManager.releaseMemoryForThisThread()
@@ -358,7 +366,7 @@ private[spark] class Executor(
358366
for ((name, timestamp) <- newFiles if currentFiles.getOrElse(name, -1L) < timestamp) {
359367
logInfo("Fetching " + name + " with timestamp " + timestamp)
360368
// Fetch file with useCache mode, close cache for local mode.
361-
Utils.fetchFile(name, new File(SparkFiles.getRootDirectory), conf,
369+
Utils.fetchFile(name, new File(SparkFiles.getRootDirectory()), conf,
362370
env.securityManager, hadoopConf, timestamp, useCache = !isLocal)
363371
currentFiles(name) = timestamp
364372
}
@@ -370,12 +378,12 @@ private[spark] class Executor(
370378
if (currentTimeStamp < timestamp) {
371379
logInfo("Fetching " + name + " with timestamp " + timestamp)
372380
// Fetch file with useCache mode, close cache for local mode.
373-
Utils.fetchFile(name, new File(SparkFiles.getRootDirectory), conf,
381+
Utils.fetchFile(name, new File(SparkFiles.getRootDirectory()), conf,
374382
env.securityManager, hadoopConf, timestamp, useCache = !isLocal)
375383
currentJars(name) = timestamp
376384
// Add it to our class loader
377-
val url = new File(SparkFiles.getRootDirectory, localName).toURI.toURL
378-
if (!urlClassLoader.getURLs.contains(url)) {
385+
val url = new File(SparkFiles.getRootDirectory(), localName).toURI.toURL
386+
if (!urlClassLoader.getURLs().contains(url)) {
379387
logInfo("Adding " + url + " to class loader")
380388
urlClassLoader.addURL(url)
381389
}
@@ -384,61 +392,69 @@ private[spark] class Executor(
384392
}
385393
}
386394

387-
def startDriverHeartbeater() {
388-
val interval = conf.getInt("spark.executor.heartbeatInterval", 10000)
395+
/** Reports heartbeat and metrics for active tasks to the driver. */
396+
private def reportHeartBeat(): Unit = {
389397
val timeout = AkkaUtils.lookupTimeout(conf)
390398
val retryAttempts = AkkaUtils.numRetries(conf)
391399
val retryIntervalMs = AkkaUtils.retryWaitMs(conf)
392400
val heartbeatReceiverRef = AkkaUtils.makeDriverRef("HeartbeatReceiver", conf, env.actorSystem)
393401

394-
val t = new Thread() {
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+
}
426+
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() {
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-heartbeat")
458+
thread.start()
443459
}
444460
}

0 commit comments

Comments
 (0)