Skip to content

Commit 3eec9c8

Browse files
committed
serialize OpenAPI spec parsing to prevent cross-contamination in concurrent environments
1 parent bedfa3b commit 3eec9c8

2 files changed

Lines changed: 25 additions & 4 deletions

File tree

modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
import io.swagger.parser.OpenAPIParser;
2323
import io.swagger.v3.core.util.Json;
2424
import io.swagger.v3.core.util.Yaml;
25+
import io.swagger.v3.oas.models.OpenAPI;
2526
import io.swagger.v3.parser.OpenAPIResolver;
2627
import io.swagger.v3.parser.OpenAPIV3Parser;
2728
import io.swagger.v3.parser.core.models.AuthorizationValue;
2829
import io.swagger.v3.parser.core.models.ParseOptions;
30+
import io.swagger.v3.parser.core.models.SwaggerParseResult;
2931
import lombok.Setter;
3032
import org.apache.commons.io.FileUtils;
3133
import org.apache.commons.io.FilenameUtils;
@@ -1046,9 +1048,12 @@ private String calculateInputSpecHash(String inputSpec) {
10461048
final URL remoteUrl = inputSpecRemoteUrl();
10471049
final List<AuthorizationValue> authorizationValues = AuthParser.parse(this.auth);
10481050

1051+
final SwaggerParseResult parseResult;
1052+
synchronized (CodegenConfigurator.SPEC_PARSE_LOCK) {
1053+
parseResult = new OpenAPIParser().readLocation(remoteUrl == null ? inputSpec : remoteUrl.toString(), authorizationValues, parseOptions);
1054+
}
10491055
return Hashing.sha256().hashBytes(
1050-
new OpenAPIParser().readLocation(remoteUrl == null ? inputSpec : remoteUrl.toString(), authorizationValues, parseOptions)
1051-
.getOpenAPI().toString().getBytes(StandardCharsets.UTF_8)
1056+
parseResult.getOpenAPI().toString().getBytes(StandardCharsets.UTF_8)
10521057
).toString();
10531058
}
10541059

@@ -1137,7 +1142,10 @@ private Path createCollapsedSpec() throws MojoExecutionException {
11371142
parseOptions.setResolve(true);
11381143
final List<AuthorizationValue> authorizationValues = AuthParser.parse(this.auth);
11391144

1140-
final var openApiMerged = new OpenAPIResolver(new OpenAPIV3Parser().readLocation(inputSpec, authorizationValues, parseOptions).getOpenAPI()).resolve();
1145+
final OpenAPI openApiMerged;
1146+
synchronized (CodegenConfigurator.SPEC_PARSE_LOCK) {
1147+
openApiMerged = new OpenAPIResolver(new OpenAPIV3Parser().readLocation(inputSpec, authorizationValues, parseOptions).getOpenAPI()).resolve();
1148+
}
11411149

11421150
// Switch based on JSON or YAML.
11431151
final var extension = inputSpec.toLowerCase(Locale.ROOT).endsWith(".json") ? ".json" : ".yaml";

modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ public class CodegenConfigurator {
5757

5858
public static final Logger LOGGER = LoggerFactory.getLogger(CodegenConfigurator.class);
5959

60+
// swagger-parser's OAS 3.1 dereferencer (OpenAPIDereferencer31) is a singleton with mutable
61+
// instance state, making concurrent spec parsing unsafe. This lock serializes readLocation()
62+
// calls to avoid cross-contamination of OpenAPI objects between concurrent generators (e.g.
63+
// GenerateBatch thread pool or parallel Maven module builds in mvnd).
64+
public static final Object SPEC_PARSE_LOCK = new Object();
65+
6066
private GeneratorSettings.Builder generatorSettingsBuilder = GeneratorSettings.newBuilder();
6167
private WorkflowSettings.Builder workflowSettingsBuilder = WorkflowSettings.newBuilder();
6268

@@ -684,7 +690,14 @@ public Context<?> toContext() {
684690
ParseOptions options = new ParseOptions();
685691
options.setResolve(true);
686692
options.setResolveResponses(true);
687-
SwaggerParseResult result = new OpenAPIParser().readLocation(inputSpec, authorizationValues, options);
693+
// Serialize spec parsing: swagger-parser's OAS 3.1 dereferencer singleton (OpenAPIDereferencer31)
694+
// has mutable instance fields (openAPI, result) written during dereference(). Concurrent calls
695+
// from GenerateBatch threads race on those fields, causing one generator to receive another's
696+
// OpenAPI object. Serializing readLocation() here prevents that cross-contamination.
697+
SwaggerParseResult result;
698+
synchronized (SPEC_PARSE_LOCK) {
699+
result = new OpenAPIParser().readLocation(inputSpec, authorizationValues, options);
700+
}
688701

689702
// TODO: Move custom validations to a separate type as part of a "Workflow"
690703
Set<String> validationMessages = new HashSet<>(null != result.getMessages() ? result.getMessages() : new ArrayList<>());

0 commit comments

Comments
 (0)