question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Adding Kroto Plus to a project with Springfox Swagger2 causes infinite loop before start

See original GitHub issue

Arrangement: 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:open
  • Created 4 years ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
mattdkerrcommented, Dec 18, 2019

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.

0reactions
marcoferrercommented, Dec 19, 2019

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.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found