From 2549eace09f5926e5948fd6b8414a5d156061ce3 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 8 Oct 2021 11:20:54 +1300 Subject: [PATCH] #2406 - Memory leak in Query.findEach --- .../server/core/OrmQueryRequest.java | 8 ----- .../io/ebeaninternal/server/query/CQuery.java | 1 - .../tests/basic/TestPersistenceContext.java | 36 +++++++++++++++++++ 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index 4335b56d5..85d89ffe2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -201,14 +201,6 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery return persistenceContext; } - /** - * Add the bean to the persistence context. - */ - public void persistenceContextAdd(EntityBean bean) { - Object id = beanDescriptor.getId(bean); - beanDescriptor.contextPut(persistenceContext, id, bean); - } - /** * This will create a local (readOnly) transaction if no current transaction * exists. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java index a865275c6..45a5b5a11 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java @@ -416,7 +416,6 @@ public final class CQuery implements DbReadContext, CancelableQuery, SpiProfi } else { // nextBean set to previously read currentBean nextBean = currentBean; - request.persistenceContextAdd(nextBean); // check the current row we have just moved to if (checkForDifferentBean()) { return true; diff --git a/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java b/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java index e18c55c57..ea894091a 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java +++ b/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java @@ -2,11 +2,15 @@ package org.tests.basic; import io.ebean.BaseTestCase; import io.ebean.DB; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicInteger; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -78,4 +82,36 @@ public class TestPersistenceContext extends BaseTestCase { } } + @Disabled + @Test + void findWithGcTest() { + for (int j = 0; j < 20; j++) { + for (int i = 0; i < 500; i++) { + Customer c = new Customer(); + c.setName("Customer #" + i); + DB.save(c); + } + int customerCount = DB.find(Customer.class).findCount(); + AtomicInteger count = new AtomicInteger(customerCount); + + WeakHashMap customers = new WeakHashMap<>(); + + DB.find(Customer.class).fetch("orders").findEach(customer -> { + customers.put(customer, customer.getId()); + if (count.decrementAndGet() == 0) { + // Trigger garbage collection on last iteration and check if beans disappear from memory + System.gc(); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } + customers.size(); // expunge stale entries + System.out.println("Total instances: " + customerCount + ", instances left in memory: " + customers.size()); + } + }); + } + + } }