From a3540057b7f594e9ab226622711e81829411ebff Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Wed, 18 Jan 2023 09:24:47 +0100 Subject: [PATCH] BUG: Memory-leak on streaming queries when LoadBuffers are not aligned --- .../basic/TestPersistenceContextMany.java | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 ebean-test/src/test/java/org/tests/basic/TestPersistenceContextMany.java diff --git a/ebean-test/src/test/java/org/tests/basic/TestPersistenceContextMany.java b/ebean-test/src/test/java/org/tests/basic/TestPersistenceContextMany.java new file mode 100644 index 000000000..2a5f9b6f8 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/basic/TestPersistenceContextMany.java @@ -0,0 +1,169 @@ +package org.tests.basic; + +import io.ebean.DB; +import io.ebean.DatabaseFactory; +import io.ebean.QueryIterator; +import io.ebean.config.DatabaseConfig; +import io.ebean.xtest.BaseTestCase; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import javax.persistence.CascadeType; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public class TestPersistenceContextMany extends BaseTestCase { + + + @Entity + @Inheritance + public abstract static class TestModel3 { + + @Id + private int id; + + @Size(max = 255) + private String someData; + + @OneToMany(cascade = CascadeType.REFRESH) + //@Where(clause = "1=1") + private List many1; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public void setSomeData(String someData) { + this.someData = someData; + } + + public String getSomeData() { + return someData; + } + } + + @Entity + @Inheritance + @DiscriminatorValue("A") + public static class TestModel3A extends TestModel3 { + + } + + @Entity + @Inheritance + @DiscriminatorValue("B") + public static class TestModel3B extends TestModel3 { + @OneToMany(cascade = CascadeType.REFRESH) + private List many2; + + } + + @Entity + public static class TestModel3Many1 { + @Id + private int id; + + @ManyToOne + private TestModel3 base; + } + + @Entity + public static class TestModel3Many2 { + @Id + private int id; + + @ManyToOne + private TestModel3B base; + } + + @Test + @Disabled + void initDb() { + DatabaseConfig config = new DatabaseConfig(); + config.setName("h2-batch"); + config.loadFromProperties(); + config.setDdlExtra(false); + config.getDataSourceConfig().setUsername("sa"); + config.getDataSourceConfig().setPassword("sa"); + config.getDataSourceConfig().setUrl("jdbc:h2:file:./testsFileMany;DB_CLOSE_ON_EXIT=FALSE;NON_KEYWORDS=KEY,VALUE"); + config.addClass(TestModel3.class); + config.addClass(TestModel3A.class); + config.addClass(TestModel3B.class); + config.addClass(TestModel3Many1.class); + config.addClass(TestModel3Many2.class); + DatabaseFactory.create(config); + + String base = "x".repeat(240); + // 10 mio TestModel - each needs about 1/4 kbytes -> 2,5 GB in total + List batch = new ArrayList<>(); + for (int i = 0; i < 1_000_000; i++) { + + TestModel3 m = i == 5 ? new TestModel3A() : new TestModel3B(); // produces "off-by-one" + m.setSomeData(base + i); // ensure we have not duplicates + batch.add(m); + if (i % 1000 == 0) { + DB.saveAll(batch); + batch.clear(); + } + if (i % 100000 == 0) { + System.out.println(i); + } + } + DB.saveAll(batch); + } + + @Test + @Disabled + void testFindEachFindList() { + DatabaseConfig config = new DatabaseConfig(); + config.setName("h2-batch"); + config.loadFromProperties(); + config.setDdlRun(false); + config.getDataSourceConfig().setUsername("sa"); + config.getDataSourceConfig().setPassword("sa"); + config.getDataSourceConfig().setUrl("jdbc:h2:file:./testsFileMany;DB_CLOSE_ON_EXIT=FALSE;NON_KEYWORDS=KEY,VALUE"); + config.addClass(TestModel3.class); + config.addClass(TestModel3A.class); + config.addClass(TestModel3B.class); + config.addClass(TestModel3Many1.class); + config.addClass(TestModel3Many2.class); + DatabaseFactory.create(config); + + AtomicInteger i = new AtomicInteger(); + System.out.println("Doing findEach"); + DB.find(TestModel3.class).select("*").findEach(c -> { + i.incrementAndGet(); + }); + System.out.println("Read " + i + " entries"); + + i.set(0); + System.out.println("Doing findStream"); + DB.find(TestModel3.class).select("*").findStream().forEach(c -> i.incrementAndGet()); + System.out.println("Read " + i + " entries"); + + i.set(0); + System.out.println("Doing findIterate"); + QueryIterator iter = DB.find(TestModel3.class).select("*").findIterate(); + while (iter.hasNext()) { + iter.next(); + i.incrementAndGet(); + } + System.out.println("Read " + i + " entries"); + + System.out.println("Doing FindList, will hold all entries in memory. Expect OOM with -Xmx100m."); + List lst = DB.find(TestModel3.class).select("*").findList(); + System.out.println("Read " + lst.size() + " entries"); + } +}