Lifecycle Service
The lifecycle service is used to orchestrate the workflow and states of a simulation participant.
It provides access to the ITimeSyncService
, registers callbacks for state changes, queries the participant’s state and issues commands to change the state.
For an overview of a participant’s state and its relation to the simulation refer to the participant lifecycle section.
Configuring the Lifecycle Service
To create the lifecycle service, a valid lifecycle configuration must be provided. Currently, only the mode that the lifecycle operates in can be configured. There are two possible operation modes:
Coordinated:
The lifecycle service will coordinate its state with other participants. It will do so by reacting to changes of the system state, which is based on the list of required participants.
A coordinated participant will only work, if any participant set a list of required participants as part of
ISystemController::SetWorkflowConfiguration()
.Coordinated participants will stop, as soon as the system state changes to the stopping state.
Coordinated participants can also be terminated externally using the
ISystemController::AbortSimulation()
call.
Autonomous:
An autonomous participant will not align its state to the system state or any participant state of other participants.
The lifecycle service will run through all participant states until it is running, and it will trigger all callbacks.
Autonomous participants must stop themselves by explicitly calling
Stop()
.The only way to stop an autonomous participant externally is the
ISystemController::AbortSimulation()
call.
Using the Lifecycle Service
An ILifecycleService
instance can be retrieved from a Participant.
To do so, a lifecycle configuration must be provided:
using SilKit::Services::Orchestration
auto lifecycleConfig = LifecycleConfiguration{OperationMode::Coordinated};
auto* lifecycleService = participant->CreateLifecycleService(lifecycleConfig);
Note
Please note that the lifecycle service and the time synchronization service can only be created once. Calling these methods more than once throws an exception.
Participants can synchronize their time with other participants (see synchronized simulation run for details):
auto* timeSyncService = lifecycleService->CreateTimeSyncService();
(...)
lifecycleService->StartLifecycle();
Alternatively, participants can run asynchronously (regarding the time synchronization).
This will happen if CreateTimeSyncService
was not called before StartLifecycle
.
There are several callbacks, which are triggered on state transitions. They are always executed in the middleware’s worker thread:
lifecycleService->SetCommunicationReadyHandler(
[]() {
std::cout << "Communication ready" << std::endl;
}
);
lifecycleService->SetStopHandler(
[]() {
std::cout << "Stopping" << std::endl;
}
);
lifecycleService->SetShutdownHandler(
[]() {
std::cout << "Shutting down" << std::endl;
}
);
lifecycleService->SetAbortHandler(
[](auto /*participantState*/) {
std::cout << "Simulation is aborting" << std::endl;
}
);
If a participant does not use the virtual time synchronization, a separate callback informs about the transition to the Running
state.
This can be used to start local timers:
lifecycleService->SetStartingHandler(
[]() {
std::cout << "Starting" << std::endl;
}
);
The CommunicationReadyHandler
should be used to initialize vehicle network controllers that require an inital configuration.
This will make sure that the controller configuration is available to all participants before the simulation start.
API calls that are recommended to be used in this handler are:
* ICanController::Start()
* ICanController::SetBaudRate()
* ILinController::Init()
* IEthernetController::Activate()
* IFlexrayController::Configure()
Communication guarantees
The following communication guarantees apply to participants that utilize a Lifecycle Service and are limited to RPC and Publish/Subscribe services.
Bus systems have definitions on the protocol level for message acknowledgment.
For RPC or Publish/Subscribe however, communication guarantees depend on the implementation.
In the SIL Kit, the CommunicationReadyHandler
handler is the first point at which guarantees can be given.
This implies that StartLifecycle
has already been called.
Remote services that have been created at the point a participant calls StartLifecycle
will be available for communication as soon as the CommunicationReadyHandler
is triggered.
As an example, a call to Publish()
on a IDataPublisher
on participant A in the CommunicationReadyHandler
is guaranteed to arrive at
participant B if its IDataPublisher
has been created before participant A has called StartLifecycle
.
Communication in the CommunicationReadyHandler
between participants that use the OperationMode::Coordinated
is guaranteed.
After StartLifecycle
is called, these participants will wait for each other until all reached the ServicesCreated
state.
Therefore all services have already been created when the CommunicationReadyHandler
is triggered.
A common use-case where the guarantee also applies is described as follows: A participant with OperationMode::Autonomous
wants to join an already running simulation.
In this case, the services of the running participants have been created and the newly joining participant can communicate in the CommunicationReadyHandler
.
If several participants are defined with OperationMode::Autonomous
and are executed ‘simultaneously’ in a distributed setup, their intention is to proceed through the lifecycle states without coordination.
In this case, there is no deterministic execution order of controller creation and calls to StartLifecycle
across the processes.
Therefore, it is impossible to guarantee that a remote subscriber already exists at the time a participants tries to Publish()
in the CommunicationReadyHandler
.
Without the useage of a lifecycle, there is no way to synchronize controller creation across different participants.
In other words, controllers can appear at any time and there is no call to the SIL Kit API to build up a communication guarantee on.
In cases were such guarantees are indespensable, it is recommended to use a ILifecycleService
.
Note that a IDataPublisher
can be defined with a history (of length 1).
This guarantees the delivery of the last publication but is not coupled to the lifecycle states.
Another guarantee can be given for communication in the SimulationStepHandler.
All participants that take part in the distributed time algorithm are ready to exchange messages if the first SimulationStepHandler is triggered, regardless of their OperationMode
.
Controlling the Participant
After a successful startup, the participant will enter the Running
state.
State()
returns the current state as a plain enumeration, whereas Status()
returns additional information such as the participant’s name, the human-readable reason for entering the state, and the wall clock time when the state was entered.
To temporarily pause a simulation task, the Pause()
method can be invoked with a human-readable explanation as a string argument.
Execution can be resumed using the Continue()
method.
To abort the simulation and report an error message, use the ReportError()
method.
This will change the current participant state to the Error
state and report the error message to the SIL Kit runtime system.
ReportError()
is also called when the invocation of a registered handler throws an exception.
To stop a particular participant, use the Stop()
method.
This will exit Running
or Paused
, call the registered StopHandler
(if it was set) and switch to the Stopped
state.
Afterwards, the participant will shut down by first changing to the ShuttingDown
state, which triggers the shutdown handler (if it was set) and then finishing at the Shutdown
state.
At this point, the future provided by StartLifecycle
will return.
API Reference
-
class ILifecycleService
Public Functions
-
virtual ~ILifecycleService() = default
-
virtual void SetCommunicationReadyHandler(CommunicationReadyHandler handler) = 0
Register a callback that is executed once all communication channels between participants with a lifecycle have been set up and are ready for communication.
The handler is called when the participant reaches the ParticipantState::CommunicationInitialized. This handler is invoked in an internal thread, and receiving messages during the handler invocation is not possible. For an asynchronous invocation, see SetCommunicationReadyHandlerAsync and CompleteCommunicationReadyHandlerAsync. After the handler has been processed, the participant switches to the ParticipantState::ReadyToRun state.
-
virtual void SetCommunicationReadyHandlerAsync(CommunicationReadyHandler handler) = 0
Register a callback that is executed once all communication channels between participants with a lifecycle have been set up and are ready for communication.
The handler is called when the participant reaches the ParticipantState::CommunicationInitialized. Even after the callback returns, the participant will stay in the ParticipantState::CommunicationInitialized until the CompleteCommunicationReadyHandlerAsync() method is called. Only then will the participant switch to the ParticipantState::ReadyToRun. Note that CompleteCommunicationReadyHandlerAsync may not be called from within any CommunicationReadyHandler. The CommunicationReadyHandler is executed in an internal thread and must not be blocked by the user.
-
virtual void CompleteCommunicationReadyHandlerAsync() = 0
Notify that the async CommunicationReadyHandler is completed.
This method must not be invoked from within a CommunicationReadyHandler.
-
virtual void SetStartingHandler(StartingHandler handler) = 0
(Participants without TimeSyncService only) Register a callback that is executed once directly before the participant enters ParticipantState::Running.
This handler is triggered just before the participant changes to ParticipantState::Running. It is only triggered if the participant does NOT use virtual time synchronization. It does not block other participants from changing to ParticipantState::Running and should only be used for lightweight operations such as starting timers. It is executed in the context of an internal thread that received the command. After the handler has been processed, the participant switches to the ParticipantState::Running state.
-
virtual void SetStopHandler(StopHandler handler) = 0
Register a callback that is executed on simulation stop.
The handler is called when the participant enters the ParticipantState::Stopping state. It is executed in the context of an internal thread that received the command. After the handler has been processed, the participant switches to the ParticipantState::Stopped state.
Throwing an error inside the handler will cause a call to ReportError().
-
virtual void SetShutdownHandler(ShutdownHandler handler) = 0
Register a callback that is executed on simulation shutdown.
The handler is called when the participant enters the ParticipantState::ShuttingDown state. It is executed in the context of an internal thread that received the command. After the handler has been processed, the participant switches to the ParticipantState::Shutdown state and is allowed to terminate.
Throwing an error inside the handler will cause a call to ReportError().
-
virtual void SetAbortHandler(AbortHandler handler) = 0
Register a callback that is executed on simulation abort.
The handler is called when the participant enters the ParticipantState::Aborting. It is executed in the context of an internal thread that received the command. After the handler has been processed, the participant switches to the ParticipantState::Shutdown state and is allowed to terminate.
Throwing an error inside the handler will cause a call to ReportError().
-
virtual auto StartLifecycle() -> std::future<ParticipantState> = 0
Start non blocking operation; returns immediately.
Starts simulation until the simulation is stopped or aborted.
- Returns
Future that will hold the final state of the participant once the LifecycleService finishes operation.
-
virtual void ReportError(std::string errorMsg) = 0
Abort current simulation run due to an error.
Switch to the ParticipantState::Error state and report the error message in the SIL Kit system.
-
virtual void Pause(std::string reason) = 0
Pause execution of the participant.
Switch to ParticipantState::Paused due to the provided
reason
.When a client is in state ParticipantState::Paused, it must not be considered as unresponsive even if a health monitoring related timeout occurs.
Precondition: State() == ParticipantState::Running
-
virtual void Continue() = 0
Switch back to ParticipantState::Running after having paused.
Precondition: State() == ParticipantState::Paused
-
virtual void Stop(std::string reason) = 0
Stop execution of the participant.
Allows the participant to exit the RunAsync loop, e.g., if it is unable to further progress its simulation.
Calls the StopHandler and then switches to the ParticipantState::Stopped state.
NB: In general, Stop should not be called by the participants as the end of simulation is governed by the central execution controller. This method should only be used if the client cannot participate in the system simulation anymore.
Precondition: State() == ParticipantState::Running
-
virtual auto State() const -> ParticipantState = 0
Get the current participant status.
-
virtual auto Status() const -> const ParticipantStatus& = 0
Get the current participant status.
-
virtual auto CreateTimeSyncService() -> ITimeSyncService* = 0
Return the ITimeSyncService for the current ILifecycleService.
-
virtual ~ILifecycleService() = default
Handlers
-
using SilKit::Services::Orchestration::CommunicationReadyHandler = std::function<void()>
This handler is called after ParticipantState::CommunicationInitialized is reached Cf., ILifecycleService::SetCommunicationReadyHandler(CommunicationReadyHandler);
-
using SilKit::Services::Orchestration::StartingHandler = std::function<void()>
This handler is called before the participant enters ParticipantState::Running. Cf., ILifecycleService::SetStartingHandler(StartingHandler);
-
using SilKit::Services::Orchestration::StopHandler = std::function<void()>
This handler is called after ParticipantState::Stopping is reached Cf., ILifecycleService::SetStopHandler(StopHandler);
-
using SilKit::Services::Orchestration::ShutdownHandler = std::function<void()>
This handler is called after ParticipantState::ShuttingDown is reached Cf., ILifecycleService::SetShutdownHandler(ShutdownHandler);
-
using SilKit::Services::Orchestration::AbortHandler = std::function<void(ParticipantState)>
This handler is called after ParticipantState::Aborting is reached Cf., ILifecycleService::SetAbortHandler(AbortHandler);
Data Structures
-
struct LifecycleConfiguration
The lifecycle start configuration.
Public Members
-
OperationMode operationMode
-
OperationMode operationMode
-
enum SilKit::Services::Orchestration::OperationMode
Available operation modes of the lifecycle service.
Values:
-
enumerator Invalid
An invalid operation mode.
-
enumerator Coordinated
The coordinated operation mode.
-
enumerator Autonomous
The autonomous operation mode.
-
enumerator Invalid
Usage Example
The following example is based on the SilKitCanDemo
source code which is distributed with the SIL Kit, and slightly adapted for clarity.
It demonstrates how to setup a lifecycle service and register callbacks to monitor participant state changes.
To demonstrate how to properly initialize other services, a CAN controller is initialized within the CommunicationReady
callback of the lifecycle service.
This is the recommended way to set up controllers before first use.
/* Copyright (c) 2022 Vector Informatik GmbH
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
auto participant = SilKit::CreateParticipant(config, participantName, registryUri);
auto* lifecycleService = participant->CreateLifecycleService({OperationMode::Autonomous});
auto* canController = participant->CreateCanController("CAN1", "CAN1");
canController->AddFrameTransmitHandler(
[](Can::ICanController* /*ctrl*/, const Can::CanFrameTransmitEvent& ack) {
// Asynchroneously handle transmit status
});
canController->AddFrameHandler(
[](Can::ICanController* /*ctrl*/, const Can::CanFrameEvent& frameEvent) {
// Asynchroneously handle message reception
});
// Set an Init Handler
lifecycleService->SetCommunicationReadyHandler(
[canController, &participantName]() {
std::cout << "Initializing " << participantName << std::endl;
canController->SetBaudRate(10000, 1000000);
canController->Start();
});
// Set a Stop Handler
lifecycleService->SetStopHandler([]() {
std::cout << "Stopping..." << std::endl;
});
// Set a Shutdown Handler
lifecycleService->SetShutdownHandler([]() {
std::cout << "Shutting down..." << std::endl;
});
auto finalStateFuture = lifecycleService->StartLifecycle();
auto finalState = finalStateFuture.get();