Shutdown Process

The overall shutdown process looks like this:

  • shutdown() on all stages is invoked
  • all stages with threads are expected to terminate those threads before leaving shutdown()
  • the executor waits for all message queues to fully drain
  • finalize() on all stages is invoked
  • the pipeline exits with a status code (0 for success, anything else for failure)

Shutdown Tokens

Particularly for simulations or other offline-processing pipelines, it can be useful for a stage to request a shutdown of the pipeline.

(Normally, a pipeline only shuts down when all of its timers are done firing and its queues are empty – but for complex pipelines or pipelines with things like the log player, which intentionally keep their timers firing, this can lead to hanging on shutdown).

Any stage can request a shutdown token, which allows you to register when you are ready to shutdown.

You can retrieve these during your initialization stage:

#include "ark/pipeline/shutdown_token.hh"

ark::pipeline::ShutdownTokenPtr my_token_;

void initialize(ark::pipeline::StageInterface &interface)
{
    my_token_ = interface.add_shutdown_token(ark::pipeline::ShutdownTokenType::Standard);
}

Once you have a token, at any time, you can request a shutdown. For normal, healthy shutdowns, use:

my_token_->request_successful_shutdown();

If an error occurred, you can give a reason:

my_token_->request_failure_shutdown("Something unrecoverable failed.");

In either case, the pipeline will shutdown as soon as possible (and shutdown will be deterministic in the case of running under the simclock executor, which is to say, the simclock executor will run until precise time that the shutdown token was invoked).

The executors execute method will return true/false to indicate if the pipeline exited successfully or failed. You can further use that to handle unexpected failures or to return non-zero from your main() API.

Token Types

You must pass in a ’type’ to the shutdown token. There are two types:

  • RequiredForShutdown
  • Standard

The RequiredForShutdown token type indicates that you must request a shutdown for the pipeline to exit. If other tokens request a shutdown, the pipeline will not exit until you request a shutdown.

The Standard token type allows you to request shutdown without holding up the rest of the system from requesting shutdown. In other words, if anyone requests a shutdown, the system will shutdown.

SIGINT and Requesting Shutdowns

When the executor’s interrupt method is called (for example, this is called when you hit CTRL+C or deliver a SIGINT and you are using the main APIs), all of the stages in the pipeline will have their interrupt() method invoked in turn.

This allows you to carry out complex shutdown logic that requires the full pipeline to be operational. A common usecase is to send out UDP or CAN packets to devices to shut them down gracefully.

If you do implement the ark::pipeline::Stage::interrupt() method, it is strongly advised that you take out a RequiredForShutdown token. Without this, the system will proceed to a normal shutdown immediately after interrupt() was invoked.

A simple example of this API:

class MyStage : public ark::pipeline::Stage
{
public:
    void initialize(ark::pipeline::StageInterface &interface) override
    {
        token_ = interface.add_shutdown_token(ark::pipeline::ShutdownTokenType::RequiredForShutdown);
    }

    void interrupt() override
    {
        token_->request_successful_shutdown(); 
    }

private:
    ShutdownTokenPtr token_;
};

This interrupt callback does no extra work; it simply requests a shutdown when a SIGINT arrives. If you do have a required-for-shutdown token, and interrupt is received, and you never request a shutdown through your token, the pipeline will not exit.