Adding Kroto Plus to a project with Springfox Swagger2 causes infinite loop before start
See original GitHub issueArrangement:
Project with a Spring Web MVC @RestController
that uses a protobuf generated class as an input, and calls the same server code as the @GrpcSpringService
(using https://github.com/yidongnan/grpc-spring-boot-starter). Kroto Plus configured to generate builders, message extensions, and coroutines for the service and messages defined in the proto files.
Kroto Plus version 0.6.0-SNAPSHOT
was used
/*
* Copyright (C) 2019 Electronic Arts Inc. All rights reserved.
*/
package com.ea.poutine.roolz.rlzservice
import com.ea.p.r.r.api.RController
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import springfox.documentation.builders.PathSelectors
import springfox.documentation.builders.RequestHandlerSelectors
import springfox.documentation.spi.DocumentationType
import springfox.documentation.spring.web.plugins.Docket
import springfox.documentation.swagger2.annotations.EnableSwagger2
@SpringBootApplication
@EnableSwagger2
// import the specific ones we want Swagger exposed for, rather than let it wander around scanning controllers in the packages
@ComponentScan(basePackageClasses = [RController::class])
class Swagger2SpringBoot {
companion object {
lateinit var appContext: ConfigurableApplicationContext
@JvmStatic
fun main(args: Array<String>) {
appContext = SpringApplication.run(Swagger2SpringBoot::class.java, *args)
}
}
@Bean
fun api(): Docket {
return Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
}
}
@SpringBootApplication
@ComponentScan("com.ea.p")
@EnableDiscoveryClient
@EnableConfigurationProperties
class RApp {
companion object {
private val logger = KotlinLogging.logger { }
@JvmStatic
fun main(args: Array<String>) {
logger.debug { "Running RApplication with args: ${Klaxon().toJsonString(args)}" }
SpringApplication.run(RApp::class.java, *args)
}
@Bean
fun init() = CommandLineRunner {
logger.debug { "init called" }
}
@Bean
fun userProvider(): UserProvider {
return ThreadLocalUserProvider()
}
}
}
@RestController
@RequestMapping("/v3/r")
class RController @Autowired constructor(
private val service: RService,
fConverter: FConverter,
private val timers: Timers,
private val rResultConverter: rResultConverter,
private val container: KContainer
) {
private val bEvaluator = BEvaluator(service, fConverter)
private val logger = KotlinLogging.logger { }
@PostMapping(value = ["/tenant/{tenantId}/entity/{entityId}"],
consumes = ["application/x-protobuf", "application/json"],
produces = ["application/x-protobuf", "application/json"])
fun ruleResultRequest(
@PathVariable("tenantId") tenantId: String,
@PathVariable("entityId") entityId: String,
@RequestBody eRequests: BERequest
): ResponseEntity<BRResults> = runBlocking {
val tags = mapOf("actorKey" to PAKey(entityId, tenantId).toString())
withLoggingContext(tags) {
respond(
// rainbow brackets plugin for IntelliJ is useful here
logicFunc = {
ruleResultConverter.toDto(
timers.recordAsync(MetricTypes.RRequestGrpc.id, tags) {
async {
bEvaluator.execute(evalRequests, entityId, tenantId)
}
}
)
},
afterFunc = { logger.info { "Finished all requests in list for $tenantId $entityId" } }
) }
}
@DeleteMapping("/tenant/{tenantId}/entity/{entityId}")
fun deleteSession(
@PathVariable("tenantId") tenantId: String,
@PathVariable("entityId") entityId: String
): ResponseEntity<Boolean> {
// this block wraps with try/catch with boilerplate logging, and is from the api-common library
// returns an ResponseEntity.Ok(this return value as body) when it exits the respond block
return respond(
logicFunc = { service.deleteSession(PAKey(entityId, tenantId)) },
afterFunc = { logger.info("Completed REST API to gRPC call for deleteSession for $tenantId $entityId") })
}
@GetMapping(value = ["/r"],
produces = ["application/json"])
fun getAllRNames(): ResponseEntity<List<String>> = runBlocking {
val rNames = container.getRNames()
val tags = mapOf("rNames" to rNames.toString())
withLoggingContext(tags) {
respond {
logger.debug { "Finished returning all the rnames" }
rNames
}
}
}
}
mockServices:
- filter:
includePath:
- com/ea/p/*
implementAsObject: true
generateServiceList: true
serviceListPackage: com.ea.p.r
serviceListName: MockRServices
protoBuilders:
- filter:
excludePath:
- google/*
includePath:
- com/ea/p/*
unwrapBuilders: true
useDslMarkers: true
grpcCoroutines: []
grpcStubExts:
- supportCoroutines: true
extendableMessages:
- filter:
includePath:
- com/ea/p/r/*
syntax = "proto3";
import "google/protobuf/any.proto";
package com.ea.p.r;
option java_package = "com.ea.p.r.rp";
option java_multiple_files = true;
service RService {
rpc bREvaluation (BERequest) returns (BRResults);
rpc deleteSession (DeleteSessionRequest) returns (DeleteSessionResponse);
}
message BERequest {
string tenantId = 1;
string entityId = 2;
repeated ERequestV3 eRequests = 3;
Service service = 4;
}
message BRResults {
repeated EResponseV3 responses = 1;
}
message EResponseV3 {
string associatedRequestId = 1;
repeated RResultV3 rResults = 3;
}
message RResultV3 {
string rId =1;
bool state = 2;
map<string, string> results = 3;
}
message ERequestV3 {
string requestId = 1;
repeated FDto f = 3;
repeated string rIdFilter = 4;
}
message Service {
string service = 1;
string key = 2;
}
message FactDto {
string factId = 1;
google.protobuf.Any value = 2;
}
// This is a possible FDto value.
message FJsonValue {
string jsonValue = 1; // The string with the JSON representation
enum FType { // The supported types the JSON representation can be mapped to.
STRING = 0;
INTEGER = 1;
BOOLEAN = 3;
DOUBLE = 4;
LIST_INTEGER = 5;
LIST_STRING = 6;
STATS_DATA = 7;
}
FType fType = 2;
}
message DeleteSessionRequest {
string tenantId = 1;
string entityId = 2;
}
message DeleteSessionResponse {
bool success = 1;
}
Expected: Works the same as before, starts up fine with a Swagger UI.
Actual: Fails to start, gets lost in the gRPC generated code.
Workaround: Remove Kroto Plus, observe that it starts up again.
Issue Analytics
- State:
- Created 4 years ago
- Comments:7 (4 by maintainers)
Top Results From Across the Web
Infinity loop when starting spring boot server #2907 - GitHub
im getting an infinite loop when starting up. @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .
Read more >kroto-plus - Bountysource
When you take a heap dump before exiting the runBlocking-block, ... Adding Kroto Plus to a project with Springfox Swagger2 causes infinite loop...
Read more >java - Spring Boot 2.6.0 / Spring fox 3 - Failed to start bean ...
This problem's caused by a bug in Springfox. It's making an assumption about how Spring MVC is set up that doesn't always hold...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Sorry, I haven’t had a chance to try this yet. When I disabled the unpackBuilders option I realized I had to first remove the code that used it to even get back to where the error was. I’m just about done with that work, so I’ll be able to cleanly add Kroto Plus back in afterwards in a test branch. Unfortunately it is lower priority than a few other tasks on my plate at work, so it may take a few days to get back to.
Thanks for the help debugging the issue. When you do get a chance to get back to it, you can do a static import of the builder objects so you don’t have to update the usages in your code.