blob: 0ac89659a2c2ffe7207804f733694f21575d2070 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
#pragma allow_unsafe_libc_calls
#endif
#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_VOTING_VOTING_H_
#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_VOTING_VOTING_H_
// Declares the various structures and formats associated with a templated
// voting system. This is templated on a vote type (e.g. a priority) and a vote
// context (e.g. a specific node type).
//
// There are 4 interrelated classes declared here:
//
// (1) Vote - A simple wrapper for a vote. This is a final concrete class.
// (2) VoteObserver - Destination for Votes. This is an interface.
// (3) VotingChannel - A mechanism by which a voter can submit votes to a
// VoteObserver. this is a final concrete class.
// (4) VotingChannelFactory - Producer and tracker of VotingChannels, meant to
// be owned by a VoteObserver, so it can issue VotingChannels associated
// with the VoteObserver. This is a final concrete class.
//
// Voters register themselves with VoteObservers, which issues them a private
// VotingChannel using their VotingChannelFactory. Voters can then use their
// VotingChannel to submit new votes (SubmitVote()), change an existing vote
// (ChangeVote()) or invalidate an existing vote (InvalidateVote()).
//
// All votes submitted through a VotingChannel must be invalidated before the
// channel is destroyed, and all VotingChannels issued by a
// VotingChannelFactory must be destroyed before the factory is destroyed. This
// is all verified via debug checks.
//
// The VoteObserver will receive a notification that is tagged with the ID of
// originating VotingChannel every time a vote is submitted (OnVoteSubmitted()),
// changed (OnVoteChanged()) or invalidated (OnVoteInvalidated()).
//
// None of these objects are thread-safe, and they should all be used from a
// single sequence. In practice this will be the PM sequence.
#include <cstring>
#include <map>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/dcheck_is_on.h"
#include "base/memory/raw_ptr.h"
#include "base/types/id_type.h"
#include "base/types/pass_key.h"
namespace performance_manager::voting {
// Contains a single vote. Specifically allows copying, etc, so as to be STL
// container friendly.
template <typename TContextType, typename TVoteType, TVoteType DefaultVote>
class Vote final {
public:
using ContextType = TContextType;
using VoteType = TVoteType;
Vote();
// NOTE: |reason| *must* be a static string.
Vote(VoteType vote, const char* reason);
Vote(const Vote& rhs);
Vote& operator=(const Vote& rhs);
~Vote();
VoteType value() const { return vote_; }
const char* reason() const { return reason_; }
bool operator==(const Vote& vote) const;
// Returns true if the vote is valid. A valid vote must have a |reason_|.
bool IsValid() const;
private:
VoteType vote_ = DefaultVote;
const char* reason_ = nullptr;
};
// Identifies a VotingChannel.
template <typename VoteImpl>
using VoterId = base::IdTypeU32<VoteImpl>;
template <class VoteImpl>
class VoteObserver {
public:
using ContextType = typename VoteImpl::ContextType;
virtual ~VoteObserver();
// Invoked when a |vote| is submitted for |context|. |voter_id| identifies the
// voting channel.
virtual void OnVoteSubmitted(VoterId<VoteImpl> voter_id,
const ContextType* context,
const VoteImpl& vote) = 0;
// Invoked when the vote for |context| is changed to |new_vote|. |voter_id|
// identifies the voting channel.
virtual void OnVoteChanged(VoterId<VoteImpl> voter_id,
const ContextType* context,
const VoteImpl& new_vote) = 0;
// Invoked when a vote for |context| is invalided. |voter_id| identifies the
// voting channel.
virtual void OnVoteInvalidated(VoterId<VoteImpl> voter_id,
const ContextType* context) = 0;
};
template <class VoteImpl>
class VotingChannelFactory;
// A channel that a voter can use to submit votes to a VoteObserver. A move-only
// type so that it can't be shared by multiple voters. This must be destroyed
// before the issuing VotingChannelFactory.
template <class VoteImpl>
class VotingChannel {
public:
using ContextType = typename VoteImpl::ContextType;
using PassKey = base::PassKey<VotingChannel>;
VotingChannel();
VotingChannel(const VotingChannel& rhs) = delete;
VotingChannel(VotingChannel&& rhs);
VotingChannel& operator=(const VotingChannel& rhs) = delete;
VotingChannel& operator=(VotingChannel&& rhs);
~VotingChannel();
// Submits a vote through this voting channel. Can only be called if this
// VotingChannel is valid.
void SubmitVote(const ContextType* context, const VoteImpl& vote);
// Modifies an existing vote. Can only be called if this VotingChannel is
// valid.
void ChangeVote(const ContextType* context, const VoteImpl& new_vote);
// Invalidates an existing vote. Can only be called if this VotingChannel is
// valid.
void InvalidateVote(const ContextType* context);
// Returns true if this VotingChannel is valid.
bool IsValid() const;
// Resets this voting channel.
void Reset();
VoterId<VoteImpl> voter_id() const { return voter_id_; }
// VotingChannelFactory is the sole producer of VotingChannels.
VotingChannel(base::PassKey<VotingChannelFactory<VoteImpl>>,
VotingChannelFactory<VoteImpl>* factory,
VoterId<VoteImpl> voter_id);
private:
void Take(VotingChannel&& rhs);
// Used to reach back into the factory to decrement the outstanding
// VotingChannel count, and for routing votes to the consumer.
raw_ptr<VotingChannelFactory<VoteImpl>> factory_ = nullptr;
VoterId<VoteImpl> voter_id_;
#if DCHECK_IS_ON()
base::flat_map<const ContextType*, VoteImpl> votes_;
#endif // DCHECK_IS_ON()
};
// A helper for creating VotingChannels that binds a unique VoterId (and
// passes the votes along to the VoteObserver with that VoterId), and a tracking
// token to ensure that the voter disconnects from the VoteObserver before it is
// itself destroyed. Implementations of VoteObservers should own an instance of
// this and use it to emit VotingChannels. This class will DCHECK in its
// destructor if there are outstanding VotingChannels at its death.
template <class VoteImpl>
class VotingChannelFactory final {
public:
using PassKey = base::PassKey<VotingChannelFactory>;
explicit VotingChannelFactory(VoteObserver<VoteImpl>* observer);
~VotingChannelFactory();
VotingChannelFactory(const VotingChannelFactory& rhs) = delete;
VotingChannelFactory& operator=(const VotingChannelFactory& rhs) = delete;
// Builds a new VotingChannel that routes votes to the |observer_|.
VotingChannel<VoteImpl> BuildVotingChannel();
size_t voting_channels_issued() const { return voting_channels_issued_; }
size_t voting_channels_outstanding() const {
return voting_channels_outstanding_;
}
// Used by ~VotingChannel to notify the factory that a channel has been
// torn down.
void OnVotingChannelDestroyed(base::PassKey<VotingChannel<VoteImpl>>);
VoteObserver<VoteImpl>* GetObserver(base::PassKey<VotingChannel<VoteImpl>>) {
return observer_;
}
private:
// The consumer that owns this factory.
raw_ptr<VoteObserver<VoteImpl>> observer_ = nullptr;
// The number of voting channels issued, and the number that remain
// outstanding.
size_t voting_channels_issued_ = 0u;
size_t voting_channels_outstanding_ = 0u;
};
/////////////////////////////////////////////////////////////////////
// Vote
template <typename ContextType, typename VoteType, VoteType DefaultVote>
Vote<ContextType, VoteType, DefaultVote>::Vote() = default;
template <typename ContextType, typename VoteType, VoteType DefaultVote>
Vote<ContextType, VoteType, DefaultVote>::Vote(VoteType vote,
const char* reason)
: vote_(std::move(vote)), reason_(reason) {}
template <typename ContextType, typename VoteType, VoteType DefaultVote>
Vote<ContextType, VoteType, DefaultVote>::Vote(const Vote& rhs) = default;
template <typename ContextType, typename VoteType, VoteType DefaultVote>
Vote<ContextType, VoteType, DefaultVote>&
Vote<ContextType, VoteType, DefaultVote>::operator=(
const Vote<ContextType, VoteType, DefaultVote>& rhs) = default;
template <typename ContextType, typename VoteType, VoteType DefaultVote>
Vote<ContextType, VoteType, DefaultVote>::~Vote() = default;
template <typename ContextType, typename VoteType, VoteType DefaultVote>
bool Vote<ContextType, VoteType, DefaultVote>::operator==(
const Vote<ContextType, VoteType, DefaultVote>& vote) const {
DCHECK(reason_);
DCHECK(vote.reason_);
return vote_ == vote.vote_ && ::strcmp(reason_, vote.reason_) == 0;
}
template <typename ContextType, typename VoteType, VoteType DefaultVote>
bool Vote<ContextType, VoteType, DefaultVote>::IsValid() const {
return reason_;
}
/////////////////////////////////////////////////////////////////////
// VoteObserver
template <class VoteImpl>
VoteObserver<VoteImpl>::~VoteObserver() = default;
/////////////////////////////////////////////////////////////////////
// VotingChannel
template <class VoteImpl>
VotingChannel<VoteImpl>::VotingChannel() = default;
template <class VoteImpl>
VotingChannel<VoteImpl>::VotingChannel(VotingChannel<VoteImpl>&& rhs) {
Take(std::move(rhs));
}
template <class VoteImpl>
VotingChannel<VoteImpl>& VotingChannel<VoteImpl>::operator=(
VotingChannel<VoteImpl>&& rhs) {
Take(std::move(rhs));
return *this;
}
template <class VoteImpl>
VotingChannel<VoteImpl>::~VotingChannel() {
Reset();
}
template <class VoteImpl>
void VotingChannel<VoteImpl>::SubmitVote(const ContextType* context,
const VoteImpl& vote) {
DCHECK(IsValid());
#if DCHECK_IS_ON()
// Ensure that only one vote is submitted for a given |context| at any time.
bool inserted = votes_.emplace(context, vote).second;
DCHECK(inserted);
#endif // DCHECK_IS_ON()
factory_->GetObserver(PassKey())->OnVoteSubmitted(voter_id_, context, vote);
}
template <class VoteImpl>
void VotingChannel<VoteImpl>::ChangeVote(const ContextType* context,
const VoteImpl& new_vote) {
DCHECK(IsValid());
#if DCHECK_IS_ON()
// Ensure that a vote exists for this context.
auto it = votes_.find(context);
CHECK(it != votes_.end());
// Ensure the vote was actually changed.
DCHECK(new_vote != it->second);
it->second = new_vote;
#endif // DCHECK_IS_ON()
factory_->GetObserver(PassKey())->OnVoteChanged(voter_id_, context, new_vote);
}
template <class VoteImpl>
void VotingChannel<VoteImpl>::InvalidateVote(const ContextType* context) {
DCHECK(IsValid());
#if DCHECK_IS_ON()
// Ensure that an existing vote is invalidated.
size_t removed = votes_.erase(context);
DCHECK_EQ(removed, 1u);
#endif // DCHECK_IS_ON()
factory_->GetObserver(PassKey())->OnVoteInvalidated(voter_id_, context);
}
template <class VoteImpl>
bool VotingChannel<VoteImpl>::IsValid() const {
return factory_ && voter_id_;
}
template <class VoteImpl>
void VotingChannel<VoteImpl>::Reset() {
if (!factory_)
return;
DCHECK(voter_id_);
factory_->OnVotingChannelDestroyed(PassKey());
factory_ = nullptr;
voter_id_ = VoterId<VoteImpl>();
#if DCHECK_IS_ON()
// Ensure that all outstanding votes are invalidated before resetting this
// channel.
DCHECK(votes_.empty());
#endif // DCHECK_IS_ON()
}
template <class VoteImpl>
VotingChannel<VoteImpl>::VotingChannel(
base::PassKey<VotingChannelFactory<VoteImpl>>,
VotingChannelFactory<VoteImpl>* factory,
VoterId<VoteImpl> voter_id)
: factory_(factory), voter_id_(voter_id) {}
template <class VoteImpl>
void VotingChannel<VoteImpl>::Take(VotingChannel<VoteImpl>&& rhs) {
Reset();
factory_ = std::exchange(rhs.factory_, nullptr);
voter_id_ = std::exchange(rhs.voter_id_, VoterId<VoteImpl>());
#if DCHECK_IS_ON()
// Track outstanding votes across moves.
votes_ =
std::exchange(rhs.votes_, base::flat_map<const ContextType*, VoteImpl>());
#endif // DCHECK_IS_ON()
}
/////////////////////////////////////////////////////////////////////
// VotingChannelFactory
template <class VoteImpl>
VotingChannelFactory<VoteImpl>::VotingChannelFactory(
VoteObserver<VoteImpl>* observer)
: observer_(observer) {
DCHECK(observer_);
}
template <class VoteImpl>
VotingChannelFactory<VoteImpl>::~VotingChannelFactory() {
// We expect all voters to have severed their VotingChannels before we are
// torn down.
DCHECK_EQ(0u, voting_channels_outstanding_);
}
template <class VoteImpl>
VotingChannel<VoteImpl> VotingChannelFactory<VoteImpl>::BuildVotingChannel() {
++voting_channels_outstanding_;
// TODO(sebmarchand): Use VoterId<VoteImpl>::Generator instead of
// FromUnsafeValue.
// Note: The pre-increment operator is used so that the value of the first
// voter ID is 1. This is required because 0 is the value for an invalid
// VoterId.
VoterId<VoteImpl> new_voter_id =
VoterId<VoteImpl>::FromUnsafeValue(++voting_channels_issued_);
DCHECK(!new_voter_id.is_null());
return VotingChannel<VoteImpl>(PassKey(), this, new_voter_id);
}
template <class VoteImpl>
void VotingChannelFactory<VoteImpl>::OnVotingChannelDestroyed(
base::PassKey<VotingChannel<VoteImpl>>) {
DCHECK_LT(0u, voting_channels_outstanding_);
--voting_channels_outstanding_;
}
} // namespace performance_manager::voting
#endif // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_VOTING_VOTING_H_