Infinite loop in history `findVersionsBetween` query with spring boot?

85 views
Skip to first unread message

Charles Munat

unread,
Jul 25, 2022, 12:59:56 AM7/25/22
to Ebean ORM
I have this model:

@Entity
@History
class Title (
    var description: String,
    var ownerName: String
): Model()
{
    @Id
    @GeneratedValue
    val id: Long = 0

    override fun toString(): String {
        return "Title(description='$description', ownerName='$ownerName', id=$id)"
    }
}

Then I try this in the controller:

@GetMapping("/{id}")
fun getTitle(@PathVariable id: Long): ResponseEntity<List<Version<Title>>> {
    val end = Timestamp.valueOf("2022-07-25 16:10:44")
    val start = Timestamp.valueOf("2017-02-03 10:37:30")

    val titleList = DB.find(Title::class.java)
        .where()
        .eq("id", id)
        .findVersionsBetween(start, end)

    return ResponseEntity.ok().body(titleList)
}

This creates an infinite loop, running the query over and over again until I quit. It appears that returning multiple versions with the same ID creates a problem when it tries to hydrate those versions.

Anyone have any idea what I'm doing wrong? This is with Kotlin and Spring Boot.

TIA,
Chas.

Rob Bygrave

unread,
Jul 25, 2022, 5:02:01 PM7/25/22
to ebean@googlegroups
I suspect it isn't running the query multiple times but that Jackson is getting into an infinite loop converting it to JSON.

You could probably confirm that by using Jackson explicitly to convert titleList into JSON. We should see that either puts Jackson into an infinite loop or somehow recursively invokes lazy loading but my suspicion is Jackson.


Cheers, Rob.

--

---
You received this message because you are subscribed to the Google Groups "Ebean ORM" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ebean+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ebean/36963de7-38a6-4df5-a742-3a5055b979bcn%40googlegroups.com.

Charles Munat

unread,
Jul 25, 2022, 5:05:35 PM7/25/22
to eb...@googlegroups.com
Outstanding. Thank you very much. I will look into this Jackson dude. First the Trail of Tears, now this. What’s next?

I need this working today so your response is nicely timely. Fingers crossed.

Cheers!
Chas.

You received this message because you are subscribed to a topic in the Google Groups "Ebean ORM" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/ebean/zUtoluISME0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to ebean+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ebean/CAC%3Dts-Ggfuic1apZhkvSr%3D0g9Yf%2BY78r1SZBJ5WeF_%3DAnw4h0Q%40mail.gmail.com.

Charles Munat

unread,
Jul 25, 2022, 6:56:27 PM7/25/22
to Ebean ORM
I tried this, but got the same problem. Any idea what I'm doing wrong?

@GetMapping("/{id}")
fun getTitle(@PathVariable id: Long): ResponseEntity<List<Version<Title>>> {
  val end = Timestamp.valueOf("2022-07-25 16:10:44")
  val start = Timestamp.valueOf("2017-02-03 10:37:30")

  val titles = DB.find(Title::class.java)
    .where()
    .eq("id", id)
    .findVersionsBetween(start, end)

  val titleList = mutableListOf<Version<Title>>()

  titles.forEach { titleList.add(it) }

  val titleJson = mapper.writeValueAsString(titleList)

  println(titleList)

  return ResponseEntity.ok().body(titleList)
}

Rob Bygrave

unread,
Jul 25, 2022, 10:02:21 PM7/25/22
to ebean@googlegroups
You don't have a stack trace right? It isn't failing with an OutOfMemoryException with an associated stack trace?  Assuming not then:

Well we were expecting to get the same "result" per say. What we are looking to do is determine if the problem is related to Jackson getting into an infinite loop or something else.

If this request failed and threw an exception for example, we'd see the infinite loop in the stack trace.  You haven't said you have a stack trace so that means you are probably looking to step through the code. I suspect it is likely going into the infinite loop at:

val titleJson = mapper.writeValueAsString(titleList)


... and so perhaps stepping into that, and then stepping through you will find out the cause of the infinite loop.

You have probably already turned on logging for io.ebean.SQL as per https://ebean.io/docs/logging/ ... but if not you should do that. That means we should be seeing if there is only the 1 sql query being executed or many.


If you haven't already, you could look to reproduce this using a test case which might make it easier to step through with a debugger but you can likely do that just as easily with the controller endpoint.

In terms of process of elimination, you can look to remove the query itself from the test scenario but instead creating fake data that matches the expectation and passing the fake data to Jackson.

Charles Munat

unread,
Jul 25, 2022, 10:18:20 PM7/25/22
to eb...@googlegroups.com
Ah. Some very helpful ideas.

I’m not getting an error. It compiles and runs, but the moment I make the GET and run the query, I get infinite individual responses. I suppose I’d get an out of memory error if I let it run long enough. Or maybe not if it’s just discarding them as it goes.

The reason I don’t think it’s the `mapper.writeValueAsString(titleList)` call is that I can run that, get the `titleList`, and use `println` to dump it to the console. It’s there and it’s fine. Readable JSON. I can `toString()` it and do the same. I can do whatever I like with it.

Except pass it to the `ResponseEntity`. And this is the weird part. I the code below I can do `println(titleJson)` and it works. But `ResponseEntity.ok().body(titleJson)` gets an infinite loop. Weirdest of all, `ResponseEntity.ok().body(titleJson.toString())` makes an infinite loop. But only when I return it in the body of a `ResponseEntity` — if I log it to the console with `println`, I’m fine.

And I can grab an individual one from the mutable list: `titleList.component1()` and pass that back or turn it to JSON and pass it back. No issues.

And I get the infinite loop even if I pass an empty list! I am missing something important here.

I’ll keep messing with it. Thanks for the help.

Chas.

Charles Munat

unread,
Jul 25, 2022, 11:25:10 PM7/25/22
to Ebean ORM
It is definitely Jackson which causes the infinite recursion when it is given a mutable list, even a list of plain strings. And only when it is passed to the ResponseEntity.body(). For some reason, println can print it out with no recursion.

Charles Munat

unread,
Jul 26, 2022, 1:03:39 AM7/26/22
to Ebean ORM
I don't think that it's the JSON. I can use the mapper to print out the JSON with no problem. I can convert it to a string. The moment I put it into the `body` to return it, I get the infinite loop. WTF? It's a string. Why can I print it out to the console with println but not pass it to body -- passing a literal string to body works fine. The only thing I can think of is that there is some kind of lazy behavior going on here, and because it's a list it creates the response over and over again for each item in the list. Wow.
Reply all
Reply to author
Forward
0 new messages