SingleWriterBpfMap - introduce getSingleton with caching am: 52d6c5587f
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/3134198 Change-Id: I53854b7b5779871d05b6754633ff36a5cb8837e4 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
commit
3f61e89bcf
3 changed files with 39 additions and 15 deletions
|
|
@ -112,8 +112,8 @@ public final class BpfMapTest {
|
|||
|
||||
private BpfMap<TetherDownstream6Key, Tether6Value> openTestMap() throws Exception {
|
||||
return mShouldTestSingleWriterMap
|
||||
? new SingleWriterBpfMap<>(TETHER2_DOWNSTREAM6_FS_PATH, TetherDownstream6Key.class,
|
||||
Tether6Value.class)
|
||||
? SingleWriterBpfMap.getSingleton(TETHER2_DOWNSTREAM6_FS_PATH,
|
||||
TetherDownstream6Key.class, Tether6Value.class)
|
||||
: new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, TetherDownstream6Key.class,
|
||||
Tether6Value.class);
|
||||
}
|
||||
|
|
@ -510,12 +510,6 @@ public final class BpfMapTest {
|
|||
@Test
|
||||
public void testSingleWriterCacheEffectiveness() throws Exception {
|
||||
assumeTrue(mShouldTestSingleWriterMap);
|
||||
|
||||
// Ensure the map is not empty.
|
||||
for (int i = 0; i < mTestData.size(); i++) {
|
||||
mTestMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
|
||||
}
|
||||
|
||||
// Benchmark parameters.
|
||||
final int timeoutMs = 5_000; // Only hit if threads don't complete.
|
||||
final int benchmarkTimeMs = 2_000;
|
||||
|
|
@ -524,11 +518,17 @@ public final class BpfMapTest {
|
|||
// Only require 3x to reduce test flakiness.
|
||||
final int expectedSpeedup = 3;
|
||||
|
||||
final BpfMap cachedMap = new SingleWriterBpfMap(TETHER_DOWNSTREAM6_FS_PATH,
|
||||
final BpfMap cachedMap = SingleWriterBpfMap.getSingleton(TETHER2_DOWNSTREAM6_FS_PATH,
|
||||
TetherDownstream6Key.class, Tether6Value.class);
|
||||
final BpfMap uncachedMap = new BpfMap(TETHER_DOWNSTREAM6_FS_PATH,
|
||||
TetherDownstream6Key.class, Tether6Value.class);
|
||||
|
||||
// Ensure the maps are not empty.
|
||||
for (int i = 0; i < mTestData.size(); i++) {
|
||||
cachedMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
|
||||
uncachedMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
|
||||
}
|
||||
|
||||
final CompletableFuture<Integer> cachedResult = new CompletableFuture<>();
|
||||
final CompletableFuture<Integer> uncachedResult = new CompletableFuture<>();
|
||||
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ public class BpfNetMaps {
|
|||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private static IBpfMap<S32, U32> getConfigurationMap() {
|
||||
try {
|
||||
return new SingleWriterBpfMap<>(
|
||||
return SingleWriterBpfMap.getSingleton(
|
||||
CONFIGURATION_MAP_PATH, S32.class, U32.class);
|
||||
} catch (ErrnoException e) {
|
||||
throw new IllegalStateException("Cannot open netd configuration map", e);
|
||||
|
|
@ -199,7 +199,7 @@ public class BpfNetMaps {
|
|||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private static IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
|
||||
try {
|
||||
return new SingleWriterBpfMap<>(
|
||||
return SingleWriterBpfMap.getSingleton(
|
||||
UID_OWNER_MAP_PATH, S32.class, UidOwnerValue.class);
|
||||
} catch (ErrnoException e) {
|
||||
throw new IllegalStateException("Cannot open uid owner map", e);
|
||||
|
|
@ -209,7 +209,7 @@ public class BpfNetMaps {
|
|||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private static IBpfMap<S32, U8> getUidPermissionMap() {
|
||||
try {
|
||||
return new SingleWriterBpfMap<>(
|
||||
return SingleWriterBpfMap.getSingleton(
|
||||
UID_PERMISSION_MAP_PATH, S32.class, U8.class);
|
||||
} catch (ErrnoException e) {
|
||||
throw new IllegalStateException("Cannot open uid permission map", e);
|
||||
|
|
@ -230,7 +230,7 @@ public class BpfNetMaps {
|
|||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private static IBpfMap<S32, U8> getDataSaverEnabledMap() {
|
||||
try {
|
||||
return new SingleWriterBpfMap<>(
|
||||
return SingleWriterBpfMap.getSingleton(
|
||||
DATA_SAVER_ENABLED_MAP_PATH, S32.class, U8.class);
|
||||
} catch (ErrnoException e) {
|
||||
throw new IllegalStateException("Cannot open data saver enabled map", e);
|
||||
|
|
@ -240,7 +240,7 @@ public class BpfNetMaps {
|
|||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private static IBpfMap<IngressDiscardKey, IngressDiscardValue> getIngressDiscardMap() {
|
||||
try {
|
||||
return new SingleWriterBpfMap<>(INGRESS_DISCARD_MAP_PATH,
|
||||
return SingleWriterBpfMap.getSingleton(INGRESS_DISCARD_MAP_PATH,
|
||||
IngressDiscardKey.class, IngressDiscardValue.class);
|
||||
} catch (ErrnoException e) {
|
||||
throw new IllegalStateException("Cannot open ingress discard map", e);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package com.android.net.module.util;
|
|||
|
||||
import android.os.Build;
|
||||
import android.system.ErrnoException;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
|
@ -61,7 +62,12 @@ public class SingleWriterBpfMap<K extends Struct, V extends Struct> extends BpfM
|
|||
// our code can contain hundreds of items.
|
||||
private final HashMap<K, V> mCache = new HashMap<>();
|
||||
|
||||
public SingleWriterBpfMap(@NonNull final String path, final Class<K> key,
|
||||
// This should only ever be called (hence private) once for a given 'path'.
|
||||
// Java-wise what matters is the entire {path, key, value} triplet,
|
||||
// but of course the kernel exclusive lock is just on the path (fd),
|
||||
// and any BpfMap has (or should have...) well defined key/value types
|
||||
// (or at least their sizes) so in practice it doesn't really matter.
|
||||
private SingleWriterBpfMap(@NonNull final String path, final Class<K> key,
|
||||
final Class<V> value) throws ErrnoException, NullPointerException {
|
||||
super(path, BPF_F_RDWR_EXCLUSIVE, key, value);
|
||||
|
||||
|
|
@ -73,6 +79,24 @@ public class SingleWriterBpfMap<K extends Struct, V extends Struct> extends BpfM
|
|||
}
|
||||
}
|
||||
|
||||
// This allows reuse of SingleWriterBpfMap objects for the same {path, keyClass, valueClass}.
|
||||
// These are never destroyed, so once created the lock is (effectively) held till process death
|
||||
// (even if fixed, there would still be a write-only fd cache in underlying BpfMap base class).
|
||||
private static final HashMap<Pair<String, Pair<Class, Class>>, SingleWriterBpfMap>
|
||||
singletonCache = new HashMap<>();
|
||||
|
||||
// This is the public 'factory method' that (creates if needed and) returns a singleton instance
|
||||
// for a given map. This holds an exclusive lock and has a permanent write-through cache.
|
||||
// It will not be released until process death (or at least unload of the relevant class loader)
|
||||
public synchronized static <KK extends Struct, VV extends Struct> SingleWriterBpfMap<KK,VV>
|
||||
getSingleton(@NonNull final String path, final Class<KK> key, final Class<VV> value)
|
||||
throws ErrnoException, NullPointerException {
|
||||
var cacheKey = new Pair<>(path, new Pair<Class,Class>(key, value));
|
||||
if (!singletonCache.containsKey(cacheKey))
|
||||
singletonCache.put(cacheKey, new SingleWriterBpfMap(path, key, value));
|
||||
return singletonCache.get(cacheKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void updateEntry(K key, V value) throws ErrnoException {
|
||||
super.updateEntry(key, value);
|
||||
|
|
|
|||
Loading…
Reference in a new issue