/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ANDROID_SERVICE_UTILS_EVICTION_POLICY_MANAGER_H #define ANDROID_SERVICE_UTILS_EVICTION_POLICY_MANAGER_H #include #include #include #include #include #include #include #include #include namespace android { namespace resource_policy { // -------------------------------------------------------------------------------- /** * The ClientDescriptor class is a container for a given key/value pair identifying a shared * resource, and the corresponding cost, priority, owner ID, and conflicting keys list used * in determining eviction behavior. * * Aside from the priority, these values are immutable once the ClientDescriptor has been * constructed. */ template class ClientDescriptor final { public: ClientDescriptor(const KEY& key, const VALUE& value, int32_t cost, const std::set& conflictingKeys, int32_t priority, int32_t ownerId); ClientDescriptor(KEY&& key, VALUE&& value, int32_t cost, std::set&& conflictingKeys, int32_t priority, int32_t ownerId); ~ClientDescriptor(); /** * Return the key for this descriptor. */ const KEY& getKey() const; /** * Return the value for this descriptor. */ const VALUE& getValue() const; /** * Return the cost for this descriptor. */ int32_t getCost() const; /** * Return the priority for this descriptor. */ int32_t getPriority() const; /** * Return the owner ID for this descriptor. */ int32_t getOwnerId() const; /** * Return true if the given key is in this descriptor's conflicting keys list. */ bool isConflicting(const KEY& key) const; /** * Return the set of all conflicting keys for this descriptor. */ std::set getConflicting() const; /** * Set the proirity for this descriptor. */ void setPriority(int32_t priority); // This class is ordered by key template friend bool operator < (const ClientDescriptor& a, const ClientDescriptor& b); private: KEY mKey; VALUE mValue; int32_t mCost; std::set mConflicting; int32_t mPriority; int32_t mOwnerId; }; // class ClientDescriptor template bool operator < (const ClientDescriptor& a, const ClientDescriptor& b) { return a.mKey < b.mKey; } template ClientDescriptor::ClientDescriptor(const KEY& key, const VALUE& value, int32_t cost, const std::set& conflictingKeys, int32_t priority, int32_t ownerId) : mKey{key}, mValue{value}, mCost{cost}, mConflicting{conflictingKeys}, mPriority{priority}, mOwnerId{ownerId} {} template ClientDescriptor::ClientDescriptor(KEY&& key, VALUE&& value, int32_t cost, std::set&& conflictingKeys, int32_t priority, int32_t ownerId) : mKey{std::forward(key)}, mValue{std::forward(value)}, mCost{cost}, mConflicting{std::forward>(conflictingKeys)}, mPriority{priority}, mOwnerId{ownerId} {} template ClientDescriptor::~ClientDescriptor() {} template const KEY& ClientDescriptor::getKey() const { return mKey; } template const VALUE& ClientDescriptor::getValue() const { return mValue; } template int32_t ClientDescriptor::getCost() const { return mCost; } template int32_t ClientDescriptor::getPriority() const { return mPriority; } template int32_t ClientDescriptor::getOwnerId() const { return mOwnerId; } template bool ClientDescriptor::isConflicting(const KEY& key) const { if (key == mKey) return true; for (const auto& x : mConflicting) { if (key == x) return true; } return false; } template std::set ClientDescriptor::getConflicting() const { return mConflicting; } template void ClientDescriptor::setPriority(int32_t priority) { mPriority = priority; } // -------------------------------------------------------------------------------- /** * A default class implementing the LISTENER interface used by ClientManager. */ template class DefaultEventListener { public: void onClientAdded(const ClientDescriptor& descriptor); void onClientRemoved(const ClientDescriptor& descriptor); }; template void DefaultEventListener::onClientAdded( const ClientDescriptor& /*descriptor*/) {} template void DefaultEventListener::onClientRemoved( const ClientDescriptor& /*descriptor*/) {} // -------------------------------------------------------------------------------- /** * The ClientManager class wraps an LRU-ordered list of active clients and implements eviction * behavior for handling shared resource access. * * When adding a new descriptor, eviction behavior is as follows: * - Keys are unique, adding a descriptor with the same key as an existing descriptor will * result in the lower-priority of the two being removed. Priority ties result in the * LRU descriptor being evicted (this means the incoming descriptor be added in this case). * - Any descriptors with keys that are in the incoming descriptor's 'conflicting keys' list * will be removed if they have an equal or lower priority than the incoming descriptor; * if any have a higher priority, the incoming descriptor is removed instead. * - If the sum of all descriptors' costs, including the incoming descriptor's, is more than * the max cost allowed for this ClientManager, descriptors with non-zero cost, equal or lower * priority, and a different owner will be evicted in LRU order until either the cost is less * than the max cost, or all descriptors meeting this criteria have been evicted and the * incoming descriptor has the highest priority. Otherwise, the incoming descriptor is * removed instead. */ template> class ClientManager { public: // The default maximum "cost" allowed before evicting static constexpr int32_t DEFAULT_MAX_COST = 100; ClientManager(); ClientManager(int32_t totalCost); /** * Add a given ClientDescriptor to the managed list. ClientDescriptors for clients that * are evicted by this action are returned in a vector. * * This may return the ClientDescriptor passed in if it would be evicted. */ std::vector>> addAndEvict( const std::shared_ptr>& client); /** * Given a map containing owner (pid) -> priority mappings, update the priority of each * ClientDescriptor with an owner in this mapping. */ void updatePriorities(const std::map& ownerPriorityList); /** * Remove all ClientDescriptors. */ void removeAll(); /** * Remove and return the ClientDescriptor with a given key. */ std::shared_ptr> remove(const KEY& key); /** * Remove the given ClientDescriptor. */ void remove(const std::shared_ptr>& value); /** * Return a vector of the ClientDescriptors that would be evicted by adding the given * ClientDescriptor. * * This may return the ClientDescriptor passed in if it would be evicted. */ std::vector>> wouldEvict( const std::shared_ptr>& client) const; /** * Return a vector of active ClientDescriptors that prevent this client from being added. */ std::vector>> getIncompatibleClients( const std::shared_ptr>& client) const; /** * Return a vector containing all currently active ClientDescriptors. */ std::vector>> getAll() const; /** * Return a vector containing all keys of currently active ClientDescriptors. */ std::vector getAllKeys() const; /** * Return a vector of the owner tags of all currently active ClientDescriptors (duplicates * will be removed). */ std::vector getAllOwners() const; /** * Return the ClientDescriptor corresponding to the given key, or an empty shared pointer * if none exists. */ std::shared_ptr> get(const KEY& key) const; /** * Block until the given client is no longer in the active clients list, or the timeout * occurred. * * Returns NO_ERROR if this succeeded, -ETIMEDOUT on a timeout, or a negative error code on * failure. */ status_t waitUntilRemoved(const std::shared_ptr> client, nsecs_t timeout) const; /** * Set the current listener for client add/remove events. * * The listener instance must inherit from the LISTENER class and implement the following * methods: * void onClientRemoved(const ClientDescriptor& descriptor); * void onClientAdded(const ClientDescriptor& descriptor); * * These callback methods will be called with the ClientManager's lock held, and should * not call any further ClientManager methods. * * The onClientRemoved method will be called when the client has been removed or evicted * from the ClientManager that this event listener has been added to. The onClientAdded * method will be called when the client has been added to the ClientManager that this * event listener has been added to. */ void setListener(const std::shared_ptr& listener); protected: ~ClientManager(); private: /** * Return a vector of the ClientDescriptors that would be evicted by adding the given * ClientDescriptor. If returnIncompatibleClients is set to true, instead, return the * vector of ClientDescriptors that are higher priority than the incoming client and * either conflict with this client, or contribute to the resource cost if that would * prevent the incoming client from being added. * * This may return the ClientDescriptor passed in. */ std::vector>> wouldEvictLocked( const std::shared_ptr>& client, bool returnIncompatibleClients = false) const; int64_t getCurrentCostLocked() const; mutable Mutex mLock; mutable Condition mRemovedCondition; int32_t mMaxCost; // LRU ordered, most recent at end std::vector>> mClients; std::shared_ptr mListener; }; // class ClientManager template ClientManager::ClientManager() : ClientManager(DEFAULT_MAX_COST) {} template ClientManager::ClientManager(int32_t totalCost) : mMaxCost(totalCost) {} template ClientManager::~ClientManager() {} template std::vector>> ClientManager::wouldEvict( const std::shared_ptr>& client) const { Mutex::Autolock lock(mLock); return wouldEvictLocked(client); } template std::vector>> ClientManager::getIncompatibleClients( const std::shared_ptr>& client) const { Mutex::Autolock lock(mLock); return wouldEvictLocked(client, /*returnIncompatibleClients*/true); } template std::vector>> ClientManager::wouldEvictLocked( const std::shared_ptr>& client, bool returnIncompatibleClients) const { std::vector>> evictList; // Disallow null clients, return input if (client == nullptr) { evictList.push_back(client); return evictList; } const KEY& key = client->getKey(); int32_t cost = client->getCost(); int32_t priority = client->getPriority(); int32_t owner = client->getOwnerId(); int64_t totalCost = getCurrentCostLocked() + cost; // Determine the MRU of the owners tied for having the highest priority int32_t highestPriorityOwner = owner; int32_t highestPriority = priority; for (const auto& i : mClients) { int32_t curPriority = i->getPriority(); if (curPriority >= highestPriority) { highestPriority = curPriority; highestPriorityOwner = i->getOwnerId(); } } if (highestPriority == priority) { // Switch back owner if the incoming client has the highest priority, as it is MRU highestPriorityOwner = owner; } // Build eviction list of clients to remove for (const auto& i : mClients) { const KEY& curKey = i->getKey(); int32_t curCost = i->getCost(); int32_t curPriority = i->getPriority(); int32_t curOwner = i->getOwnerId(); bool conflicting = (curKey == key || i->isConflicting(key) || client->isConflicting(curKey)); if (!returnIncompatibleClients) { // Find evicted clients if (conflicting && curPriority > priority) { // Pre-existing conflicting client with higher priority exists evictList.clear(); evictList.push_back(client); return evictList; } else if (conflicting || ((totalCost > mMaxCost && curCost > 0) && (curPriority <= priority) && !(highestPriorityOwner == owner && owner == curOwner))) { // Add a pre-existing client to the eviction list if: // - We are adding a client with higher priority that conflicts with this one. // - The total cost including the incoming client's is more than the allowable // maximum, and the client has a non-zero cost, lower priority, and a different // owner than the incoming client when the incoming client has the // highest priority. evictList.push_back(i); totalCost -= curCost; } } else { // Find clients preventing the incoming client from being added if (curPriority > priority && (conflicting || (totalCost > mMaxCost && curCost > 0))) { // Pre-existing conflicting client with higher priority exists evictList.push_back(i); } } } // Immediately return the incompatible clients if we are calculating these instead if (returnIncompatibleClients) { return evictList; } // If the total cost is too high, return the input unless the input has the highest priority if (totalCost > mMaxCost && highestPriorityOwner != owner) { evictList.clear(); evictList.push_back(client); return evictList; } return evictList; } template std::vector>> ClientManager::addAndEvict( const std::shared_ptr>& client) { Mutex::Autolock lock(mLock); auto evicted = wouldEvictLocked(client); auto it = evicted.begin(); if (it != evicted.end() && *it == client) { return evicted; } auto iter = evicted.cbegin(); if (iter != evicted.cend()) { if (mListener != nullptr) mListener->onClientRemoved(**iter); // Remove evicted clients from list mClients.erase(std::remove_if(mClients.begin(), mClients.end(), [&iter] (std::shared_ptr>& curClientPtr) { if (curClientPtr->getKey() == (*iter)->getKey()) { iter++; return true; } return false; }), mClients.end()); } if (mListener != nullptr) mListener->onClientAdded(*client); mClients.push_back(client); mRemovedCondition.broadcast(); return evicted; } template std::vector>> ClientManager::getAll() const { Mutex::Autolock lock(mLock); return mClients; } template std::vector ClientManager::getAllKeys() const { Mutex::Autolock lock(mLock); std::vector keys(mClients.size()); for (const auto& i : mClients) { keys.push_back(i->getKey()); } return keys; } template std::vector ClientManager::getAllOwners() const { Mutex::Autolock lock(mLock); std::set owners; for (const auto& i : mClients) { owners.emplace(i->getOwnerId()); } return std::vector(owners.begin(), owners.end()); } template void ClientManager::updatePriorities( const std::map& ownerPriorityList) { Mutex::Autolock lock(mLock); for (auto& i : mClients) { auto j = ownerPriorityList.find(i->getOwnerId()); if (j != ownerPriorityList.end()) { i->setPriority(j->second); } } } template std::shared_ptr> ClientManager::get( const KEY& key) const { Mutex::Autolock lock(mLock); for (const auto& i : mClients) { if (i->getKey() == key) return i; } return std::shared_ptr>(nullptr); } template void ClientManager::removeAll() { Mutex::Autolock lock(mLock); if (mListener != nullptr) { for (const auto& i : mClients) { mListener->onClientRemoved(*i); } } mClients.clear(); mRemovedCondition.broadcast(); } template std::shared_ptr> ClientManager::remove( const KEY& key) { Mutex::Autolock lock(mLock); std::shared_ptr> ret; // Remove evicted clients from list mClients.erase(std::remove_if(mClients.begin(), mClients.end(), [this, &key, &ret] (std::shared_ptr>& curClientPtr) { if (curClientPtr->getKey() == key) { if (mListener != nullptr) mListener->onClientRemoved(*curClientPtr); ret = curClientPtr; return true; } return false; }), mClients.end()); mRemovedCondition.broadcast(); return ret; } template status_t ClientManager::waitUntilRemoved( const std::shared_ptr> client, nsecs_t timeout) const { status_t ret = NO_ERROR; Mutex::Autolock lock(mLock); bool isRemoved = false; // Figure out what time in the future we should hit the timeout nsecs_t failTime = systemTime(SYSTEM_TIME_MONOTONIC) + timeout; while (!isRemoved) { isRemoved = true; for (const auto& i : mClients) { if (i == client) { isRemoved = false; } } if (!isRemoved) { ret = mRemovedCondition.waitRelative(mLock, timeout); if (ret != NO_ERROR) { break; } timeout = failTime - systemTime(SYSTEM_TIME_MONOTONIC); } } return ret; } template void ClientManager::setListener(const std::shared_ptr& listener) { Mutex::Autolock lock(mLock); mListener = listener; } template void ClientManager::remove( const std::shared_ptr>& value) { Mutex::Autolock lock(mLock); // Remove evicted clients from list mClients.erase(std::remove_if(mClients.begin(), mClients.end(), [this, &value] (std::shared_ptr>& curClientPtr) { if (curClientPtr == value) { if (mListener != nullptr) mListener->onClientRemoved(*curClientPtr); return true; } return false; }), mClients.end()); mRemovedCondition.broadcast(); } template int64_t ClientManager::getCurrentCostLocked() const { int64_t totalCost = 0; for (const auto& x : mClients) { totalCost += x->getCost(); } return totalCost; } // -------------------------------------------------------------------------------- }; // namespace resource_policy }; // namespace android #endif // ANDROID_SERVICE_UTILS_EVICTION_POLICY_MANAGER_H