-
Notifications
You must be signed in to change notification settings - Fork 135
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
Fix race condition in ApiClient::invokeApi() #1580
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Does this "fix" mean we can't use the ApiClient concurrently anymore? We have a lot of code doing concurrent requests to the Okta API (using parallel streams mostly). |
@clementdenis Here's a brief description of the original issue this PR was meant to fix:
|
I am also concerned about the performance implications raised by @clementdenis. Setting Storing diff --git a/api/src/main/resources/custom_templates/ApiClient.mustache b/api/src/main/resources/custom_templates/ApiClient.mustache
index f0318f8c6da..ee367549858 100644
--- a/api/src/main/resources/custom_templates/ApiClient.mustache
+++ b/api/src/main/resources/custom_templates/ApiClient.mustache
@@ -156,8 +156,8 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
private Cache<String, Object> cache;
- private int statusCode;
- private Map<String, List<String>> responseHeaders;
+ private int synchronizedStatusCode;
+ private Map<String, List<String>> synchronizedResponseHeaders;
private DateFormat dateFormat;
@@ -316,16 +316,21 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
*
* @return Status code
*/
- public int getStatusCode() {
- return statusCode;
+ public synchronized int getStatusCode() {
+ return synchronizedStatusCode;
+ }
+
+ private synchronized void setStatusCodeAndResponseHeaders(int statusCode, Map<String, List<String>> responseHeaders) {
+ this.synchronizedStatusCode = statusCode;
+ this.synchronizedResponseHeaders = responseHeaders;
}
/**
* Gets the response headers of the previous request
* @return Response headers
*/
- public Map<String, List<String>> getResponseHeaders() {
- return responseHeaders;
+ public synchronized Map<String, List<String>> getResponseHeaders() {
+ return synchronizedResponseHeaders;
}
/**
@@ -879,7 +884,7 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
* @throws IOException IO exception
*/
@SuppressWarnings("unchecked")
- public <T> T deserialize(CloseableHttpResponse response, TypeReference<T> valueType) throws ApiException, IOException, ParseException {
+ public <T> T deserialize(CloseableHttpResponse response, Map<String, List<String>> responseHeaders, TypeReference<T> valueType) throws ApiException, IOException, ParseException {
if (valueType == null) {
return null;
}
@@ -1053,14 +1058,16 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
}
protected <T> T processResponse(CloseableHttpResponse response, TypeReference<T> returnType) throws ApiException, IOException, ParseException {
- statusCode = response.getCode();
+ int statusCode = response.getCode();
if (statusCode == HttpStatus.SC_NO_CONTENT) {
+ setStatusCodeAndResponseHeaders(statusCode, new Map<String, List<String>>());
return null;
}
- responseHeaders = transformResponseHeaders(response.getHeaders());
+ Map<String, List<String>> responseHeaders = transformResponseHeaders(response.getHeaders());
+ setStatusCodeAndResponseHeaders(statusCode, responseHeaders);
if (isSuccessfulStatus(statusCode)) {
- return this.deserialize(response, returnType);
+ return this.deserialize(response, responseHeaders, returnType);
} else {
String message = EntityUtils.toString(response.getEntity());
throw new ApiException(message, statusCode, responseHeaders, message);
@@ -1087,7 +1094,7 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
* @return The response body in type of string
* @throws ApiException API exception
*/
- public synchronized <T> T invokeAPI(
+ public <T> T invokeAPI(
String path,
String method,
List<Pair> queryParams, |
This issue was actually fixed in OpenAPI generator v7.8.0 in this PR (commit), which is the version you are supposed to be using => you just need to merge the changes / fixes from base template files in your custom template. The problem is that your customized mustache templates have diverged from original in a way that it seems very hard reconcile ... Maybe it's time to start from scratch again with the base template files, and introduce your customization in a maintainable way (mostly by making sure formatting isn't broken like in current template files). |
Thanks for pointing out the upstream fix for this issue @clementdenis. I had missed that before. The approach in upstream seems reasonable to me—these methods should be marked deprecated, and the concurrent hash map based on thread id is the simplest fix to maintain functional parity. The issue with the custom templates diverging notwithstanding, here would be the smallest diff to pick up that patch and apply it. diff --git a/api/src/main/resources/custom_templates/ApiClient.mustache b/api/src/main/resources/custom_templates/ApiClient.mustache
index f0318f8c6da..3cc0dbb1027 100644
--- a/api/src/main/resources/custom_templates/ApiClient.mustache
+++ b/api/src/main/resources/custom_templates/ApiClient.mustache
@@ -69,6 +69,7 @@ import java.util.Arrays;
import java.util.ArrayList;
import java.util.Date;
import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -156,8 +157,8 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
private Cache<String, Object> cache;
- private int statusCode;
- private Map<String, List<String>> responseHeaders;
+ private Map<Long, Integer> lastStatusCodeByThread = new ConcurrentHashMap<>();
+ private Map<Long, Map<String, List<String>>> lastResponseHeadersByThread = new ConcurrentHashMap<>();
private DateFormat dateFormat;
@@ -316,16 +317,18 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
*
* @return Status code
*/
+ @Deprecated
public int getStatusCode() {
- return statusCode;
+ return lastStatusCodeByThread.get(Thread.currentThread().getId());
}
/**
* Gets the response headers of the previous request
* @return Response headers
*/
+ @Deprecated
public Map<String, List<String>> getResponseHeaders() {
- return responseHeaders;
+ return lastResponseHeadersByThread.get(Thread.currentThread().getId());
}
/**
@@ -918,6 +921,7 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
java.util.Scanner s = new java.util.Scanner(entity.getContent()).useDelimiter("\\A");
return (T) (s.hasNext() ? s.next() : "");
} else {
+ Map<String, List<String>> responseHeaders = transformResponseHeaders(response.getHeaders());
throw new ApiException(
"Deserialization for content type '" + mimeType + "' not supported for type '" + valueType + "'",
response.getCode(),
@@ -1053,12 +1057,15 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
}
protected <T> T processResponse(CloseableHttpResponse response, TypeReference<T> returnType) throws ApiException, IOException, ParseException {
- statusCode = response.getCode();
+ int statusCode = response.getCode();
+ lastStatusCodeByThread.put(Thread.currentThread().getId(), statusCode);
if (statusCode == HttpStatus.SC_NO_CONTENT) {
return null;
}
- responseHeaders = transformResponseHeaders(response.getHeaders());
+ Map<String, List<String>> responseHeaders = transformResponseHeaders(response.getHeaders());
+ lastResponseHeadersByThread.put(Thread.currentThread().getId(), responseHeaders);
+
if (isSuccessfulStatus(statusCode)) {
return this.deserialize(response, returnType);
} else {
@@ -1087,7 +1094,7 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
* @return The response body in type of string
* @throws ApiException API exception
*/
- public synchronized <T> T invokeAPI(
+ public <T> T invokeAPI(
String path,
String method,
List<Pair> queryParams,
I tested above diff with:
|
Issue(s)
OKTA-831355
Description
Category
Signoff