Skip to content

fix: flagd caching #581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dev.openfeature.contrib.providers.flagd;

import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType;

/**
* Helper class to hold configuration default values.
*/
Expand Down Expand Up @@ -34,7 +36,7 @@ public final class Config {
public static final String REASON_FIELD = "reason";
public static final String METADATA_FIELD = "metadata";

public static final String LRU_CACHE = "lru";
public static final String LRU_CACHE = CacheType.LRU.getValue();
static final String DEFAULT_CACHE = LRU_CACHE;

static final int DEFAULT_MAX_EVENT_STREAM_RETRIES = 5;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import dev.openfeature.contrib.providers.flagd.resolver.Resolver;
import dev.openfeature.contrib.providers.flagd.resolver.grpc.GrpcResolver;
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheFactory;
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.Cache;
import dev.openfeature.contrib.providers.flagd.resolver.process.InProcessResolver;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.EventProvider;
Expand Down Expand Up @@ -51,7 +51,10 @@ public FlagdProvider(final FlagdOptions options) {
break;
case RPC:
this.flagResolver =
new GrpcResolver(options, CacheFactory.getCache(options), this::getState, this::setState);
new GrpcResolver(options,
new Cache(options.getCacheType(), options.getMaxCacheSize()),
this::getState,
this::setState);
break;
default:
throw new IllegalStateException(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,42 @@
package dev.openfeature.contrib.providers.flagd.resolver.grpc.cache;

import dev.openfeature.sdk.ProviderEvaluation;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.LRUMap;

import java.util.Collections;
import java.util.Map;

import static dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType.DISABLED;
import static dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType.LRU;

/**
* Exposes caching mechanism for flag evaluations.
*/
@Slf4j
public class Cache {
private Map<String,ProviderEvaluation<? extends Object>> store;
private Boolean enabled = false;
private Map<String, ProviderEvaluation<? extends Object>> store;

@Getter
private final Boolean enabled;

/**
* Initialize the cache.
* @param type of cache.
*
* @param forType type of the cache.
* @param maxCacheSize max amount of element to keep.
*/
public Cache(CacheType type, int maxCacheSize) {
if (type == null) {
return;
}

switch (type) {
case DISABLED:
return;
case LRU:
default:
this.store = Collections.synchronizedMap(new LRUMap<>(maxCacheSize));
public Cache(final String forType, int maxCacheSize) {
if (DISABLED.getValue().equals(forType)) {
enabled = false;
} else if (LRU.getValue().equals(forType)) {
enabled = true;
this.store = Collections.synchronizedMap(new LRUMap<>(maxCacheSize));
} else {
enabled = false;
log.info(String.format("Unsupported cache type %s, continuing without cache", forType));
}

this.enabled = true;
}

public Boolean getEnabled() {
return this.enabled;
}

public void put(String key, ProviderEvaluation<? extends Object> value) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
package dev.openfeature.contrib.providers.flagd.resolver.grpc.cache;

import static dev.openfeature.contrib.providers.flagd.Config.LRU_CACHE;
import lombok.Getter;

/**
* Defines which type of cache to use.
*/
@Getter
public enum CacheType {
DISABLED("disabled"),
LRU(LRU_CACHE);
LRU("lru");

private final String type;
private final String value;

CacheType(String type) {
this.type = type;
}

@Override
public String toString() {
return this.type;
CacheType(String value) {
this.value = value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ void invalidate_cache() {
mockStaticService.when(() -> ServiceGrpc.newStub(any()))
.thenReturn(serviceStubMock);

final Cache cache = new Cache(CacheType.LRU, 5);
final Cache cache = new Cache("lru", 5);
grpc = new GrpcConnector(FlagdOptions.builder().build(), cache, state -> {
});
}
Expand Down Expand Up @@ -694,7 +694,7 @@ void disabled_cache() {
.resolveObject(argThat(x -> FLAG_KEY_OBJECT.equals(x.getFlagKey())))).thenReturn(objectResponse);

// disabled cache
final Cache cache = new Cache(CacheType.DISABLED, 0);
final Cache cache = new Cache("disabled", 0);

GrpcConnector grpc;
try (MockedStatic<ServiceGrpc> mockStaticService = mockStatic(ServiceGrpc.class)) {
Expand Down Expand Up @@ -808,7 +808,7 @@ private FlagdProvider createProvider(GrpcConnector grpc) {

// create provider with given grpc provider and state supplier
private FlagdProvider createProvider(GrpcConnector grpc, Supplier<ProviderState> getState) {
final Cache cache = new Cache(CacheType.LRU, 5);
final Cache cache = new Cache("lru", 5);

return createProvider(grpc, cache, getState);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.openfeature.contrib.providers.flagd.e2e.rpc;

import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.parallel.Isolated;

Expand All @@ -19,10 +20,11 @@ public class FlagdRpcSetup {
@BeforeAll()
public static void setup() {
FlagdRpcSetup.provider = new FlagdProvider(FlagdOptions.builder()
.resolverType(Config.Evaluator.RPC)
// set a generous deadline, to prevent timeouts in actions
.deadline(3000)
.build());
.resolverType(Config.Evaluator.RPC)
// set a generous deadline, to prevent timeouts in actions
.deadline(3000)
.cacheType(CacheType.DISABLED.getValue())
.build());
StepDefinitions.setProvider(provider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void validate_retry_calls(int retries) throws NoSuchFieldException, IllegalAcces
.maxEventStreamRetries(retries)
.build();

final Cache cache = new Cache(CacheType.DISABLED, 0);
final Cache cache = new Cache("disabled", 0);

final ServiceGrpc.ServiceStub mockStub = mock(ServiceGrpc.ServiceStub.class);
doAnswer(invocation -> null).when(mockStub).eventStream(any(), any());
Expand Down Expand Up @@ -87,7 +87,7 @@ void validate_retry_calls(int retries) throws NoSuchFieldException, IllegalAcces

@Test
void initialization_succeed_with_connected_status() throws NoSuchFieldException, IllegalAccessException {
final Cache cache = new Cache(CacheType.DISABLED, 0);
final Cache cache = new Cache("disabled", 0);

final ServiceGrpc.ServiceStub mockStub = mock(ServiceGrpc.ServiceStub.class);
doAnswer(invocation -> null).when(mockStub).eventStream(any(), any());
Expand All @@ -111,7 +111,7 @@ void initialization_succeed_with_connected_status() throws NoSuchFieldException,

@Test
void initialization_fail_with_timeout() throws Exception {
final Cache cache = new Cache(CacheType.DISABLED, 0);
final Cache cache = new Cache("disabled", 0);

final ServiceGrpc.ServiceStub mockStub = mock(ServiceGrpc.ServiceStub.class);
doAnswer(invocation -> null).when(mockStub).eventStream(any(), any());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package dev.openfeature.contrib.providers.flagd.resolver.grpc.cache;

import dev.openfeature.sdk.ProviderEvaluation;
import org.junit.jupiter.api.Test;

import static dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType.DISABLED;
import static dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType.LRU;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

class CacheTest {

@Test
void cacheTypeTest() {
// given
final Cache disabled = new Cache(DISABLED.getValue(), 0);
final Cache lru = new Cache(LRU.getValue(), 10);
final Cache undefined = new Cache("invalid", 10);

// then
assertTrue(lru.getEnabled());
assertFalse(disabled.getEnabled());
assertFalse(undefined.getEnabled());
}


@Test
void lruOperationValidation() {
// given
final Cache lru = new Cache(LRU.getValue(), 1);

// when
final ProviderEvaluation<Object> evaluation = ProviderEvaluation.builder()
.value("value")
.variant("one")
.build();
lru.put("key", evaluation);

// then
assertEquals(evaluation, lru.get("key"));

// when
lru.clear();

// then
assertNull(lru.get("key"));
}
}
2 changes: 1 addition & 1 deletion providers/flagd/test-harness