@@ -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 */
4447private [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