Skip to content

Commit 67d56bf

Browse files
DVC-6643 - Update variable() to use new WASM variableForUser function (#56)
* Adding data fixtures matching the other server SDKs * simple server for unit tests to validate client * changing the unit test to use the new config server and data fixtures * fixing some logic and adding new WASM variable function * tweaked how fixtures work * fixed missing test annotation and optimized imports * adding more unit tests, bug fixes and code cleanup * review feedback * update test to actually create a new client properly * Making LocalBucketing an instance member not static * comments and formatting
1 parent 0c67ceb commit 67d56bf

File tree

8 files changed

+8514
-55
lines changed

8 files changed

+8514
-55
lines changed

src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
public final class DVCLocalClient {
1818

19-
private static LocalBucketing localBucketing = new LocalBucketing();
19+
private LocalBucketing localBucketing = new LocalBucketing();
2020

2121
private EnvironmentConfigManager configManager;
2222

@@ -101,10 +101,6 @@ public <T> Variable<T> variable(User user, String key, T defaultValue) {
101101
throw new IllegalArgumentException("Missing parameter: defaultValue");
102102
}
103103

104-
if (!configManager.isConfigInitialized()) {
105-
System.out.println("Variable called before DVCClient has initialized, returning default value");
106-
}
107-
108104
TypeEnum variableType = TypeEnum.fromClass(defaultValue.getClass());
109105
Variable<T> defaultVariable = (Variable<T>) Variable.builder()
110106
.key(key)
@@ -114,40 +110,43 @@ public <T> Variable<T> variable(User user, String key, T defaultValue) {
114110
.isDefaulted(true)
115111
.build();
116112

117-
if (!isInitialized) {
113+
if (!configManager.isConfigInitialized() || !isInitialized) {
114+
System.out.println("Variable called before DVCClient has initialized, returning default value");
115+
try {
116+
eventQueueManager.queueAggregateEvent(Event.builder().type("aggVariableDefaulted").target(key).build(), null);
117+
} catch (Exception e) {
118+
System.out.printf("Unable to parse aggVariableDefaulted event for Variable %s due to error: %s", key, e.toString());
119+
}
118120
return defaultVariable;
119121
}
120-
122+
String variableJSON = null;
121123
try {
122-
BucketedUserConfig bucketedUserConfig = localBucketing.generateBucketedConfig(sdkKey, user);
123-
if (bucketedUserConfig.variables.containsKey(key)) {
124-
BaseVariable baseVariable = bucketedUserConfig.variables.get(key);
124+
variableJSON = localBucketing.getVariable(sdkKey, user, key, variableType, true);
125+
if (variableJSON == null || variableJSON.isEmpty()) {
126+
return defaultVariable;
127+
} else {
128+
ObjectMapper mapper = new ObjectMapper();
129+
Variable baseVariable = mapper.readValue(variableJSON, Variable.class);
130+
125131
Variable<T> variable = (Variable<T>) Variable.builder()
126-
.key(key)
127-
.type(baseVariable.getType())
128-
.value(baseVariable.getValue())
129-
.defaultValue(defaultValue)
130-
.isDefaulted(false)
131-
.build();
132+
.key(key)
133+
.type(baseVariable.getType())
134+
.value(baseVariable.getValue())
135+
.defaultValue(defaultValue)
136+
.isDefaulted(false)
137+
.build();
132138
if (variable.getType() != variableType) {
133-
throw new IllegalArgumentException("Variable type mismatch, returning default value");
139+
System.out.printf("Variable type mismatch, returning default value");
140+
return defaultVariable;
134141
}
135142
variable.setDefaultValue(defaultValue);
136143
variable.setIsDefaulted(false);
137-
eventQueueManager.queueAggregateEvent(Event.builder().type("aggVariableEvaluated").target(key).build(), bucketedUserConfig);
138144
return variable;
139-
} else {
140-
eventQueueManager.queueAggregateEvent(Event.builder().type("aggVariableDefaulted").target(key).build(), bucketedUserConfig);
141-
return defaultVariable;
142145
}
146+
} catch(JsonProcessingException jpe){
147+
System.out.printf("Unable to parse Variable %s due to JSON error: err=%s, data=", key, jpe.getMessage(), variableJSON);
143148
} catch (Exception e) {
144-
System.out.printf("Unable to parse JSON for Variable %s due to error: %s", key, e.toString());
145-
}
146-
147-
try {
148-
eventQueueManager.queueAggregateEvent(Event.builder().type("aggVariableDefaulted").target(key).build(), null);
149-
} catch (Exception e) {
150-
System.out.printf("Unable to parse aggVariableDefaulted event for Variable %s due to error: %s", key, e.toString());
149+
System.out.printf("Unable to evaluate Variable %s due to error: %s", key, e);
151150
}
152151
return defaultVariable;
153152
}
@@ -220,8 +219,12 @@ public void close() {
220219
if (!isInitialized) {
221220
return;
222221
}
223-
configManager.cleanup();
224-
eventQueueManager.cleanup();
222+
if (configManager != null) {
223+
configManager.cleanup();
224+
}
225+
if (eventQueueManager != null) {
226+
eventQueueManager.cleanup();
227+
}
225228
}
226229

227230
private void validateUser(User user) {

src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.concurrent.atomic.AtomicReference;
1313

1414
import com.devcycle.sdk.server.common.model.User;
15+
import com.devcycle.sdk.server.common.model.Variable;
1516
import com.devcycle.sdk.server.local.model.BucketedUserConfig;
1617
import com.devcycle.sdk.server.local.model.FlushPayload;
1718
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -31,6 +32,8 @@ public class LocalBucketing {
3132
private Set<Integer> pinnedAddresses;
3233
private HashMap<String, Integer> sdkKeyAddresses;
3334

35+
private HashMap<Variable.TypeEnum, Integer> variableTypeMap = new HashMap<Variable.TypeEnum, Integer>();
36+
3437
public LocalBucketing() {
3538
OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
3639

@@ -53,6 +56,12 @@ public LocalBucketing() {
5356

5457
Memory mem = linker.get(store, "", "memory").get().memory();
5558
memRef.set(mem);
59+
60+
// WASM time seems problematic for getting global values so we'll just hardcode them
61+
variableTypeMap.put(Variable.TypeEnum.BOOLEAN, 0);
62+
variableTypeMap.put(Variable.TypeEnum.NUMBER, 1);
63+
variableTypeMap.put(Variable.TypeEnum.STRING, 2);
64+
variableTypeMap.put(Variable.TypeEnum.JSON, 3);
5665
}
5766

5867
private Collection<Extern> setImportsOnLinker() {
@@ -176,6 +185,29 @@ public BucketedUserConfig generateBucketedConfig(String sdkKey, User user) throw
176185
return config;
177186
}
178187

188+
public String getVariable(String sdkKey, User user, String key, Variable.TypeEnum variableTypeEnum, boolean shouldTrackEvent) throws JsonProcessingException {
189+
// need some kind of mutex?
190+
String userString = OBJECT_MAPPER.writeValueAsString(user);
191+
192+
int wasmVariableType = this.variableTypeMap.get(variableTypeEnum);
193+
194+
unpinAll();
195+
int sdkKeyAddress = getSDKKeyAddress(sdkKey);
196+
int userAddress = newWasmString(userString);
197+
int keyAddress = newWasmString(key);
198+
199+
Func getVariablePtr = linker.get(store, "", "variableForUser").get().func();
200+
WasmFunctions.Function5<Integer, Integer, Integer, Integer, Integer, Integer> variableForUser = WasmFunctions.func(
201+
store, getVariablePtr, I32, I32, I32, I32, I32, I32);
202+
203+
int resultAddress = variableForUser.call(sdkKeyAddress, userAddress, keyAddress, wasmVariableType, shouldTrackEvent ? 1 : 0);
204+
if (resultAddress == 0){
205+
return null;
206+
}
207+
String variableString = readWasmString(resultAddress);
208+
return variableString;
209+
}
210+
179211
public void initEventQueue(String sdkKey, String options) {
180212
unpinAll();
181213
int sdkKeyAddress = getSDKKeyAddress(sdkKey);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.devcycle.sdk.server.helpers;
2+
3+
import com.sun.net.httpserver.HttpExchange;
4+
import com.sun.net.httpserver.HttpServer;
5+
6+
import java.io.*;
7+
import java.net.InetSocketAddress;
8+
import java.nio.charset.StandardCharsets;
9+
10+
public class LocalConfigServer {
11+
private HttpServer server;
12+
private String configData = "";
13+
public LocalConfigServer(String configData) throws IOException{
14+
this.configData = configData;
15+
InetSocketAddress address = new InetSocketAddress(8000);
16+
server = HttpServer.create(address, 0);
17+
server.createContext("/", this::handleConfigRequest);
18+
server.setExecutor(null); // use the default executor
19+
System.out.println("Starting config server on " + address);
20+
}
21+
22+
public void handleConfigRequest(HttpExchange exchange) throws IOException {
23+
byte[] responseData = configData.getBytes(StandardCharsets.UTF_8);
24+
exchange.sendResponseHeaders(200, responseData.length);
25+
OutputStream outputStream = exchange.getResponseBody();
26+
outputStream.write(responseData);
27+
outputStream.flush();
28+
outputStream.close();
29+
}
30+
31+
public void setConfigData(String configData) {
32+
this.configData = configData;
33+
}
34+
35+
public void start() {
36+
this.server.start();
37+
}
38+
39+
public void stop() {
40+
this.server.stop(0);
41+
}
42+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.devcycle.sdk.server.helpers;
2+
import java.io.BufferedReader;
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.io.InputStreamReader;
6+
import java.nio.charset.StandardCharsets;
7+
8+
public class TestDataFixtures {
9+
/**
10+
* Loads the config data from the file in the resources folder
11+
* @param fileName name of the file to load
12+
* @return the String contents of that resource file
13+
*/
14+
private static String loadConfigData(String fileName) {
15+
String configData = "";
16+
ClassLoader classLoader = TestDataFixtures.class.getClassLoader();
17+
try (InputStream inputStream = classLoader.getResourceAsStream(fileName);
18+
InputStreamReader streamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
19+
BufferedReader reader = new BufferedReader(streamReader)) {
20+
String line;
21+
while ((line = reader.readLine()) != null) {
22+
configData += line;
23+
}
24+
} catch (IOException e) {
25+
System.out.println("Failed to load config data ["+fileName+"]: " + e.getMessage());
26+
e.printStackTrace();
27+
}
28+
return configData;
29+
}
30+
31+
32+
public static String SmallConfig() {
33+
return loadConfigData("fixture_small_config.json");
34+
}
35+
36+
public static String LargeConfig() {
37+
return loadConfigData("fixture_large_config.json");
38+
}
39+
40+
public static String SmallConfigWithSpecialCharacters() {
41+
return loadConfigData("fixture_small_config_special_characters.json");
42+
}
43+
}

0 commit comments

Comments
 (0)