@@ -21,7 +21,7 @@ import java.io.File
2121import java .lang .management .ManagementFactory
2222import java .net .URL
2323import java .nio .ByteBuffer
24- import java .util .concurrent ._
24+ import java .util .concurrent .ConcurrentHashMap
2525
2626import scala .collection .JavaConversions ._
2727import scala .collection .mutable .{ArrayBuffer , HashMap }
@@ -31,24 +31,26 @@ import akka.actor.Props
3131
3232import org .apache .spark ._
3333import org .apache .spark .deploy .SparkHadoopUtil
34- import org .apache .spark .scheduler ._
34+ import org .apache .spark .scheduler .{ DirectTaskResult , IndirectTaskResult , Task }
3535import org .apache .spark .shuffle .FetchFailedException
3636import 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 */
4446private [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