//------------------------------------------------------------------------------ //! @file RateLimit.cc //! @author Elvin Sindrilaru - CERN //------------------------------------------------------------------------------ /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2019 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "common/RateLimit.hh" #include #include #include using namespace std::chrono; EOSCOMMONNAMESPACE_BEGIN //---------------------------------------------------------------------------- //! Get delay for the current request //! //! @return delay in microseconds for the current requestor //---------------------------------------------------------------------------- microseconds RequestRateLimit::GetDelay() { // If no rate limit enforced then no delay if (mRate == 0ull) { return microseconds(0); } std::lock_guard lock(mMutex); // Compute expire timestamp which is one second back in time uint64_t now_us = duration_cast (mClock.getTime().time_since_epoch()).count(); // Round to the beginning of the interval now_us = (now_us / mRateIntervalUs) * mRateIntervalUs; uint64_t expire_us = now_us - 1000000; // Try to expire old entries if any present if (!mSetReqTimestamps.empty()) { auto it = mSetReqTimestamps.lower_bound(expire_us); if (it != mSetReqTimestamps.end()) { // If current entry is after the expire timestamp we have to go one // back unless this is the beginning if ((it != mSetReqTimestamps.begin()) && (*it > expire_us)) { --it; } } if (it == mSetReqTimestamps.begin()) { if (*it <= expire_us) { (void) mSetReqTimestamps.erase(it); } } else { // Delete all the entries before this iterator i.e. expired ones for (auto it_del = mSetReqTimestamps.begin(); it_del != it; /*no inc*/) { it_del = mSetReqTimestamps.erase(it_del); } } } uint64_t new_entry_us {now_us}; uint64_t num_requests = mSetReqTimestamps.size(); bool has_delay = false; if (num_requests >= mRate) { has_delay = true; // We need to wait for the first entry in the set to expire new_entry_us = *mSetReqTimestamps.begin() + 1000000; auto it = mSetReqTimestamps.lower_bound(new_entry_us); // If that slot is taken already then move on to the next one while (it != mSetReqTimestamps.end()) { new_entry_us += mRateIntervalUs; it = mSetReqTimestamps.lower_bound(new_entry_us); } } else { // If the current slot is taken then try the next one while (mSetReqTimestamps.find(new_entry_us) != mSetReqTimestamps.end()) { new_entry_us += mRateIntervalUs; } } mSetReqTimestamps.insert(new_entry_us); uint64_t delay {0ull}; if (has_delay) { delay = new_entry_us - now_us; } // This is for testing purposes only if (mLastTimestampUs < new_entry_us) { mLastTimestampUs = new_entry_us; } return microseconds(delay); } EOSCOMMONNAMESPACE_END