Skip to content

Commit 21b5a2d

Browse files
Fix the usage of CacheIteratorHelper for service account (#75510) (#75765)
CacheIteratorHelper requires lock acquisition for any mutation to the underlying cache. This means it is incorrect to manipulate the cache without invocation of CacheIteratorHelper#acquireUpdateLock. This is OK for caches of simple values, but feels excessive for caches of ListenableFuture. This PR update the cache invalidation code to use cache.forEach instead of CacheInvalidator. It simplifies the code by removing any explicit lockings. The tradeoff is that it needs to build a list of keys to delete in memory. Overall it is a better tradeoff since no explicit locking is required and better leverage of Cache's own methods. Co-authored-by: Yang Wang <yang.wang@elastic.co>
1 parent 6c5bfe9 commit 21b5a2d

File tree

1 file changed

+27
-12
lines changed

1 file changed

+27
-12
lines changed

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/CachingServiceAccountTokenStore.java

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@
2020
import org.elasticsearch.threadpool.ThreadPool;
2121
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
2222
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
23-
import org.elasticsearch.xpack.core.security.support.CacheIteratorHelper;
2423
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
2524

25+
import java.util.ArrayList;
2626
import java.util.Collection;
27+
import java.util.HashSet;
28+
import java.util.Iterator;
29+
import java.util.List;
30+
import java.util.Set;
2731
import java.util.concurrent.ExecutionException;
2832
import java.util.concurrent.atomic.AtomicBoolean;
33+
import java.util.function.Predicate;
2934

3035
public abstract class CachingServiceAccountTokenStore implements ServiceAccountTokenStore, CacheInvalidatorRegistry.CacheInvalidator {
3136

@@ -42,7 +47,6 @@ public abstract class CachingServiceAccountTokenStore implements ServiceAccountT
4247
private final Settings settings;
4348
private final ThreadPool threadPool;
4449
private final Cache<String, ListenableFuture<CachedResult>> cache;
45-
private CacheIteratorHelper<String, ListenableFuture<CachedResult>> cacheIteratorHelper;
4650
private final Hasher hasher;
4751

4852
CachingServiceAccountTokenStore(Settings settings, ThreadPool threadPool) {
@@ -54,10 +58,8 @@ public abstract class CachingServiceAccountTokenStore implements ServiceAccountT
5458
.setExpireAfterWrite(ttl)
5559
.setMaximumWeight(CACHE_MAX_TOKENS_SETTING.get(settings))
5660
.build();
57-
cacheIteratorHelper = new CacheIteratorHelper<>(cache);
5861
} else {
5962
cache = null;
60-
cacheIteratorHelper = null;
6163
}
6264
hasher = Hasher.resolve(CACHE_HASH_ALGO_SETTING.get(settings));
6365
}
@@ -126,16 +128,29 @@ private void authenticateWithCache(ServiceAccountToken token, ActionListener<Sto
126128
@Override
127129
public final void invalidate(Collection<String> qualifiedTokenNames) {
128130
if (cache != null) {
129-
logger.trace("invalidating cache for service token [{}]",
130-
Strings.collectionToCommaDelimitedString(qualifiedTokenNames));
131-
for (String qualifiedTokenName : qualifiedTokenNames) {
132-
if (qualifiedTokenName.endsWith("/")) {
133-
// Wildcard case of invalidating all tokens for a service account, e.g. "elastic/fleet-server/"
134-
cacheIteratorHelper.removeKeysIf(key -> key.startsWith(qualifiedTokenName));
135-
} else {
136-
cache.invalidate(qualifiedTokenName);
131+
logger.trace("invalidating cache for service token [{}]", Strings.collectionToCommaDelimitedString(qualifiedTokenNames));
132+
final Set<String> exacts = new HashSet<>(qualifiedTokenNames);
133+
final Set<String> prefixes = new HashSet<>();
134+
final Iterator<String> it = exacts.iterator();
135+
while (it.hasNext()) {
136+
final String name = it.next();
137+
if (name.endsWith("/")) {
138+
prefixes.add(name);
139+
it.remove();
137140
}
138141
}
142+
143+
exacts.forEach(cache::invalidate);
144+
if (false == prefixes.isEmpty()) {
145+
final Predicate<String> predicate = k -> prefixes.stream().anyMatch(k::startsWith);
146+
final List<String> keys = new ArrayList<>();
147+
cache.forEach((k, v) -> {
148+
if (predicate.test(k)) {
149+
keys.add(k);
150+
}
151+
});
152+
keys.forEach(cache::invalidate);
153+
}
139154
}
140155
}
141156

0 commit comments

Comments
 (0)