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
Invoker graceful shutdown and drain mode #3704
Conversation
Codecov Report
@@ Coverage Diff @@
## master #3704 +/- ##
==========================================
+ Coverage 74.34% 74.79% +0.44%
==========================================
Files 128 128
Lines 6081 6129 +48
Branches 398 387 -11
==========================================
+ Hits 4521 4584 +63
+ Misses 1560 1545 -15
Continue to review full report at Codecov.
|
I believe this is first management api over REST which allows change of state of system. Does this api has some form of security enabled? |
+1 @chetanmeh The administrative interfaces should have authentication in place. While at it, we should apply the same to the |
What do we gain over Have you thought about using kill-signal based functionality here to integrate better with tools like kube? |
I vote for not using url I consider this anti pattern. I preferred os.Signal for use cases that “docker CLI” doesn’t cover CLI already if any. |
Both Kubernetes and Mesos support graceful shutdown via So if we can ensure that Invoker can finish up shutdown in this time window then we should rely on this support. If at all we need to expose a url endpoint then we can secure it via leveraging same credentials which we use for JMX authentication (either directly or probably via some form of JAAS auth) |
becb227
to
768f6b2
Compare
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.
@csantanapr, @chetanmeh, I pushed a second commit that uses sigterm to put the invoker in maintenance mode. To put an invoker in this mode, execute: docker kill --signal=USR2 invoker0
.
I think it is important to note that maintenance mode and a graceful shutdown are two different features. A graceful shutdown would have to check to see if the invoker's queue is empty and that there are no user containers running, which I think is useful. Whereas maintenance just marks the invoker as down in the load balancer.
case "USR2" => | ||
logger.info(this, s"Stopping health scheduler") | ||
actorSystem.stop(healthScheduler) | ||
while (true) {} |
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.
For a graceful shutdown here, we would need to make sure user containers are not running and possibly check that the queue is empty.
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.
By queue do you mean the internal message feed or the kafka topic? The topic doesn't need to be empty as that can take an indeterminate amount of time --- bringing up another invoker to take over the topic would address the messages held in the topic.
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.
For the Kafka topic, I mean. Need to find a way to make the invoker stop consuming messages from the topic. Will take a look.
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.
For the above, might just be able to shutdown the activationFeed
actor.
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.
I thought the feed had a shutdown hook. It is worth adding one.
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.
Looks like there is a shutdown hook for containerFactory
. Perhaps maintenance mode is not necessary if we have a graceful shutdown.
@chetanmeh 30s may not be enough time to drain all the request live in memory since each request could be for the max allowed duration. This assumes draining means execute the outstanding requests. It could also mean post these requests back to the topic and let some other invoker deal with them. For that, 30s is likely more suitable. |
Correct, maintenance mode and graceful shutdown are different things. What's the benefit of this extra kill signal vs. just killing the whole invoker? Running actions will die either way. |
@markusthoemmes, as long as the thread is busy where we hook the signal the invoker will not shutdown. Currently there is just an infinite loop there, but a graceful shutdown would keep the thread busy until a shutdown can happen. |
768f6b2
to
64a9026
Compare
} | ||
}) | ||
|
||
Signal.handle( |
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.
Code is still a little clunky, but I think the behavior is right. Ie, stopping the health scheduler, stopping the Kafka message consumer, and waiting for the user containers to finish.
6f3cb5c
to
558647a
Compare
@dgrove-oss, do you think these changes will work with Kube's graceful shutdown feature now? |
@dgrove-oss, I am using |
This looks pretty good to me. I think we'd just need to tweak invoker.yaml to set the container's |
Max duration isn’t enough since there could be requests queued still in memory (feeds are double buffered). |
I'm wondering if there is a race condition possible: Can the pool be intermittently non-busy while getting new messages from the feed? That is: Should we attach this condition to the feed as in:
|
There’s no other way to know the invoker has quiesced - but a processed message isn’t enough since the pool releases the resource (sends the message) before all the book keeping has finished. For example outstanding db operations might still be in flight to record the activation metadata. |
while (Await.result(pool ? Busy, timeout.duration).asInstanceOf[Boolean] == true) { | ||
logging.info(this, s"Container pool is busy") | ||
Thread.sleep(1000) | ||
} |
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.
WDYT about applying some Future composition here to simplify the flow, like
/** Polls the pools status and returns a future which completes once the pool is idle. */
def waitForContainerPoolIdle(pool: ActorRef): Future[Unit] = {
(pool ? Busy).mapTo[Boolean].flatMap {
case false => akka.pattern.after(1.second, system.scheduler)(waitForContainerPoolIdle(pool))
case true => Future.successful(())
}.recoverWith { case _ => akka.pattern.after(1.second, system.scheduler) }
}
val shutdowns = Seq(
gracefulStop(healthScheduler, 5.seconds).recover { case _ => logging.info(this, "Health communication failed to shutdown gracefully")},
gracefulStop(activationFeed, 5.seconds).recover { case _ => logging.info(this, "Activation feed failed to shutdown gracefully")},
waitForContainerPoolIdle(pool))
// Allow the shutdown to take a maximum of 3 times the maximum action runtime since the
// feed can be buffered and we want to allow for some grace period.
Await.result(shutdowns, TimeLimit.MAX_DURATION * 3)
Does that make sense?
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.
I like it!
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.
Can you emit a log message while waiting for quiescence?
sendOutstandingMessages() | ||
stay | ||
|
||
case Event(FillCompleted(messages), _) => |
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.
Still concerned about a race condition here. There might be a chance that the Busy
event returns false
while fillPipeline()
is running and has yet to trigger the FillCompleted
event.
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.
right - if filling, you need to wait.
you'll also need to prevent a further fill.
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.
Hmm, do we know if events are handled in synchronous or asynchronous fashion in the Akka FSM?
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.
Always asynchronous for akka.
5c83169
to
5db3dc5
Compare
5db3dc5
to
ecc2308
Compare
try { | ||
// Allow the shutdown to take a maximum of 3 times the maximum action runtime since the feed can be | ||
// buffered and we want to allow for some grace period. | ||
Await.result(shutdowns, TimeLimit.MAX_DURATION * 3) |
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.
May be we return a different exit code in case of unclean shutdown
} finally { | ||
containerFactory.cleanup() | ||
logging.info(this, "Shutting down invoker") | ||
System.exit(0) |
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.
Given its a System.exit
we should catch and log exception of Await
operation
// Capture SIGTERM signals to gracefully shutdown the invoker. When gracefully shutting down, the health scheduler is | ||
// shutdown preventing additional actions from being scheduler to the invoker, then the invoker processes its buffered | ||
// messages from the activation feed, and waits for its user containers to finish running before the process exits. | ||
Signal.handle( |
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.
Do we need to rely on Signal
here or normal shutdown hook would work as its also invoked in response to SIGTERM
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.
From experimenting, order of shutdown hooks is not guaranteed. That means the actor system may shutdown before the graceful shutdown hook is executed. However, the actor system needs to be running in order to perform the graceful shutdown.
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.
Okie. Reading this comment and Signal Source Docs it appeared that JVM would ignore any handler for signals for which it registers its own handler like SIGTERM.
* Java code cannot register a handler for signals that are already used
* by the Java VM implementation. The <code>Signal.handle</code>
* function raises an <code>IllegalArgumentException</code> if such an attempt
* is made.
Per comment Oracle JDK would not throw exception and silently ignore the handler. So was not sure if current approach would always work or not
14d3683
to
bcafc9c
Compare
bcafc9c
to
3c5855f
Compare
@dubee what's the status of this? Seems like you've run into some dead-ends/race conditions? |
Closing this as there seem to be race conditions. |
Allows an invoker to manually be marked as down so that maintenance can be performed on the invoker. When in maintenance mode, the invoker will finish executing all actions currently running and finish processing its queued actions. Since an invoker in maintenance mode will appear as down to the load balancer, new actions will not be queued up for the invoker. The invoker will exit maintenance mode when it is restarted.
Related to #3681
Description
Related issue and scope
My changes affect the following components
Types of changes
Checklist: