cmvr-es/cmvr-es/manager/safety_manager/include/command_ledger.h

131 lines
3.6 KiB
C++

#pragma once
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>
#include "manager/safety_manager/include/safety_types.h"
namespace cmvr::safety {
struct CommandKey {
std::string effective_principal_id;
std::string command_id;
bool operator==(const CommandKey& other) const noexcept
{
return effective_principal_id == other.effective_principal_id &&
command_id == other.command_id;
}
};
struct CommandOutcome {
CommandLifecycle lifecycle{CommandLifecycle::Failed};
SafetyReason reason{SafetyReason::InternalError};
std::string detail;
std::string serialized_response;
std::uint64_t safety_epoch{0};
std::uint64_t device_generation{0};
bool hardware_submission_possible{false};
};
enum class CommandReservationStatus {
AcceptedNew,
JoinedInFlight,
CachedResult,
CommandIdConflict,
ResultEvicted,
LedgerExhausted,
Invalid,
};
class CommandLedger final {
private:
struct State;
public:
struct Config {
std::size_t result_capacity{4096};
std::size_t total_id_capacity{256U * 1024U};
};
class Ticket final {
public:
Ticket() = default;
bool valid() const noexcept { return state_ != nullptr; }
private:
friend class CommandLedger;
explicit Ticket(std::shared_ptr<State> state)
: state_(std::move(state))
{
}
std::shared_ptr<State> state_;
};
struct Reservation {
CommandReservationStatus status{CommandReservationStatus::Invalid};
Ticket ticket;
std::optional<CommandOutcome> cached_outcome;
};
CommandLedger();
explicit CommandLedger(Config config);
Reservation reserve(CommandKey key, std::string payload_hash);
bool setLifecycle(const Ticket& ticket,
CommandLifecycle lifecycle,
std::uint64_t safety_epoch = 0,
std::uint64_t device_generation = 0,
bool hardware_submission_possible = false);
bool complete(const Ticket& ticket, CommandOutcome outcome);
std::optional<CommandOutcome> wait(
const Ticket& ticket,
SafetyClock::time_point deadline = SafetyClock::time_point::max()) const;
std::optional<CommandOutcome> lookup(
const CommandKey& key,
const std::string& payload_hash) const;
std::size_t acceptedIdCount() const;
std::size_t liveRecordCount() const;
std::size_t retiredIdCount() const;
private:
struct KeyHash {
std::size_t operator()(const CommandKey& key) const noexcept;
};
struct State {
CommandKey key;
std::string payload_hash;
mutable std::mutex mutex;
mutable std::condition_variable condition;
CommandLifecycle lifecycle{CommandLifecycle::Reserved};
std::optional<CommandOutcome> outcome;
std::uint64_t safety_epoch{0};
std::uint64_t device_generation{0};
bool hardware_submission_possible{false};
bool terminal{false};
};
static bool validKey_(const CommandKey& key) noexcept;
void trimTerminalResultsLocked_();
const Config config_;
mutable std::mutex mutex_;
std::unordered_map<CommandKey, std::shared_ptr<State>, KeyHash> records_;
std::unordered_map<CommandKey, std::string, KeyHash> retired_ids_;
std::vector<CommandKey> terminal_order_;
std::size_t terminal_result_count_{0};
};
const char* toString(CommandReservationStatus status) noexcept;
} // namespace cmvr::safety