/* * Copyright (C) 2018 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 NETUTILS_OPERATIONLIMITER_H #define NETUTILS_OPERATIONLIMITER_H #include #include #include #include namespace android { namespace netdutils { // Tracks the number of operations in progress on behalf of a particular key or // ID, rejecting further attempts to start new operations after a configurable // limit has been reached. // // The intended usage pattern is: // OperationLimiter connections_per_user; // ... // int connectToSomeResource(int user) { // if (!connections_per_user.start(user)) return TRY_AGAIN_LATER; // // ...do expensive work here... // connections_per_user.finish(user); // } // // This class is thread-safe. template class OperationLimiter { public: OperationLimiter(int limitPerKey, int globalLimit = INT_MAX) : mLimitPerKey(limitPerKey), mGlobalLimit(globalLimit) {} ~OperationLimiter() { DCHECK(mCounters.empty()) << "Destroying OperationLimiter with active operations"; } // Returns false if |key| has reached the maximum number of concurrent operations, // or if the global limit has been reached. Otherwise, increments the counter and returns true. // // Note: each successful start(key) must be matched by exactly one call to // finish(key). bool start(KeyType key) EXCLUDES(mMutex) { std::lock_guard lock(mMutex); if (mGlobalCounter >= mGlobalLimit) { // Oh, no! return false; } auto& cnt = mCounters[key]; // operator[] creates new entries as needed. if (cnt >= mLimitPerKey) { // Oh, no! return false; } ++cnt; ++mGlobalCounter; return true; } // Decrements the number of operations in progress accounted to |key|. // See usage notes on start(). void finish(KeyType key) EXCLUDES(mMutex) { std::lock_guard lock(mMutex); --mGlobalCounter; if (mGlobalCounter < 0) { LOG(FATAL_WITHOUT_ABORT) << "Global operations counter going negative, this is a bug."; return; } auto it = mCounters.find(key); if (it == mCounters.end()) { LOG(FATAL_WITHOUT_ABORT) << "Decremented non-existent counter for key=" << key; return; } auto& cnt = it->second; --cnt; if (cnt <= 0) { // Cleanup counters once they drop down to zero. mCounters.erase(it); } } private: // Protects access to the map below. std::mutex mMutex; // Tracks the number of outstanding queries by key. std::unordered_map mCounters GUARDED_BY(mMutex); int mGlobalCounter GUARDED_BY(mMutex) = 0; // Maximum number of outstanding queries from a single key. const int mLimitPerKey; // Maximum number of outstanding queries, globally. const int mGlobalLimit; }; } // namespace netdutils } // namespace android #endif // NETUTILS_OPERATIONLIMITER_H