From b5aaf08e12b4a3c191912f58e9d87b671a4acb33 Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Thu, 25 May 2023 07:22:16 -0700 Subject: [PATCH 01/15] minor refactor --- .../server/local/managers/EventQueueManager.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java index 5496069e..68261e44 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java @@ -45,17 +45,15 @@ public EventQueueManager(String sdkKey, LocalBucketing localBucketing, DVCLocalO } private void setupScheduler() { - Runnable getConfigRunnable = new Runnable() { - public void run() { - try { - flushEvents(); - } catch (Exception e) { - e.printStackTrace(); - } + Runnable flushEventsRunnable = () -> { + try { + flushEvents(); + } catch (Exception e) { + e.printStackTrace(); } }; - scheduler.scheduleAtFixedRate(getConfigRunnable, 0, this.eventFlushIntervalMS, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate(flushEventsRunnable, 0, this.eventFlushIntervalMS, TimeUnit.MILLISECONDS); } /** @@ -173,6 +171,7 @@ private boolean checkEventQueueSize() throws Exception { } public void cleanup() { + // Flush any remaining events try { flushEvents(); } catch (Exception e) { From aa0bd8b9e1abb4f8fe12effbe59c5fd22c232f0f Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Thu, 25 May 2023 07:22:38 -0700 Subject: [PATCH 02/15] fixed data type to match what test harness expects --- src/main/java/com/devcycle/sdk/server/common/model/User.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/devcycle/sdk/server/common/model/User.java b/src/main/java/com/devcycle/sdk/server/common/model/User.java index a8b0ee6a..bb8c8aaf 100755 --- a/src/main/java/com/devcycle/sdk/server/common/model/User.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/User.java @@ -13,6 +13,7 @@ package com.devcycle.sdk.server.common.model; import com.devcycle.sdk.server.local.utils.LongTimestampDeserializer; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -70,11 +71,13 @@ public class User { @Schema(description = "Date the user was created, Unix epoch timestamp format") @JsonProperty("createdDate") @JsonDeserialize(using = LongTimestampDeserializer.class) + @JsonFormat(shape = JsonFormat.Shape.STRING) private Long createdDate; @Schema(description = "Date the user was last seen, Unix epoch timestamp format") @JsonProperty("lastSeenDate") @JsonDeserialize(using = LongTimestampDeserializer.class) + @JsonFormat(shape = JsonFormat.Shape.STRING) private Long lastSeenDate; @Schema(description = "Platform the SDK is running on") From d2ac6273e5b884a975a8f16c4ef1aa4a584a96d4 Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Thu, 25 May 2023 12:37:12 -0700 Subject: [PATCH 03/15] properly throw an error when the SDK key is bad in the cloud sdk --- .../sdk/server/cloud/api/DVCCloudClient.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java index 9318a698..d14b81be 100755 --- a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java +++ b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java @@ -28,6 +28,14 @@ public DVCCloudClient(String sdkKey) { } public DVCCloudClient(String sdkKey, DVCCloudOptions options) { + if(sdkKey == null || sdkKey.equals("")) { + throw new IllegalArgumentException("Missing sdk key! Call initialize with a valid sdk key"); + } + + if(!isValidServerKey(sdkKey)) { + throw new IllegalArgumentException("Invalid sdk key provided. Please call initialize with a valid server sdk key"); + } + this.dvcOptions = options; api = new DVCCloudApiClient(sdkKey, options).initialize(); OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); @@ -194,6 +202,10 @@ private T getResponse(Call call) throws DVCException { } } + private boolean isValidServerKey(String serverKey) { + return serverKey.startsWith("server") || serverKey.startsWith("dvc_server"); + } + private void validateUser(User user) { if (user == null) { throw new IllegalArgumentException("User cannot be null"); From f9266b0f506cd5944d6c21d87ae978aa9889cd0c Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Thu, 25 May 2023 15:42:34 -0700 Subject: [PATCH 04/15] added retries to cloud track() and fixed response messages to match test harness --- .../sdk/server/cloud/api/DVCCloudClient.java | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java index d14b81be..7f39b1a7 100755 --- a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java +++ b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java @@ -153,9 +153,43 @@ public void track(User user, Event event) throws DVCException { .build(); Call response = api.track(userAndEvents, dvcOptions.getEnableEdgeDB()); - getResponse(response); + getResponseWithRetries(response, 5); } + + private T getResponseWithRetries(Call call, int maxRetries) throws DVCException { + int attempt = 0; + do { + try { + return getResponse(call); + } catch (DVCException e) { + attempt++; + + // if out of retries or this is an unauthorized error, throw up exception + if (attempt >= maxRetries || e.getHttpResponseCode() == HttpResponseCode.UNAUTHORIZED) { + throw e; + } + + try { + // sleep for a bit before retrying + long waitIntervalMS = (long) (10 * Math.pow(2, attempt)); + Thread.sleep(waitIntervalMS); + } catch (InterruptedException ex) { + // no-op + } + + // prep the call for a retry + call = call.clone(); + } + }while (attempt < maxRetries); + + // getting here should not be possible + ErrorResponse errorResponse = ErrorResponse.builder().build(); + errorResponse.setMessage("Out of retry attempts"); + throw new DVCException(HttpResponseCode.SERVER_ERROR, errorResponse); + } + + private T getResponse(Call call) throws DVCException { ErrorResponse errorResponse = ErrorResponse.builder().build(); Response response; @@ -170,15 +204,15 @@ private T getResponse(Call call) throws DVCException { HttpResponseCode httpResponseCode = HttpResponseCode.byCode(response.code()); errorResponse.setMessage("Unknown error"); - if (response.errorBody() != null) { - try { - errorResponse = OBJECT_MAPPER.readValue(response.errorBody().string(), ErrorResponse.class); - } catch (IOException e) { - errorResponse.setMessage(e.getMessage()); - throw new DVCException(httpResponseCode, errorResponse); - } + if (response.errorBody() != null) { + try { + errorResponse = OBJECT_MAPPER.readValue(response.errorBody().string(), ErrorResponse.class); + } catch (IOException e) { + errorResponse.setMessage(e.getMessage()); throw new DVCException(httpResponseCode, errorResponse); } + throw new DVCException(httpResponseCode, errorResponse); + } if (response.body() == null) { throw new DVCException(httpResponseCode, errorResponse); @@ -188,7 +222,7 @@ private T getResponse(Call call) throws DVCException { return response.body(); } else { if (httpResponseCode == HttpResponseCode.UNAUTHORIZED) { - errorResponse.setMessage("API Key is unauthorized"); + errorResponse.setMessage("Invalid sdk key"); } else if (!response.message().equals("")) { try { errorResponse = OBJECT_MAPPER.readValue(response.message(), ErrorResponse.class); From 24fb750cf7b7aff87e09b703c5f0a147284913a2 Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Fri, 26 May 2023 12:03:39 -0700 Subject: [PATCH 05/15] fixing retries/error handling for the test harness --- .../sdk/server/cloud/api/DVCCloudClient.java | 23 ++++++++++++------- .../server/common/exception/DVCException.java | 4 ++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java index 7f39b1a7..487a439a 100755 --- a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java +++ b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import retrofit2.Call; import retrofit2.Response; @@ -29,11 +30,11 @@ public DVCCloudClient(String sdkKey) { public DVCCloudClient(String sdkKey, DVCCloudOptions options) { if(sdkKey == null || sdkKey.equals("")) { - throw new IllegalArgumentException("Missing sdk key! Call initialize with a valid sdk key"); + throw new IllegalArgumentException("Missing environment key! Call initialize with a valid environment key"); } if(!isValidServerKey(sdkKey)) { - throw new IllegalArgumentException("Invalid sdk key provided. Please call initialize with a valid server sdk key"); + throw new IllegalArgumentException("Invalid environment key provided. Please call initialize with a valid server environment key"); } this.dvcOptions = options; @@ -96,7 +97,7 @@ public Variable variable(User user, String key, T defaultValue) throws DV try { Call response = api.getVariableByKey(user, key, dvcOptions.getEnableEdgeDB()); - variable = getResponse(response); + variable = getResponseWithRetries(response, 5); if (variable.getType() != variableType) { throw new IllegalArgumentException("Variable type mismatch, returning default value"); } @@ -143,7 +144,7 @@ public Map allVariables(User user) throws DVCException { public void track(User user, Event event) throws DVCException { validateUser(user); - if (event == null || event.getType().equals("")) { + if (event == null || event.getType() == null || event.getType().equals("")) { throw new IllegalArgumentException("Invalid Event"); } @@ -158,6 +159,7 @@ public void track(User user, Event event) throws DVCException { private T getResponseWithRetries(Call call, int maxRetries) throws DVCException { + // attempt 0 is the initial request, attempt > 0 are all retries int attempt = 0; do { try { @@ -166,12 +168,12 @@ private T getResponseWithRetries(Call call, int maxRetries) throws DVCExc attempt++; // if out of retries or this is an unauthorized error, throw up exception - if (attempt >= maxRetries || e.getHttpResponseCode() == HttpResponseCode.UNAUTHORIZED) { + if (attempt > maxRetries || !e.isRetryable()) { throw e; } try { - // sleep for a bit before retrying + // exponential backoff long waitIntervalMS = (long) (10 * Math.pow(2, attempt)); Thread.sleep(waitIntervalMS); } catch (InterruptedException ex) { @@ -181,9 +183,9 @@ private T getResponseWithRetries(Call call, int maxRetries) throws DVCExc // prep the call for a retry call = call.clone(); } - }while (attempt < maxRetries); + }while (attempt <= maxRetries); - // getting here should not be possible + // getting here should not happen, but is technically possible ErrorResponse errorResponse = ErrorResponse.builder().build(); errorResponse.setMessage("Out of retry attempts"); throw new DVCException(HttpResponseCode.SERVER_ERROR, errorResponse); @@ -196,7 +198,12 @@ private T getResponse(Call call) throws DVCException { try { response = call.execute(); + } catch(MismatchedInputException mie) { + // got a badly formatted JSON response from the server + errorResponse.setMessage(mie.getMessage()); + throw new DVCException(HttpResponseCode.NOT_FOUND, errorResponse); } catch (IOException e) { + // issues reaching the server or reading the response errorResponse.setMessage(e.getMessage()); throw new DVCException(HttpResponseCode.byCode(500), errorResponse); } diff --git a/src/main/java/com/devcycle/sdk/server/common/exception/DVCException.java b/src/main/java/com/devcycle/sdk/server/common/exception/DVCException.java index 766c0fd8..98bb658b 100644 --- a/src/main/java/com/devcycle/sdk/server/common/exception/DVCException.java +++ b/src/main/java/com/devcycle/sdk/server/common/exception/DVCException.java @@ -15,4 +15,8 @@ public DVCException(HttpResponseCode httpResponseCode, ErrorResponse errorRespon this.httpResponseCode = httpResponseCode; this.errorResponse = errorResponse; } + + public boolean isRetryable() { + return httpResponseCode.code() >= HttpResponseCode.SERVER_ERROR.code(); + } } From b842eafc684283925dabe9209c9621f1c0636167 Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Fri, 26 May 2023 14:38:48 -0700 Subject: [PATCH 06/15] adding retry for loading local bucketing config --- .../managers/EnvironmentConfigManager.java | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java index 8bac325e..3db6564d 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java @@ -52,7 +52,7 @@ private void setupScheduler() { public void run() { try { getConfig(); - } catch (DVCException | JsonProcessingException e) { + } catch (DVCException e) { e.printStackTrace(); } } @@ -65,14 +65,47 @@ public boolean isConfigInitialized() { return config != null; } - private ProjectConfig getConfig() throws DVCException, JsonProcessingException { + private ProjectConfig getConfig() throws DVCException { Call config = this.configApiClient.getConfig(this.sdkKey, this.configETag); - - this.config = getConfigResponse(config); + System.out.println("Retrieving Config: " + this.sdkKey); + this.config = getResponseWithRetries(config, 1); return this.config; } - private ProjectConfig getConfigResponse(Call call) throws DVCException, JsonProcessingException { + private ProjectConfig getResponseWithRetries(Call call, int maxRetries) throws DVCException { + // attempt 0 is the initial request, attempt > 0 are all retries + int attempt = 0; + do { + try { + return getConfigResponse(call); + } catch (DVCException e) { + attempt++; + + // if out of retries or this is an unauthorized error, throw up exception + if (attempt > maxRetries || !e.isRetryable()) { + throw e; + } + + try { + // exponential backoff + long waitIntervalMS = (long) (10 * Math.pow(2, attempt)); + Thread.sleep(waitIntervalMS); + } catch (InterruptedException ex) { + // no-op + } + + // prep the call for a retry + call = call.clone(); + } + }while (attempt <= maxRetries); + + // getting here should not happen, but is technically possible + ErrorResponse errorResponse = ErrorResponse.builder().build(); + errorResponse.setMessage("Out of retry attempts"); + throw new DVCException(HttpResponseCode.SERVER_ERROR, errorResponse); + } + + private ProjectConfig getConfigResponse(Call call) throws DVCException { ErrorResponse errorResponse = ErrorResponse.builder().build(); Response response; @@ -97,7 +130,8 @@ private ProjectConfig getConfigResponse(Call call) throws DVCExce System.out.printf("Unable to parse config with etag: %s. Using cache, etag %s%n", currentETag, this.configETag); return this.config; } else { - throw e; + errorResponse.setMessage(e.getMessage()); + throw new DVCException(HttpResponseCode.SERVER_ERROR, errorResponse); } } this.configETag = currentETag; From 1161a4f1b0fc01369f0f00eca891dc91ccf16eb9 Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Mon, 29 May 2023 07:55:52 -0700 Subject: [PATCH 07/15] Fix error message to match test harness expectations --- .../java/com/devcycle/sdk/server/local/api/DVCLocalClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java b/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java index e960bc5a..8a04b5d4 100755 --- a/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java +++ b/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java @@ -37,7 +37,7 @@ public DVCLocalClient(String sdkKey, DVCLocalOptions dvcOptions) { throw new IllegalArgumentException("Missing sdk key! Call initialize with a valid sdk key"); } if(!isValidServerKey(sdkKey)) { - throw new IllegalArgumentException("Invalid sdk key provided. Please call initialize with a valid server sdk key"); + throw new IllegalArgumentException("Invalid SDK key provided. Please call initialize with a valid server SDK key"); } if(!isValidRuntime()){ From c7e156a9b547f02ddf239bd530a4017e31a0538c Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Mon, 29 May 2023 09:19:35 -0700 Subject: [PATCH 08/15] more fixes to initialization workflow --- .../sdk/server/local/api/DVCLocalClient.java | 2 +- .../managers/EnvironmentConfigManager.java | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java b/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java index 8a04b5d4..ca58695a 100755 --- a/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java +++ b/src/main/java/com/devcycle/sdk/server/local/api/DVCLocalClient.java @@ -34,7 +34,7 @@ public DVCLocalClient(String sdkKey) { public DVCLocalClient(String sdkKey, DVCLocalOptions dvcOptions) { if(sdkKey == null || sdkKey.equals("")) { - throw new IllegalArgumentException("Missing sdk key! Call initialize with a valid sdk key"); + throw new IllegalArgumentException("Missing SDK key! Call initialize with a valid SDK key"); } if(!isValidServerKey(sdkKey)) { throw new IllegalArgumentException("Invalid SDK key provided. Please call initialize with a valid server SDK key"); diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java index 3db6564d..45eeba87 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java @@ -1,10 +1,5 @@ package com.devcycle.sdk.server.local.managers; -import java.io.IOException; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - import com.devcycle.sdk.server.common.api.IDVCApi; import com.devcycle.sdk.server.common.exception.DVCException; import com.devcycle.sdk.server.common.model.ErrorResponse; @@ -13,12 +8,17 @@ import com.devcycle.sdk.server.local.api.DVCLocalApiClient; import com.devcycle.sdk.server.local.bucketing.LocalBucketing; import com.devcycle.sdk.server.local.model.DVCLocalOptions; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; - import retrofit2.Call; import retrofit2.Response; +import java.io.IOException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + public final class EnvironmentConfigManager { private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -53,7 +53,7 @@ public void run() { try { getConfig(); } catch (DVCException e) { - e.printStackTrace(); + System.out.println("Failed to load config: " + e.getMessage()); } } }; @@ -67,7 +67,6 @@ public boolean isConfigInitialized() { private ProjectConfig getConfig() throws DVCException { Call config = this.configApiClient.getConfig(this.sdkKey, this.configETag); - System.out.println("Retrieving Config: " + this.sdkKey); this.config = getResponseWithRetries(config, 1); return this.config; } @@ -111,6 +110,11 @@ private ProjectConfig getConfigResponse(Call call) throws DVCExce try { response = call.execute(); + } catch(JsonParseException badJsonExc) { + // Got a valid status code but the response body was not valid json, + // need to ignore this attempt and let the polling retry + errorResponse.setMessage(badJsonExc.getMessage()); + throw new DVCException(HttpResponseCode.byCode(204), errorResponse); } catch (IOException e) { errorResponse.setMessage(e.getMessage()); throw new DVCException(HttpResponseCode.byCode(500), errorResponse); From 572632b26de6d718ec0967a591d5e1b1d3511aae Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Mon, 29 May 2023 15:59:32 -0700 Subject: [PATCH 09/15] cleaning up polling and errors --- .../sdk/server/cloud/api/DVCCloudClient.java | 2 +- .../server/common/model/HttpResponseCode.java | 2 ++ .../managers/EnvironmentConfigManager.java | 26 ++++++++++++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java index 487a439a..47eda305 100755 --- a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java +++ b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java @@ -168,7 +168,7 @@ private T getResponseWithRetries(Call call, int maxRetries) throws DVCExc attempt++; // if out of retries or this is an unauthorized error, throw up exception - if (attempt > maxRetries || !e.isRetryable()) { + if (!e.isRetryable() || attempt > maxRetries) { throw e; } diff --git a/src/main/java/com/devcycle/sdk/server/common/model/HttpResponseCode.java b/src/main/java/com/devcycle/sdk/server/common/model/HttpResponseCode.java index 31b29f43..6ccae082 100644 --- a/src/main/java/com/devcycle/sdk/server/common/model/HttpResponseCode.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/HttpResponseCode.java @@ -6,9 +6,11 @@ public enum HttpResponseCode { OK(200), ACCEPTED(201), + NO_CONTENT(204), NOT_MODIFIED(304), BAD_REQUEST(400), UNAUTHORIZED(401), + FORBIDDEN(403), NOT_FOUND(404), SERVER_ERROR(500); diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java index 45eeba87..d1221fe1 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java @@ -33,6 +33,7 @@ public final class EnvironmentConfigManager { private String sdkKey; private int pollingIntervalMS; + private boolean pollingEnabled = true; public EnvironmentConfigManager(String sdkKey, LocalBucketing localBucketing, DVCLocalOptions options) { this.sdkKey = sdkKey; @@ -51,7 +52,9 @@ private void setupScheduler() { Runnable getConfigRunnable = new Runnable() { public void run() { try { - getConfig(); + if(pollingEnabled){ + getConfig(); + } } catch (DVCException e) { System.out.println("Failed to load config: " + e.getMessage()); } @@ -78,10 +81,11 @@ private ProjectConfig getResponseWithRetries(Call call, int maxRe try { return getConfigResponse(call); } catch (DVCException e) { + attempt++; // if out of retries or this is an unauthorized error, throw up exception - if (attempt > maxRetries || !e.isRetryable()) { + if ( !e.isRetryable() || attempt > maxRetries) { throw e; } @@ -96,7 +100,7 @@ private ProjectConfig getResponseWithRetries(Call call, int maxRe // prep the call for a retry call = call.clone(); } - }while (attempt <= maxRetries); + }while (attempt <= maxRetries && pollingEnabled); // getting here should not happen, but is technically possible ErrorResponse errorResponse = ErrorResponse.builder().build(); @@ -114,7 +118,7 @@ private ProjectConfig getConfigResponse(Call call) throws DVCExce // Got a valid status code but the response body was not valid json, // need to ignore this attempt and let the polling retry errorResponse.setMessage(badJsonExc.getMessage()); - throw new DVCException(HttpResponseCode.byCode(204), errorResponse); + throw new DVCException(HttpResponseCode.NO_CONTENT, errorResponse); } catch (IOException e) { errorResponse.setMessage(e.getMessage()); throw new DVCException(HttpResponseCode.byCode(500), errorResponse); @@ -147,6 +151,9 @@ private ProjectConfig getConfigResponse(Call call) throws DVCExce if (response.errorBody() != null) { try { errorResponse = OBJECT_MAPPER.readValue(response.errorBody().string(), ErrorResponse.class); + } catch (JsonProcessingException e) { + errorResponse.setMessage("Unable to parse error response: " + e.getMessage()); + throw new DVCException(httpResponseCode, errorResponse); } catch (IOException e) { errorResponse.setMessage(e.getMessage()); throw new DVCException(httpResponseCode, errorResponse); @@ -154,8 +161,10 @@ private ProjectConfig getConfigResponse(Call call) throws DVCExce throw new DVCException(httpResponseCode, errorResponse); } - if (httpResponseCode == HttpResponseCode.UNAUTHORIZED) { + if (httpResponseCode == HttpResponseCode.UNAUTHORIZED || httpResponseCode == HttpResponseCode.FORBIDDEN) { + // SDK Key is no longer authorized or now blocked, stop polling for configs errorResponse.setMessage("API Key is unauthorized"); + stopPolling(); } else if (!response.message().equals("")) { try { errorResponse = OBJECT_MAPPER.readValue(response.message(), ErrorResponse.class); @@ -169,7 +178,12 @@ private ProjectConfig getConfigResponse(Call call) throws DVCExce } } - public void cleanup() { + private void stopPolling() { + pollingEnabled = false; scheduler.shutdown(); } + + public void cleanup() { + stopPolling(); + } } \ No newline at end of file From c877f64b1c89e332f5c83c2be8ed9736d26c3efc Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Mon, 29 May 2023 16:00:23 -0700 Subject: [PATCH 10/15] removed unnecessary threading and error reporting --- .../local/managers/EventQueueManager.java | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java index 68261e44..c3a08ae8 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java @@ -49,7 +49,7 @@ private void setupScheduler() { try { flushEvents(); } catch (Exception e) { - e.printStackTrace(); + System.out.println("Error flushing events: " + e.getMessage()); } }; @@ -59,7 +59,7 @@ private void setupScheduler() { /** * Flush events in queue to DevCycle Events API. Requeue events if flush fails */ - public void flushEvents() throws Exception { + public synchronized void flushEvents() throws Exception { if (isFlushingEvents) return; if (sdkKey == null || sdkKey.equals("")) { @@ -116,23 +116,15 @@ public void queueAggregateEvent(Event event, BucketedUserConfig bucketedConfig) } private void publishEvents(String sdkKey, FlushPayload flushPayload) throws InterruptedException { - Thread publishEventsThread = new Thread(new Runnable() { - @Override - public void run() { - Call response = eventsApiClient.publishEvents(EventsBatch.builder().batch(flushPayload.records).build()); - int responseCode = getResponse(response); - - if (responseCode == 201) { - localBucketing.onPayloadSuccess(sdkKey, flushPayload.payloadId); - } else { - System.out.printf("DVC Error Publishing Events: %d%n", responseCode); - localBucketing.onPayloadFailure(sdkKey, flushPayload.payloadId, responseCode >= 500); - } - } - }); + Call response = eventsApiClient.publishEvents(EventsBatch.builder().batch(flushPayload.records).build()); + int responseCode = getResponse(response); - publishEventsThread.start(); - publishEventsThread.join(); + if (responseCode == 201) { + localBucketing.onPayloadSuccess(sdkKey, flushPayload.payloadId); + } else { + System.out.printf("DVC Error Publishing Events: %d%n", responseCode); + localBucketing.onPayloadFailure(sdkKey, flushPayload.payloadId, responseCode >= 500); + } } private int getResponse(Call call) { From 8f668a3ba401e3e7db08150f8fa85ef0edaceed7 Mon Sep 17 00:00:00 2001 From: chris-hoefgen Date: Tue, 30 May 2023 08:16:22 -0700 Subject: [PATCH 11/15] use new status code for bad json responses --- .../java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java index 47eda305..d58dda0a 100755 --- a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java +++ b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java @@ -201,7 +201,7 @@ private T getResponse(Call call) throws DVCException { } catch(MismatchedInputException mie) { // got a badly formatted JSON response from the server errorResponse.setMessage(mie.getMessage()); - throw new DVCException(HttpResponseCode.NOT_FOUND, errorResponse); + throw new DVCException(HttpResponseCode.NO_CONTENT, errorResponse); } catch (IOException e) { // issues reaching the server or reading the response errorResponse.setMessage(e.getMessage()); From 71c35dfd0d5395cb545f4c31bbca4633584fc5e8 Mon Sep 17 00:00:00 2001 From: Chris Hoefgen <58341173+chris-hoefgen@users.noreply.github.com> Date: Wed, 31 May 2023 12:24:54 -0700 Subject: [PATCH 12/15] Update src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java Co-authored-by: Jonathan Norris --- .../sdk/server/local/managers/EnvironmentConfigManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java index d1221fe1..a3fc8cc4 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java @@ -184,6 +184,6 @@ private void stopPolling() { } public void cleanup() { - stopPolling(); + stopPolling(); } } \ No newline at end of file From 0dcab57ba819d86adce4e66dd2dde9a0dfd8ffbc Mon Sep 17 00:00:00 2001 From: Chris Hoefgen <58341173+chris-hoefgen@users.noreply.github.com> Date: Wed, 31 May 2023 12:25:32 -0700 Subject: [PATCH 13/15] Update src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java Co-authored-by: Jonathan Norris --- .../sdk/server/local/managers/EnvironmentConfigManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java index a3fc8cc4..576c49bb 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java @@ -100,7 +100,7 @@ private ProjectConfig getResponseWithRetries(Call call, int maxRe // prep the call for a retry call = call.clone(); } - }while (attempt <= maxRetries && pollingEnabled); + } while (attempt <= maxRetries && pollingEnabled); // getting here should not happen, but is technically possible ErrorResponse errorResponse = ErrorResponse.builder().build(); From 120ecd8d33404b744486eb777097edeccbb94529 Mon Sep 17 00:00:00 2001 From: Chris Hoefgen <58341173+chris-hoefgen@users.noreply.github.com> Date: Wed, 31 May 2023 12:25:42 -0700 Subject: [PATCH 14/15] Update src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java Co-authored-by: Jonathan Norris --- .../sdk/server/local/managers/EnvironmentConfigManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java index 576c49bb..b25820ce 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java @@ -52,7 +52,7 @@ private void setupScheduler() { Runnable getConfigRunnable = new Runnable() { public void run() { try { - if(pollingEnabled){ + if (pollingEnabled) { getConfig(); } } catch (DVCException e) { From 71a599aa46747d5d5db2d63ce096afde6386d8dd Mon Sep 17 00:00:00 2001 From: Chris Hoefgen <58341173+chris-hoefgen@users.noreply.github.com> Date: Wed, 31 May 2023 12:30:41 -0700 Subject: [PATCH 15/15] Update src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java Co-authored-by: Jonathan Norris --- .../java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java index d58dda0a..19316300 100755 --- a/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java +++ b/src/main/java/com/devcycle/sdk/server/cloud/api/DVCCloudClient.java @@ -229,7 +229,7 @@ private T getResponse(Call call) throws DVCException { return response.body(); } else { if (httpResponseCode == HttpResponseCode.UNAUTHORIZED) { - errorResponse.setMessage("Invalid sdk key"); + errorResponse.setMessage("Invalid SDK Key"); } else if (!response.message().equals("")) { try { errorResponse = OBJECT_MAPPER.readValue(response.message(), ErrorResponse.class);