-
Notifications
You must be signed in to change notification settings - Fork 50
[SPARK-8167] Tasks that fail from YARN preemption should not fail job #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
63cd958
1100ed8
08d2bdd
62e4342
bd1056e
2bbdcfd
a697c7c
3f1104f
e791cd6
aa69b6f
e3e827f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,12 +17,13 @@ | |
|
|
||
| package org.apache.spark.scheduler.cluster | ||
|
|
||
| import scala.collection.mutable.{ArrayBuffer, HashSet} | ||
| import scala.concurrent.{Future, ExecutionContext} | ||
|
|
||
| import org.apache.spark.{Logging, SparkContext} | ||
| import org.apache.spark.rpc._ | ||
| import org.apache.spark.scheduler.cluster.CoarseGrainedClusterMessages._ | ||
| import org.apache.spark.scheduler.TaskSchedulerImpl | ||
| import org.apache.spark.scheduler._ | ||
| import org.apache.spark.ui.JettyUtils | ||
| import org.apache.spark.util.{ThreadUtils, RpcUtils} | ||
|
|
||
|
|
@@ -89,6 +90,47 @@ private[spark] abstract class YarnSchedulerBackend( | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Override the DriverEndpoint to add extra logic for the case when an executor is disconnected. | ||
| * We should check the cluster manager and find if the loss of the executor was caused by YARN | ||
| * force killing it due to preemption. | ||
| */ | ||
| private class YarnDriverEndpoint(rpcEnv: RpcEnv, sparkProperties: ArrayBuffer[(String, String)]) | ||
| extends DriverEndpoint(rpcEnv, sparkProperties) { | ||
|
|
||
| private val pendingDisconnectedExecutors = new HashSet[String] | ||
| private val handleDisconnectedExecutorThreadPool = | ||
| ThreadUtils.newDaemonCachedThreadPool("yarn-driver-endpoint-handle-disconnected-executor-thread-pool") | ||
|
|
||
| override def onDisconnected(rpcAddress: RpcAddress): Unit = { | ||
| addressToExecutorId.get(rpcAddress).foreach({ executorId => | ||
| pendingDisconnectedExecutors.synchronized { | ||
| if (!pendingDisconnectedExecutors.contains(executorId)) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you need to keep this hashset? Is it possible that the same executors appear twice in the onDisconnected() callback?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Happened to me in local testing, but it's still not clear why. Might be a weird Akka thing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
| pendingDisconnectedExecutors.add(executorId) | ||
| handleDisconnectedExecutorThreadPool.submit(new Runnable() { | ||
| override def run(): Unit = { | ||
| val executorLossReason = yarnSchedulerEndpoint.askWithRetry[Option[ExecutorLossReason]](GetExecutorLossReason(executorId)) | ||
| executorLossReason match { | ||
| case Some(reason) => driverEndpoint.askWithRetry[Boolean](RemoveExecutor(executorId, reason)) | ||
| case None => | ||
| logWarning(s"Attempted to get executor loss reason for $rpcAddress but got no response. Marking as slave lost.") | ||
| driverEndpoint.askWithRetry[Boolean](RemoveExecutor(executorId, SlaveLost())) | ||
| } | ||
| pendingDisconnectedExecutors.synchronized { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if it's overkill to be cleaning up this data structure here... |
||
| pendingDisconnectedExecutors.remove(executorId) | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| override def createDriverEndpoint(properties: ArrayBuffer[(String, String)]): DriverEndpoint = { | ||
| new YarnDriverEndpoint(rpcEnv, properties) | ||
| } | ||
|
|
||
| /** | ||
| * An [[RpcEndpoint]] that communicates with the ApplicationMaster. | ||
| */ | ||
|
|
@@ -141,6 +183,20 @@ private[spark] abstract class YarnSchedulerBackend( | |
| context.reply(false) | ||
| } | ||
|
|
||
| case c: GetExecutorLossReason => | ||
| amEndpoint match { | ||
| case Some(am) => | ||
| Future { | ||
| context.reply(am.askWithRetry[Option[ExecutorLossReason]](c)) | ||
| } onFailure { | ||
| case NonFatal(e) => | ||
| logError(s"Finding the executor loss reason was unsuccessful", e) | ||
| context.sendFailure(e) | ||
| } | ||
| case None => | ||
| logWarning("Attempted to check if an executor exited normally before the AM has registered!") | ||
| context.reply(None) | ||
| } | ||
| } | ||
|
|
||
| override def onDisconnected(remoteAddress: RpcAddress): Unit = { | ||
|
|
@@ -155,6 +211,7 @@ private[spark] abstract class YarnSchedulerBackend( | |
| } | ||
| } | ||
|
|
||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. revert |
||
| private[spark] object YarnSchedulerBackend { | ||
| val ENDPOINT_NAME = "YarnScheduler" | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how I feel about creating the subclass of DriverEndpoint here.
The architecture basically only enforces changing the behavior in YARN mode. One could conceivably however want to do something similar in standalone mode, e.g. ask the Spark master why an executor terminated. But to be safe and to minimize the places this change impacts I tried to localize everything to just YARN mode.