$darkmode
simulation_machine.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2024 Robert Bosch GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * SPDX-License-Identifier: Apache-2.0
17  */
75 #pragma once
76 
77 #include <future> // for future<>, async
78 
79 #include <cloe/core/abort.hpp> // for AsyncAbort
80 
81 #include "simulation_context.hpp" // for SimulationContext
82 #include "utility/state_machine.hpp" // for State, StateMachine
83 
84 namespace engine {
85 
101  : private StateMachine<State<SimulationMachine, SimulationContext>, SimulationContext> {
103 
104  public:
106  register_states({
107  new Connect{this},
108  new Start{this},
109  new Probe{this},
110  new StepBegin{this},
111  new StepSimulators{this},
112  new StepControllers{this},
113  new StepEnd{this},
114  new Pause{this},
115  new Resume{this},
116  new Success{this},
117  new Fail{this},
118  new Abort{this},
119  new Stop{this},
120  new Reset{this},
121  new KeepAlive{this},
122  new Disconnect{this},
123  });
124  }
125 
132  void run(SimulationContext& ctx) { run_machine(CONNECT, ctx); }
133 
138  void run_machine(StateId initial, SimulationContext& ctx) {
139  StateId id = initial;
140  std::optional<StateId> interrupt;
141 
142  // Keep processing states as long as they are coming either from
143  // an interrupt or from normal execution.
144  while ((interrupt = pop_interrupt()) || id != nullptr) {
145  try {
146  // Handle interrupts that have been inserted via push_interrupt.
147  // Only one interrupt is stored.
148  //
149  // If one interrupt follows another, the handler is responsible
150  // for restoring nominal flow after all is done.
151  if (interrupt) {
152  id = handle_interrupt(id, *interrupt, ctx);
153  continue;
154  }
155 
156  if (ctx.config.engine.watchdog_mode == cloe::WatchdogMode::Off) {
157  // Run state in this thread synchronously.
158  id = run_state(id, ctx);
159  continue;
160  }
161 
162  id = run_state_async(id, ctx);
163  } catch (cloe::AsyncAbort&) {
164  this->push_interrupt(ABORT);
165  } catch (cloe::ModelReset& e) {
166  logger()->error("Unhandled reset request in {} state: {}", id, e.what());
167  this->push_interrupt(RESET);
168  } catch (cloe::ModelStop& e) {
169  logger()->error("Unhandled stop request in {} state: {}", id, e.what());
170  this->push_interrupt(STOP);
171  } catch (cloe::ModelAbort& e) {
172  logger()->error("Unhandled abort request in {} state: {}", id, e.what());
173  this->push_interrupt(ABORT);
174  } catch (cloe::ModelError& e) {
175  logger()->error("Unhandled model error in {} state: {}", id, e.what());
176  this->push_interrupt(ABORT);
177  } catch (std::exception& e) {
178  logger()->critical("Fatal error in {} state: {}", id, e.what());
179  throw;
180  }
181  }
182  }
183 
191  StateId run_state_async(StateId id, SimulationContext& ctx) {
192  std::chrono::milliseconds timeout = ctx.config.engine.watchdog_default_timeout;
193  if (ctx.config.engine.watchdog_state_timeouts.count(id)) {
194  auto maybe = ctx.config.engine.watchdog_state_timeouts[id];
195  if (maybe) {
196  timeout = *maybe;
197  }
198  }
199  auto interval = timeout.count() > 0 ? timeout : ctx.config.engine.polling_interval;
200 
201  // Launch state
202  std::future<StateId> f =
203  std::async(std::launch::async, [this, id, &ctx]() { return run_state(id, ctx); });
204 
205  std::future_status status;
206  for (;;) {
207  status = f.wait_for(interval);
208  if (status == std::future_status::ready) {
209  return f.get();
210  } else if (status == std::future_status::deferred) {
211  if (timeout.count() > 0) {
212  logger()->warn("Watchdog waiting on deferred execution.");
213  }
214  } else if (status == std::future_status::timeout) {
215  if (timeout.count() > 0) {
216  logger()->critical("Watchdog timeout of {} ms exceeded for state: {}", timeout.count(),
217  id);
218 
219  if (ctx.config.engine.watchdog_mode == cloe::WatchdogMode::Abort) {
220  logger()->critical("Aborting simulation... this might take a while...");
221  return ABORT;
222  } else if (ctx.config.engine.watchdog_mode == cloe::WatchdogMode::Kill) {
223  logger()->critical("Killing program... this is going to be messy...");
224  std::abort();
225  }
226  }
227  }
228  }
229  }
230 
231  // Asynchronous Actions:
232  void pause() { this->push_interrupt(PAUSE); }
233  void resume() { this->push_interrupt(RESUME); }
234  void stop() { this->push_interrupt(STOP); }
235  void succeed() { this->push_interrupt(SUCCESS); }
236  void fail() { this->push_interrupt(FAIL); }
237  void reset() { this->push_interrupt(RESET); }
238  void abort() { this->push_interrupt(ABORT); }
239 
240  StateId handle_interrupt(StateId nominal, StateId interrupt, SimulationContext& ctx) override {
241  logger()->debug("Handle interrupt: {}", interrupt);
242  // We don't necessarily actually go directly to each desired state. The
243  // states PAUSE and RESUME are prime examples; they should be entered and
244  // exited from at pre-defined points.
245  if (interrupt == PAUSE) {
246  ctx.pause_execution = true;
247  } else if (interrupt == RESUME) {
248  ctx.pause_execution = false;
249  } else {
250  // All other interrupts will lead directly to the end of the
251  // simulation.
252  return this->run_state(interrupt, ctx);
253  }
254  return nominal;
255  }
256 
257  friend void to_json(cloe::Json& j, const SimulationMachine& m) {
258  j = cloe::Json{
259  {"states", m.states()},
260  };
261  }
262 
263 #define DEFINE_STATE(Id, S) DEFINE_STATE_STRUCT(SimulationMachine, SimulationContext, Id, S)
264  public:
265  DEFINE_STATE(CONNECT, Connect);
266  DEFINE_STATE(PROBE, Probe);
267  DEFINE_STATE(START, Start);
268  DEFINE_STATE(STEP_BEGIN, StepBegin);
269  DEFINE_STATE(STEP_SIMULATORS, StepSimulators);
270  DEFINE_STATE(STEP_CONTROLLERS, StepControllers);
271  DEFINE_STATE(STEP_END, StepEnd);
272  DEFINE_STATE(PAUSE, Pause);
273  DEFINE_STATE(RESUME, Resume);
274  DEFINE_STATE(SUCCESS, Success);
275  DEFINE_STATE(FAIL, Fail);
276  DEFINE_STATE(ABORT, Abort);
277  DEFINE_STATE(STOP, Stop);
278  DEFINE_STATE(RESET, Reset);
279  DEFINE_STATE(KEEP_ALIVE, KeepAlive);
280  DEFINE_STATE(DISCONNECT, Disconnect);
281 #undef DEFINE_STATE
282 };
283 
284 } // namespace engine
Definition: abort.hpp:45
Definition: model.hpp:85
Definition: model.hpp:62
Definition: model.hpp:106
Definition: model.hpp:127
Definition: simulation_machine.hpp:101
void run(SimulationContext &ctx)
Definition: simulation_machine.hpp:132
void run_machine(StateId initial, SimulationContext &ctx)
Definition: simulation_machine.hpp:138
StateId run_state_async(StateId id, SimulationContext &ctx)
Definition: simulation_machine.hpp:191
StateId handle_interrupt(StateId nominal, StateId interrupt, SimulationContext &ctx) override
Definition: simulation_machine.hpp:240
Definition: state_machine.hpp:122
virtual cloe::Logger logger() const
Definition: state_machine.hpp:219
Definition: state_machine.hpp:49
@ Success
Simulation explicitly concluded with success.
Definition: simulation_context.hpp:67
cloe::Stack config
Input configuration.
Definition: simulation_context.hpp:79
bool pause_execution
Definition: simulation_context.hpp:134