Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -11,15 +11,9 @@
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Set;

public class OpenAPIDereferencer31 implements OpenAPIDereferencer {

protected final Set<String> messages = new HashSet<>();

private OpenAPI openAPI;
private SwaggerParseResult result;

public boolean canDereference(DereferencerContext context) {
if (context.openApi != null && context.openApi.getOpenapi().startsWith("3.1")) {
return true;
Expand All @@ -38,8 +32,8 @@ public void dereference(DereferencerContext context, Iterator<OpenAPIDereference
}
}

openAPI = context.openApi;
result = context.swaggerParseResult;
OpenAPI openAPI = context.openApi;
SwaggerParseResult result = context.swaggerParseResult;

if (openAPI == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.swagger.v3.parser.test;

import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static org.junit.Assert.assertEquals;

/**
* Verifies that concurrent parsing of OpenAPI 3.1 specs with resolve=true
* does not cause results to be swapped between threads.
*
* This is a regression test for https://github.com/swagger-api/swagger-parser/issues/2293
* where OpenAPIDereferencer31 (a singleton) stored per-call state in instance fields,
* causing concurrent dereference() calls to overwrite each other's OpenAPI/result objects.
*/
public class OpenAPIDereferencer31ThreadSafetyTest {

private static String makeSpec(String title) {
return "{\n" +
" \"openapi\": \"3.1.0\",\n" +
" \"info\": { \"title\": \"" + title + "\", \"version\": \"1.0.0\" },\n" +
" \"paths\": {},\n" +
" \"components\": {\n" +
" \"schemas\": {\n" +
" \"Item\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": { \"id\": { \"type\": \"string\" } }\n" +
" },\n" +
" \"ItemList\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"items\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": { \"$ref\": \"#/components/schemas/Item\" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}

private static String parseAndGetTitle(String spec) {
ParseOptions options = new ParseOptions();
options.setResolve(true);
options.setResolveFully(false);
SwaggerParseResult result = new OpenAPIV3Parser().readContents(spec, null, options);
return result.getOpenAPI().getInfo().getTitle();
}

@Test
public void concurrentParsingOfDifferentSpecsShouldNotCorruptResults() throws InterruptedException, ExecutionException {
String specA = makeSpec("SpecA");
String specB = makeSpec("SpecB");

int threadCount = 8;
int iterations = 200;
ExecutorService pool = Executors.newFixedThreadPool(threadCount);

try {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < iterations; i++) {
final String spec = (i % 2 == 0) ? specA : specB;
final String expectedTitle = (i % 2 == 0) ? "SpecA" : "SpecB";
futures.add(pool.submit(() -> {
String actual = parseAndGetTitle(spec);
assertEquals("Parsed title should match input spec (concurrent race detected)", expectedTitle, actual);
return actual;
}));
}

for (Future<String> future : futures) {
future.get();
}
} finally {
pool.shutdown();
}
}
}
Loading