Skip to content

Commit

Permalink
WIP add a decorator to limit cache size
Browse files Browse the repository at this point in the history
  • Loading branch information
dotasek committed Jun 18, 2024
1 parent 563d6fe commit 4fa581d
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
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;
Expand Down Expand Up @@ -44,6 +45,12 @@ public String cacheSession(ValidationEngine validationEngine) {
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.
Expand Down Expand Up @@ -96,6 +103,11 @@ public boolean sessionExists(String sessionId) {
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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.
Expand All @@ -23,16 +32,20 @@ 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.
* @return {@link Boolean#TRUE} if such id exists.
*/
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,9 +489,20 @@ public String initializeValidator(CliContext cliContext, String definitions, Tim
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());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}

}

0 comments on commit 4fa581d

Please sign in to comment.