Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e250e133 authored by Maciej Żenczykowski's avatar Maciej Żenczykowski Committed by Gerrit Code Review
Browse files

Merge "Move OperationLimiter to DnsResolver"

parents 3334a5e0 ac6c9a40
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -259,6 +259,7 @@ cc_test {
        "DnsQueryLogTest.cpp",
        "DnsStatsTest.cpp",
        "ExperimentsTest.cpp",
        "OperationLimiterTest.cpp",
        "PrivateDnsConfigurationTest.cpp",
    ],
    shared_libs: [
+1 −1
Original line number Diff line number Diff line
@@ -39,7 +39,6 @@
#include <cutils/misc.h>           // FIRST_APPLICATION_UID
#include <cutils/multiuser.h>
#include <netdutils/InternetAddresses.h>
#include <netdutils/OperationLimiter.h>
#include <netdutils/ResponseCode.h>
#include <netdutils/Slice.h>
#include <netdutils/Stopwatch.h>
@@ -50,6 +49,7 @@

#include "DnsResolver.h"
#include "NetdPermissions.h"
#include "OperationLimiter.h"
#include "PrivateDnsConfiguration.h"
#include "ResolverEventReporter.h"
#include "dnsproxyd_protocol/DnsProxydProtocol.h"  // NETID_USE_LOCAL_NAMESERVERS

OperationLimiter.h

0 → 100644
+103 −0
Original line number Diff line number Diff line
/*
 * 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 <mutex>
#include <unordered_map>

#include <android-base/logging.h>
#include <android-base/thread_annotations.h>

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<UserId> connections_per_user;
//     ...
//     // Before opening a new connection
//     if (!limiter.start(user)) {
//         return error;
//     } else {
//         // open the connection
//         // ...do some work...
//         // close the connection
//         limiter.finish(user);
//     }
//
// This class is thread-safe.
template <typename KeyType>
class OperationLimiter {
  public:
    explicit OperationLimiter(int limit) : mLimitPerKey(limit) {}

    ~OperationLimiter() {
        DCHECK(mCounters.empty()) << "Destroying OperationLimiter with active operations";
    }

    // Returns false if |key| has reached the maximum number of concurrent
    // operations, 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);
        auto& cnt = mCounters[key];  // operator[] creates new entries as needed.
        if (cnt >= mLimitPerKey) {
            // Oh, no!
            return false;
        }
        ++cnt;
        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);
        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<KeyType, int> mCounters GUARDED_BY(mMutex);

    // Maximum number of outstanding queries from a single key.
    const int mLimitPerKey;
};

}  // namespace netdutils
}  // namespace android

#endif  // NETUTILS_OPERATIONLIMITER_H
+75 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#include "OperationLimiter.h"

#include <gtest/gtest-spi.h>

namespace android {
namespace netdutils {

TEST(OperationLimiter, limits) {
    OperationLimiter<int> limiter(3);

    EXPECT_TRUE(limiter.start(42));
    EXPECT_TRUE(limiter.start(42));
    EXPECT_TRUE(limiter.start(42));

    // Limit reached... calling any number of times should have no effect.
    EXPECT_FALSE(limiter.start(42));
    EXPECT_FALSE(limiter.start(42));
    EXPECT_FALSE(limiter.start(42));

    // Finishing a single operations is enough for starting a new one...
    limiter.finish(42);
    EXPECT_TRUE(limiter.start(42));

    // ...but not two!
    EXPECT_FALSE(limiter.start(42));

    // Different ids should still have quota...
    EXPECT_TRUE(limiter.start(666));
    limiter.finish(666);

    // Finish all pending operations
    limiter.finish(42);
    limiter.finish(42);
    limiter.finish(42);
}

TEST(OperationLimiter, finishWithoutStart) {
    OperationLimiter<int> limiter(1);

    // Will output a LOG(FATAL_WITHOUT_ABORT), but we have no way to probe this.
    limiter.finish(42);

    // This will ensure that the finish() above didn't set a negative value.
    EXPECT_TRUE(limiter.start(42));
    EXPECT_FALSE(limiter.start(42));
}

TEST(OperationLimiter, destroyWithActiveOperations) {
    // The death message doesn't seem to be captured on Android.
    EXPECT_DEBUG_DEATH(
            {
                OperationLimiter<int> limiter(3);
                limiter.start(42);
            },
            "" /* "active operations */);
}

}  // namespace netdutils
}  // namespace android