Add GetLatestSlot and GetLatestSample functionality#208
Add GetLatestSlot and GetLatestSample functionality#208muhseth wants to merge 29 commits intoeclipse-score:mainfrom
Conversation
|
@muhseth please fix conflicts. |
| } | ||
|
|
||
| template <template <class> class AtomicIndirectorType> | ||
| score::cpp::optional<SlotIndexType> EventDataControlCompositeImpl<AtomicIndirectorType>::FindLatestReadableSlotIndex( |
There was a problem hiding this comment.
You should instead change existing EventDataControlCompositeImpl::GetLatestTimestamp()! You are mostly duplicating code here. The new/refactored function should be named GetLatestSlot and it should return the index PLUS the timestamp!
And please take into account, what I have written here: Ticket-253464
There was a problem hiding this comment.
Done.
I have removed GetLatestTimeStamp function and corresponding upper layer code.
As I see it is not used any where.
| slot_index < static_cast<SlotIndexType>(control.state_slots_.size()); | ||
| ++slot_index) | ||
| { | ||
| SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(static_cast<std::size_t>(slot_index) < control.state_slots_.size()); |
There was a problem hiding this comment.
? Why this assert? I do not get it.
There was a problem hiding this comment.
I have removed it. As I see it there is no needed of it.
I have kept it as it was the part of previous code.
c4ffb3a to
f0c0b32
Compare
SampleAllocateePtr changed because the mock binding variant has been changed to also carry a custom deleter. Reflect this change by reusing the infrastructure of SamplePtr, whose mock variant uses the same pattern. Original author: darkwisebear
| CheckForValidDataControls(); | ||
| } | ||
|
|
||
| template <template <class> class AtomicIndirectorType> |
There was a problem hiding this comment.
You have to be aware, that you have to align your changes with Brenbdans massive change here: #268
I.e. imho it makes sense, that you base your work on Brendans PR!
| } | ||
|
|
||
| template <template <class> class AtomicIndirectorType> | ||
| score::cpp::optional<SlotIndexType> EventDataControlComposite<AtomicIndirectorType>::FindLatestReadableSlotIndex( |
There was a problem hiding this comment.
What does "readable" mean?
Just: FindNewestSlot ?
There was a problem hiding this comment.
I think what Wahaj means is "FindLatestReferencedSlotIndex". I think it makes sense because just "newest" could also mean newest available slot to allocate in which isn't what we want.
| auto EventSlotStatus::IsInvalid() const noexcept -> bool | ||
| { | ||
| return data_ == INVALID_EVENT; | ||
| return data_ == static_cast<value_type>(InvalidTimestamp); |
There was a problem hiding this comment.
This cast looks "odd" to me!
Our EventSlotStatus::value_type is an uint64 ... the timestamp is a uint32!
An invalid event-slot status technically means:
The timestamp part is 0 (upper32 bit)
the ref-count part is 0 (lower 32 bit)
Just casting the timestamp part does miss this a bit ... if you want to be more "expressive" you could write something like:
constexpr EventSlotStatus::value_type INVALID_EVENT = (InvalidTimestamp <<32) | ZeroRefCount)
but this is "overdone" -> you could simply document INVALID_EVENT = 0u means an invalid timestamp and zero refcount ...
There was a problem hiding this comment.
i have made the changes as suggested.
| auto EventSlotStatus::MarkInvalid() noexcept -> void | ||
| { | ||
| data_ = INVALID_EVENT; | ||
| data_ = static_cast<value_type>(InvalidTimestamp); |
| static constexpr EventTimeStamp TIMESTAMP_MAX = std::numeric_limits<EventTimeStamp>::max(); | ||
|
|
||
| /// \brief Timestamp value indicating that a slot is invalid / was never written. | ||
| static constexpr EventTimeStamp InvalidTimestamp{0U}; |
There was a problem hiding this comment.
I'm fine with introducing these constexpr ... but be symmetric in naming? constexpr TIMESTAMP_MAX is capital letters ... so please no mixup
There was a problem hiding this comment.
Does invalid really make sense here? Wouldn't something like UNINITIALIZED_TIMESTAMP make more sense?
There was a problem hiding this comment.
Shouldn't this be private? If a user wants an uninitialized slot, shouldn't they simply default construct the EventSlotStatus?
| std::optional<EventDataControlComposite<>>& | ||
| event_data_control_composite_ref_; // Reference to the optional in derived class | ||
| EventSlotStatus::EventTimeStamp& current_timestamp_ref_; // Reference to the timestamp in derived class | ||
| event_data_control_composite_ref_; // Reference to the optional in derived class |
There was a problem hiding this comment.
This identation looks weird? .. and please fix the comment! lola::SkeletonEvent is NOT derived from SkeletonEventCommon! It aggregates it....
-> // Reference to the optional in the enclosing/aggregator class
There was a problem hiding this comment.
Also, nitpick, but this isn't really a reference to an optional but rather an optional that is either empty or contains a reference? IMO, we can simply use a raw pointer here to avoid having to have std::optional<std::reference_wrapper>.
| } | ||
|
|
||
| template <typename SampleType> | ||
| Result<SampleType> SkeletonEvent<SampleType>::GetLatestSample() noexcept |
There was a problem hiding this comment.
Ouch. Why are you returning the sample by value here??? We never do this. A sample can be several megabytes in size ... so why are you copying it here - all our zero-copy architecture goes through the roof then ... and applications may crash, because the value will end up on stack, which will overflow ...
I guess you will use/need this functionality, when implementing the Skeleton-side get() method? Or even/also the set-method, where you need to work on the latest existing sample ...?
But then you need to apply SamplePtr logic!
I.e. you refcount the latest sample ... and create a SamplPtr from it, which you return from here ...
At your caller-side (your get/set impl) you then copy the data from the SamplePtr to your method ReturnType/InArg and once you are done with the copying, you destroy the SamplPtr freeing the slot.
This way you have just ONE copy! With your approach taken here, you copy TWICE! You copy out from shm to your sampl variable and then you copy from that variable back intro the call-queue! And this already expects, that you have RVO (which I guess is mandatory right now) ... but again - your caller side will crash, because the returned value (with RVO) will land most likely on stack and might explode there (if it is large).
| { | ||
| const auto sample = event_data_storage_->at(static_cast<std::uint64_t>(slot.GetIndex())); | ||
|
|
||
| if (slot.IsValidQM()) |
There was a problem hiding this comment.
OK - so here you are doing "manually", what should be the job of the SamplePtr dtor
| static constexpr EventTimeStamp TIMESTAMP_MAX = std::numeric_limits<EventTimeStamp>::max(); | ||
|
|
||
| /// \brief Timestamp value indicating that a slot is invalid / was never written. | ||
| static constexpr EventTimeStamp InvalidTimestamp{0U}; |
There was a problem hiding this comment.
Does invalid really make sense here? Wouldn't something like UNINITIALIZED_TIMESTAMP make more sense?
| /// the control block. It provides meta-information for an event. This class acts as an easy access towards this | ||
| /// meta-information. | ||
| /// | ||
| /// \details This data structure needs to fit into std::atomic. Thus it's size shall not exceed the machine word size. |
There was a problem hiding this comment.
I know it's not part of this change, but can you add a static assert that sizeof(EventSlotStatus) <= sizeof(std::uint64_t)?
| static constexpr EventTimeStamp FirstValidTimestamp{1U}; | ||
|
|
||
| /// \brief If default constructed, SlotStatus is invalid | ||
| EventSlotStatus() noexcept = default; |
There was a problem hiding this comment.
Should we dispatch to the other constructor with:
EventSlotStatus() noexcept : EventSlotStatus(UNINITIALIZED_TIMESTAMP) {}
It's more explicit and we can then remove the comment.
| static constexpr EventTimeStamp TIMESTAMP_MAX = std::numeric_limits<EventTimeStamp>::max(); | ||
|
|
||
| /// \brief Timestamp value indicating that a slot is invalid / was never written. | ||
| static constexpr EventTimeStamp InvalidTimestamp{0U}; |
There was a problem hiding this comment.
Shouldn't this be private? If a user wants an uninitialized slot, shouldn't they simply default construct the EventSlotStatus?
| static constexpr EventTimeStamp InvalidTimestamp{0U}; | ||
|
|
||
| /// \brief First valid timestamp value that can appear in shared memory. | ||
| static constexpr EventTimeStamp FirstValidTimestamp{1U}; |
There was a problem hiding this comment.
Where is this actually used?
| const auto new_refcount = static_cast<EventSlotStatus::SubscriberCount>(slot_old.GetReferenceCount() + 1U); | ||
| EventSlotStatus slot_new{slot_old.GetTimeStamp(), new_refcount}; | ||
|
|
||
| auto slot_old_value = static_cast<EventSlotStatus::value_type&>(slot_old); |
There was a problem hiding this comment.
Do you really want to cast to a reference? If yes, they should both be auto&. But I think that making a copy would probably be more performant than a reference anyway?
| EventSlotStatus slot_new{slot_old.GetTimeStamp(), new_refcount}; | ||
|
|
||
| auto slot_old_value = static_cast<EventSlotStatus::value_type&>(slot_old); | ||
| auto slot_new_value = static_cast<EventSlotStatus::value_type&>(slot_new); |
There was a problem hiding this comment.
const auto slot_new_value
| auto& slot_qm = asil_qm_control_->state_slots_[slot_index]; | ||
| auto& slot_asil_b = asil_b_control_->state_slots_[slot_index]; | ||
|
|
||
| if (!TryIncreaseReferenceCount(slot_qm)) |
There was a problem hiding this comment.
Why is this within the if (asil_b_control_ != nullptr)? Why not just do this first and then do the asil b one if needed? That way we don't need the duplicate call to if (!TryIncreaseReferenceCount(slot_qm)) below.
| } | ||
|
|
||
| template <template <class> class AtomicIndirectorType> | ||
| bool EventDataControlComposite<AtomicIndirectorType>::TryIncreaseReferenceCount(ControlSlotType& slot) const noexcept |
There was a problem hiding this comment.
This function is missing recording transactions in the TransactionLog. We currently already have EventDataControl::ReferenceSpecificEvent? This function assumes that the slot stays in reading for the duration of the function (to be enforced by the caller). We can't make the same guarantee here because while this function is being called, the current slot could be overwritten be the provider. What about adding EventDataControl::TryReferenceSpecificEvent?
| template <typename SampleType> | ||
| Result<SampleType> SkeletonEvent<SampleType>::GetLatestSample() noexcept | ||
| { | ||
| if (event_data_control_composite_.has_value() == false) |
There was a problem hiding this comment.
(!event_data_control_composite_.has_value())
This commit splits up ServiceDataControl, EventDataControl and EventControl into a data class (ServiceDataControl, EventDataControl and EventControl) which contains the data that is placed in shared memory and behaviour "view" classes (ServiceDataControlLocalView, EventDataControlLocalView and EventControlLocalView) which contain behaviour for interacting with the data via process local raw pointers which avoid performance overhead of interacting with data directly in shared memory (due to performance overhead of dealing with OffsetPtrs).
Since we always create a QM EventDataControl, we pass it into the composite by reference instead of by pointer to avoid doing nullptr checks within the composite (and for clearer semantics).
We previously introduced these classes to avoid additional bounds checking done when indexing into a DynamicArray in shared memory. Since we now are using the local views into shared memory, we no longer need this optimisation and can revert to simply accessing slots using their indices.
Since the static_cast can only return a nullptr if the usable base address is a nullptr, we assert that the base address is not a nullptr as this is better reflecting the intention of the assertion.
ReferenceSpecificEvent has two assertions and corresponding tests for these assertions which are very similar. Additional documentation is added to the assertions and the tests are refactored to make the distinction clearer.
These Impl classes were previously added to avoid having to change calling code to explicitly use the default template argument (i.e. EventSubscriptionControl<>). Using the explicit template argument makes the code more explicit and also removes the additional code complexity of having the alias and Impl class.
We remove noexcept from all signatures to allow us to use precondition violation tests instead of death tests. Precondition tests are orders of magnitude faster than death tests due to not having to fork a process and also don't have issues associated with RAII objects being destroyed twice (i.e. in forked process and main process).
We now return a struct instead of a pair. This makes it easier to add additional return types in future (we will soon return a reference to a TransactionLogSet as well) and also makes it clearer on caller side as they can access members using the names defined in the struct rather than .first / .second. We also return a reference to EventDataStorage instead of a pointer, since it should always be created.
Updated documentation of functions and general clean up of functions.
We use ScopeExit so that we can rely only the more complicated testing of move construction / move assignment from the ScopeExit class.
This change will be needed in an upcoming PR in which TransactionLogSet::Register() creates a TransactionLogRegistrationGuard. Both of thes files will need TransactionLogIndex so to avoid cyclic dependencies we extract TransactionLogIndex into a separate file.
Previously, we were returning an index and then manually creating a guard from the index. We now return the guard directly. This is important because in an upcoming PR, the guard will be responsible for unregistering the TransactionLog with the TransactionLogSet and also destroying the cached TransactionLogLocalView in the Proxy/SkeletonEventDataControlView. Therefore, we want to make the guard responsible for all cleanup.
This commit implements multiple related changes. To fully split these changes into atomic commits would require a huge amount of work in temporary refactoring in production code and updating a large number of unit tests in each commit. Much of these changes would be temporary and simply overwritten in the next commit. Therefore, we combine the changes into a single commit. * Moves TransactionLogSet from EventDataControl to EventControl TransactionLogSet is currently stored in EventDataControl. The ownership semantics here don't make sense: the EventDataControl is responsible for managing slot reference counting and part of this is to record transactions in the TransactionLogSet. However, we also need to record subscription transactions which occurs outside of EventDataControl. Therefore, we move TransactionLogSet into EventControl to make it clearer that it's not owned by EventDataControl and to avoid having to have getters in ProxyEventDataControlLocalView / SkeletonEventDataControlLocalView for the TransactionLogSet which makes no sense semantically. * Cache TransactionLogViews in ProxyEventDataControlLocalView ProxyEventDataControlLocalView is responsible for doing the reference counting in EventDataControl in shared memory. These operations must be recorded in a TransactionLog so that these operations can be undone when restarting in case the process containing a Proxy crashes. This TransactionLogLocalView is now cached inside the ProxyEventDataControlLocalView on ProxyEvent subscription (and removed on unsubscription) to avoid having to look up the view in the TransactionLogSet every time it's required. * TransactionLogRegistrationGuard now also injects the TransactionLogLocalView in ProxyEventDataControlLocalView on construction and clears it on destruction The TransactionLogRegistrationGuard manages the lifetime of a registered TransactionLog. As long as it's alive (it's alive as long as a ProxyEvent is subscribed), we want the cached TransactionLogLocalView to be stored in ProxyEventDataControlLocalView. Therefore, the TransactionLogRegistrationGuard is now also responsible for injecting / clearing this TransactionLogLocalView. * Since ProxyEventDataControlLocalView now contains a cached TransactionLogLocalView, we no longer need to pass the TransactionLogIndex around to lookup TransactionLogs in TransactionLogSet Getters for TransactionLogIndex are removed from ProxyEventCommon / SubscriptionStateMachine. Classes such as SlotCollector, SlotDecrementer etc. no longer receive a TransactionLogIndex as argument.
Dereferencing is something inherent to a consumer. Therefore, it should only be called by a ProxyEventDataControlLocalView. When a Skeleton creates or opens a shared memory region, it will only create a ProxyEventDataControlLocalView if tracing is enabled in the current process. However, when opening a shared memory region from a crashed process, it's possible that tracing was enabled in that process but currently disabled. Therefore, we may not have created a ProxyEventDataControlLocalView even though we need one to rollback old transactions. Therefore, the Skeleton creates a new ProxyEventDataControlLocalView just to perform the rollback.
The two local views currently are split based on behaviour. ProxyEventDataControlLocalView is responsible for all "consumer" behaviour e.g. referencing / dereferencing a slot and SkeletonEventDataControlLocalView is responsible for all "provider" behaviour e.g. allocating slots. Due to IPC tracing, we have a unique scenario in which a SkeletonEvent acts like a consumer (i.e. it creates a SamplePtr which it uses to keep a slot alive while the tracing daemon is using it). In this case, it needs to use the "consumer" behaviour. Since we can have consumer behaviour also on skeleton side, it doesn't make sense to call the class ProxyEventDataControlLocalView. Therefore we rename them to have Consumer / Provider prefixes.
- introduce InvalidTimestamp/FirstValidTimestamp constants - adapt timestamp tests
- moves common attributes to skeleton event common.
f0c0b32 to
a65dc2c
Compare
- Adds GetLatestSlot function to EventDataControlComposite
- Adds GetLatestSampleFunction to SkeletonEvent
a65dc2c to
636762e
Compare
| ~SkeletonEventCommon() = default; | ||
|
|
||
| void PrepareOfferCommon(TransactionLogSet& transaction_log_set) noexcept; | ||
| void PrepareOfferCommon(TransactionLogSet& transaction_log_set, EventDataControlComposite<>& event_data_control_composite_ref) noexcept; |
There was a problem hiding this comment.
event_data_control_composite_ref should be passed by value?
| std::optional<EventDataControlComposite<>>& | ||
| event_data_control_composite_ref_; // Reference to the optional in derived class | ||
| EventSlotStatus::EventTimeStamp& current_timestamp_ref_; // Reference to the timestamp in derived class | ||
| std::optional<EventDataControlComposite<>> event_data_control_composite_; |
There was a problem hiding this comment.
-
EventDataControlComposite should not contain a conumer event data control. (maybe don't to this now).
-
we should store here a qm and asil b consumer event data control local view.
- qm is used for tracing and get (if the calling event is qm)
- asil b is used for get (if the calling event is asil b)
- TransactionLogs are registered by SkeletonEventCommon (qm or asil b consumereventdatacontrollocalviews are provided to register calls).
-
Move consumereventdatacontrollocalviews out of EventDataControlComposite. To be owned by SkeletonEventCommon and passed to SampleAllocateePtr directly.
-
Change Skeleton::Register to return EventDataControl& qm, EventDataControl* asil-b. Event now creates composite and ConsumerEventDataControlViews (which need EventDataControl).
No description provided.