WIP add a decorator to limit cache size

This commit is contained in:
dotasek 2024-06-18 15:24:58 -04:00
parent 563d6fe27a
commit 4fa581d242
5 changed files with 179 additions and 6 deletions

View File

@ -0,0 +1,83 @@
package org.hl7.fhir.validation.cli.services;
import org.hl7.fhir.validation.ValidationEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
public class MaxSizeSessionCacheDecorator implements SessionCache {
public final int maxSize;
public final SessionCache sessionCache;
private final List<String> sessionIds;
public MaxSizeSessionCacheDecorator(SessionCache sessionCache, int maxSize) {
this.sessionCache = sessionCache;
this.maxSize = maxSize;
this.sessionIds = new ArrayList<>(sessionCache.getSessionIds());
if (this.sessionIds.size() > maxSize) {
throw new IllegalArgumentException("Session cache size exceeds the maximum size");
}
}
@Override
public String cacheSession(ValidationEngine validationEngine) {
checkSizeAndMaintainMax(null);
String key = sessionCache.cacheSession(validationEngine);
sessionIds.add(key);
return key;
}
@Override
public String cacheSession(Supplier<ValidationEngine> validationEngineSupplier) {
checkSizeAndMaintainMax(null);
ValidationEngine validationEngine = validationEngineSupplier.get();
return sessionCache.cacheSession(validationEngine);
}
private void checkSizeAndMaintainMax(String keyToAdd) {
if (keyToAdd != null || sessionCache.sessionExists(keyToAdd)) {
return;
}
Set<String> sessionIds = sessionCache.getSessionIds();
//Sync our tracked keys, in case the underlying cache has changed
this.sessionIds.removeIf(key -> !sessionIds.contains(key));
if (this.sessionIds.size() >= maxSize) {
final String key = this.sessionIds.remove(0);
sessionCache.removeSession(key);
}
}
@Override
public String cacheSession(String sessionId, ValidationEngine validationEngine) {
checkSizeAndMaintainMax(sessionId);
cacheSession(sessionId, validationEngine);
return sessionCache.cacheSession(
sessionId, validationEngine);
}
@Override
public boolean sessionExists(String sessionId) {
return sessionCache.sessionExists(sessionId);
}
@Override
public ValidationEngine removeSession(String sessionId) {
sessionIds.remove(sessionId);
return sessionCache.removeSession(sessionId);
}
@Override
public ValidationEngine fetchSessionValidatorEngine(String sessionId) {
return sessionCache.fetchSessionValidatorEngine(sessionId);
}
@Override
public Set<String> getSessionIds() {
return sessionCache.getSessionIds();
}
}

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.validation.cli.services;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.apache.commons.collections4.map.PassiveExpiringMap;
import org.hl7.fhir.validation.ValidationEngine;
@ -44,6 +45,12 @@ public class PassiveExpiringSessionCache implements SessionCache {
return generatedId;
}
@Override
public String cacheSession(Supplier<ValidationEngine> validationEngineSupplier) {
ValidationEngine engine = validationEngineSupplier.get();
return this.cacheSession(engine);
}
/**
* Stores the initialized {@link ValidationEngine} in the cache with the passed in id as the key. If a null key is
* passed in, a new key is generated and returned.
@ -96,6 +103,11 @@ public class PassiveExpiringSessionCache implements SessionCache {
return cachedSessions.containsKey(sessionId);
}
@Override
public ValidationEngine removeSession(String sessionId) {
return cachedSessions.remove(sessionId);
}
/**
* Returns the stored {@link ValidationEngine} associated with the passed in session id, if one such instance exists.
* @param sessionId The {@link String} session id.

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.validation.cli.services;
import java.util.Set;
import java.util.function.Supplier;
import org.hl7.fhir.validation.ValidationEngine;
@ -14,6 +15,14 @@ public interface SessionCache {
*/
String cacheSession(ValidationEngine validationEngine);
/**
* Uses the passed {@link Supplier} to generate a {@link ValidationEngine} and add it to the cache. Returns the
* session id that will be associated with the generated instance.
* @param validationEngineSupplier {@link Supplier} of {@link ValidationEngine}
* @return The {@link String} id associated with the stored instance.
*/
String cacheSession(Supplier<ValidationEngine> validationEngineSupplier);
/**
* Stores the initialized {@link ValidationEngine} in the cache with the passed in id as the key. If a null key is
* passed in, a new key is generated and returned.
@ -23,9 +32,6 @@ public interface SessionCache {
*/
String cacheSession(String sessionId, ValidationEngine validationEngine);
/**
* Checks if the passed in {@link String} id exists in the set of stored session id.
* @param sessionId The {@link String} id to search for.
@ -33,6 +39,13 @@ public interface SessionCache {
*/
boolean sessionExists(String sessionId);
/**
* Removes the {@link ValidationEngine} associated with the passed in session id.
* @param sessionId The {@link String} session id.
* @return The {@link ValidationEngine} instance that was removed.
*/
ValidationEngine removeSession(String sessionId);
/**
* Returns the stored {@link ValidationEngine} associated with the passed in session id, if one such instance exists.
* @param sessionId The {@link String} session id.

View File

@ -489,9 +489,20 @@ public class ValidationService {
if (sessionId != null) {
System.out.println("No such cached session exists for session id " + sessionId + ", re-instantiating validator.");
}
ValidationEngine validationEngine = getValidationEngineFromCliContext(cliContext, definitions, tt);
sessionId = sessionCache.cacheSession(validationEngine);
System.out.println("Cached new session. Cache size = " + sessionCache.getSessionIds().size());
// Send a supplier instead of instantiating. This will permit the sessionCache to manage existing sessions (drop
// or expire old sessions as needed)
sessionId = sessionCache.cacheSession(() -> {
try {
return buildValidationEngine(cliContext, definitions, tt);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
});
} else {
System.out.println("Cached session exists for session id " + sessionId + ", returning stored validator session id. Cache size = " + sessionCache.getSessionIds().size());
}

View File

@ -0,0 +1,54 @@
package org.hl7.fhir.validation.cli.services;
import org.hl7.fhir.validation.ValidationEngine;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ArrayList;
import static org.mockito.Mockito.mock;
public class MaxSizeSessionCacheDecoratorTest {
private List<ValidationEngine> getMockedEngines(int count) {
List<ValidationEngine> engines = new ArrayList<>();
for (int i = 0; i < count; i++) {
engines.add(mock(ValidationEngine.class));
}
return engines;
}
private LinkedHashMap<String, ValidationEngine> addMockedEngines(SessionCache cache, int count) {
LinkedHashMap<String, ValidationEngine> engineMap = new LinkedHashMap<>();
List<ValidationEngine> engines = getMockedEngines(count);
for (ValidationEngine engine : engines) {
String key = cache.cacheSession(engine);
engineMap.put(key, engine);
}
return engineMap;
}
@Test
public void trivialCase() {
MaxSizeSessionCacheDecorator maxSizeSessionCacheDecorator = new MaxSizeSessionCacheDecorator(new PassiveExpiringSessionCache(), 4);
LinkedHashMap<String, ValidationEngine> initialEngines = addMockedEngines(maxSizeSessionCacheDecorator, 3);
Assertions.assertEquals(3, maxSizeSessionCacheDecorator.getSessionIds().size());
List<ValidationEngine> newEngines = getMockedEngines(2);
for (ValidationEngine engine : newEngines) {
maxSizeSessionCacheDecorator.cacheSession(engine);
}
Assertions.assertEquals(4, maxSizeSessionCacheDecorator.getSessionIds().size());
Assertions.assertTrue(maxSizeSessionCacheDecorator.getSessionIds().contains()
}
}